<commit_before>ClientConfig.grid.Groups = function(config) {
    config = config || {};
    Ext.applyIf(config,{
		url: ClientConfig.config.connectorUrl,
		id: 'clientconfig-grid-groups',
		baseParams: {
            action: 'mgr/groups/getlist'
        },
        emptyText: _('clientconfig.error.noresults'),
		fields: [
            {name: 'id', type: 'int'},
            {name: 'label', type: 'string'},
            {name: 'description', type: 'string'},
            {name: 'sortorder', type: 'int'},
            {name: 'settings_count', type: 'int'}
        ],
        paging: true,
		remoteSort: true,
		columns: [{
			header: _('clientconfig.id'),
			dataIndex: 'id',
			sortable: true,
			width: .1
		},{
			header: _('clientconfig.label'),
			dataIndex: 'label',
		    sortable: true,
			width: .3
		},{
			header: _('clientconfig.description'),
			dataIndex: 'description',
			sortable: true,
			width: .5
		},{
			header: _('clientconfig.settings_count'),
			dataIndex: 'settings_count',
		    sortable: true,
			width: .1
		}],
        tbar: [{
            text: _('clientconfig.add_group'),
            handler: this.addGroup,
            scope: this
        }, '->', {
            text: _('clientconfig.export_groups'),
            handler: this.exportGroups,
            scope: this
        }, '-', {
            text: _('clientconfig.import_groups'),
            handler: this.importGroups,
            scope: this
        }]
    });
    ClientConfig.grid.Groups.superclass.constructor.call(this,config);
};
Ext.extend(ClientConfig.grid.Groups,MODx.grid.Grid,{
    addGroup: function() {
        var win = MODx.load({
            xtype: 'clientconfig-window-group',
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            }
        });
        win.show();
    },
    updateGroup: function() {
        var record = this.menu.record;
        var win = MODx.load({
            xtype: 'clientconfig-window-group',
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            },
            isUpdate: true
        });
        win.setValues(record);
        win.show();
    },
    removeGroup: function() {
        var id = this.menu.record.id;
        MODx.msg.confirm({
            title: _('clientconfig.remove_group'),
            text: _('clientconfig.remove_group.confirm'),
            url: this.config.url,
            params: {
                action: 'mgr/groups/remove',
                id: id
            },
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            }
        });
    },
    getMenu: function(node) {
        var m = [];

        m.push({
            text: _('clientconfig.update_group'),
            handler: this.updateGroup,
            scope: this
        },'-',{
            text: _('clientconfig.remove_group'),
            handler: this.removeGroup,
            scope: this
        });
        return m;
    },

    exportGroups: function() {
        Ext.Msg.confirm(_('clientconfig.export_groups'), _('clientconfig.export_groups.confirm'), function(e) {
            if (e == 'yes') {
                window.location = ClientConfig.config.connectorUrl + '?action=mgr/groups/export&HTTP_MODAUTH=' + MODx.siteId;
            }
        });
    },

    importGroups: function() {
        var win = MODx.load({
            xtype: 'clientconfig-window-import',
            title: _('clientconfig.import_groups'),
            introduction: _('clientconfig.import_groups.desc'),
            what: _('clientconfig.groups'),
            baseParams: {
                action: 'mgr/groups/import'
            },
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            }
        });
        win.show();
    }
});
Ext.reg('clientconfig-grid-groups',ClientConfig.grid.Groups);
<commit_msg>Update grid.groups.js<commit_after>ClientConfig.grid.Groups = function(config) {
    config = config || {};
    Ext.applyIf(config,{
		url: ClientConfig.config.connectorUrl,
		id: 'clientconfig-grid-groups',
		baseParams: {
            action: 'mgr/groups/getlist'
        },
        save_action: 'mgr/groups/updatefromgrid',
        autosave: true,
        emptyText: _('clientconfig.error.noresults'),
		fields: [
            {name: 'id', type: 'int'},
            {name: 'label', type: 'string'},
            {name: 'description', type: 'string'},
            {name: 'sortorder', type: 'int'},
            {name: 'settings_count', type: 'int'}
        ],
        paging: true,
		remoteSort: true,
		columns: [{
			header: _('clientconfig.id'),
			dataIndex: 'id',
			sortable: true,
			width: .1
		},{
			header: _('clientconfig.label'),
			dataIndex: 'label',
			editor: { xtype: 'textfield' },
		    sortable: true,
			width: .3
		},{
			header: _('clientconfig.description'),
			dataIndex: 'description',
			editor: { xtype: 'textfield' },
			sortable: true,
			width: .5
		},{
			header: _('clientconfig.settings_count'),
			dataIndex: 'settings_count',
		    sortable: true,
			width: .1
		},{
			header: _('clientconfig.sortorder'),
			dataIndex: 'sortorder',
			editor: {
			    xtype: 'numberfield',
			    allowDecimal: false,
			    allowNegative: false
			},
			sortable: true,
			width: .1
		}],
        tbar: [{
            text: _('clientconfig.add_group'),
            handler: this.addGroup,
            scope: this
        }, '->', {
            text: _('clientconfig.export_groups'),
            handler: this.exportGroups,
            scope: this
        }, '-', {
            text: _('clientconfig.import_groups'),
            handler: this.importGroups,
            scope: this
        }]
    });
    ClientConfig.grid.Groups.superclass.constructor.call(this,config);
};
Ext.extend(ClientConfig.grid.Groups,MODx.grid.Grid,{
    addGroup: function() {
        var win = MODx.load({
            xtype: 'clientconfig-window-group',
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            }
        });
        win.show();
    },
    updateGroup: function() {
        var record = this.menu.record;
        var win = MODx.load({
            xtype: 'clientconfig-window-group',
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            },
            isUpdate: true
        });
        win.setValues(record);
        win.show();
    },
    removeGroup: function() {
        var id = this.menu.record.id;
        MODx.msg.confirm({
            title: _('clientconfig.remove_group'),
            text: _('clientconfig.remove_group.confirm'),
            url: this.config.url,
            params: {
                action: 'mgr/groups/remove',
                id: id
            },
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            }
        });
    },
    getMenu: function(node) {
        var m = [];

        m.push({
            text: _('clientconfig.update_group'),
            handler: this.updateGroup,
            scope: this
        },'-',{
            text: _('clientconfig.remove_group'),
            handler: this.removeGroup,
            scope: this
        });
        return m;
    },

    exportGroups: function() {
        Ext.Msg.confirm(_('clientconfig.export_groups'), _('clientconfig.export_groups.confirm'), function(e) {
            if (e == 'yes') {
                window.location = ClientConfig.config.connectorUrl + '?action=mgr/groups/export&HTTP_MODAUTH=' + MODx.siteId;
            }
        });
    },

    importGroups: function() {
        var win = MODx.load({
            xtype: 'clientconfig-window-import',
            title: _('clientconfig.import_groups'),
            introduction: _('clientconfig.import_groups.desc'),
            what: _('clientconfig.groups'),
            baseParams: {
                action: 'mgr/groups/import'
            },
            listeners: {
                success: {fn: function(r) {
                    this.refresh();
                },scope: this},
                scope: this
            }
        });
        win.show();
    }
});
Ext.reg('clientconfig-grid-groups',ClientConfig.grid.Groups);
<|endoftext|>


<commit_before>'use strict'

const pushJenkinsUpdate = require('../lib/push-jenkins-update')
const enabledRepos = ['citgm', 'http-parser', 'node']

const jenkinsIpWhitelist = process.env.JENKINS_WORKER_IPS ? process.env.JENKINS_WORKER_IPS.split(',') : []

function isJenkinsIpWhitelisted (req) {
  const ip = req.connection.remoteAddress

  if (jenkinsIpWhitelist.length && !jenkinsIpWhitelist.includes(ip)) {
    req.log.warn({ ip }, 'Ignoring, not allowed to push Jenkins updates')
    return false
  }

  return true
}

module.exports = function (app) {
  app.post('/:repo/jenkins/start', (req, res) => {
    const isValid = pushJenkinsUpdate.validate(req.body)
    const repo = req.params.repo

    if (!isValid) {
      return res.status(400).end('Invalid payload')
    }

    if (!enabledRepos.includes(repo)) {
      return res.status(400).end('Invalid repository')
    }

    if (!isJenkinsIpWhitelisted(req)) {
      return res.status(401).end('Invalid Jenkins IP')
    }

    pushJenkinsUpdate.pushStarted({
      owner: 'nodejs',
      repo,
      logger: req.log
    }, req.body)

    res.status(201).end()
  })

  app.post('/:repo/jenkins/end', (req, res) => {
    const isValid = pushJenkinsUpdate.validate(req.body)
    const repo = req.params.repo

    if (!isValid) {
      return res.status(400).end('Invalid payload')
    }

    if (!enabledRepos.includes(repo)) {
      return res.status(400).end('Invalid repository')
    }

    if (!isJenkinsIpWhitelisted(req)) {
      return res.status(401).end('Invalid Jenkins IP')
    }

    pushJenkinsUpdate.pushEnded({
      owner: 'nodejs',
      repo,
      logger: req.log
    }, req.body)

    res.status(201).end()
  })
}
<commit_msg>jenkins: handle IPv4 remote address converted to IPv6 (#161)<commit_after>'use strict'

const pushJenkinsUpdate = require('../lib/push-jenkins-update')
const enabledRepos = ['citgm', 'http-parser', 'node']

const jenkinsIpWhitelist = process.env.JENKINS_WORKER_IPS ? process.env.JENKINS_WORKER_IPS.split(',') : []

function isJenkinsIpWhitelisted (req) {
  const ip = req.connection.remoteAddress.split(':').pop()

  if (jenkinsIpWhitelist.length && !jenkinsIpWhitelist.includes(ip)) {
    req.log.warn({ ip }, 'Ignoring, not allowed to push Jenkins updates')
    return false
  }

  return true
}

module.exports = function (app) {
  app.post('/:repo/jenkins/start', (req, res) => {
    const isValid = pushJenkinsUpdate.validate(req.body)
    const repo = req.params.repo

    if (!isValid) {
      return res.status(400).end('Invalid payload')
    }

    if (!enabledRepos.includes(repo)) {
      return res.status(400).end('Invalid repository')
    }

    if (!isJenkinsIpWhitelisted(req)) {
      return res.status(401).end('Invalid Jenkins IP')
    }

    pushJenkinsUpdate.pushStarted({
      owner: 'nodejs',
      repo,
      logger: req.log
    }, req.body)

    res.status(201).end()
  })

  app.post('/:repo/jenkins/end', (req, res) => {
    const isValid = pushJenkinsUpdate.validate(req.body)
    const repo = req.params.repo

    if (!isValid) {
      return res.status(400).end('Invalid payload')
    }

    if (!enabledRepos.includes(repo)) {
      return res.status(400).end('Invalid repository')
    }

    if (!isJenkinsIpWhitelisted(req)) {
      return res.status(401).end('Invalid Jenkins IP')
    }

    pushJenkinsUpdate.pushEnded({
      owner: 'nodejs',
      repo,
      logger: req.log
    }, req.body)

    res.status(201).end()
  })
}
<|endoftext|>


<commit_before>/**
 * Contains addCompileResult that decides
 * how current compiled styles should be exposed outside:
 * whether they will be just bundled together with other styles,
 * or will be available as static assets on the server as well.
 */
BasicCompiler = class BasicCompiler {
  addCompileResult(inputFile, result) {
    const ext = inputFile.getExtension();
    const sourcePath = inputFile.getPathInPackage();
    const targetPath = ext !== 'css' ? sourcePath + '.css' : sourcePath;

    // Combine all styles from other than imports folders.
    if (sourcePath.indexOf('imports') === -1) {
      inputFile.addStylesheet({
        data: result.css,
        path: targetPath,
        sourceMap: result.sourceMap
      });
      return;
    }

    let resultCss = result.css;
    if (! Meteor.isDevelopment) {
      resultCss = CssTools.minifyCss(resultCss)[0];
    }

    // CSS files that comes from `imports` folder
    // can be downloaded as server assets.
    inputFile.addAsset({
      data: resultCss || '',
      path: sourcePath
    });

    if (this.customAddCompileResult) {
      this.customAddCompileResult(inputFile, result);
    }
  }
};

classMixin = function(target, source) {
  target = target.prototype; source = source.prototype;

  Object.getOwnPropertyNames(source).forEach(name => {
    if (name !== 'constructor') {
      Object.defineProperty(target, name,
        Object.getOwnPropertyDescriptor(source, name));
    }
 });
};
<commit_msg>fix(css-coilers): skip processing files from node_modules.<commit_after>const IMPORTS_REGEXP = /imports(\\|\/)/;
const NPM_REGEXP = /node_modules(\\|\/)/;

/**
 * Contains addCompileResult that decides
 * how current compiled styles should be exposed outside:
 * whether they will be just bundled together with other styles,
 * or will be available as static assets on the server as well.
 */
BasicCompiler = class BasicCompiler {
  addCompileResult(inputFile, result) {
    const ext = inputFile.getExtension();
    const sourcePath = inputFile.getPathInPackage();
    const targetPath = ext !== 'css' ? sourcePath + '.css' : sourcePath;

    // Skip files from node_modules.
    if (NPM_REGEXP.test(sourcePath)) return;

    // Combine all styles from other than imports folders.
    if (! IMPORTS_REGEXP.test(sourcePath)) {
      inputFile.addStylesheet({
        data: result.css,
        path: targetPath,
        sourceMap: result.sourceMap
      });
      return;
    }

    let resultCss = result.css;
    if (! Meteor.isDevelopment) {
      resultCss = CssTools.minifyCss(resultCss)[0];
    }

    // CSS files that comes from `imports` folder
    // can be downloaded as server assets.
    inputFile.addAsset({
      data: resultCss || '',
      path: sourcePath
    });

    if (this.customAddCompileResult) {
      this.customAddCompileResult(inputFile, result);
    }
  }
};

classMixin = function(target, source) {
  target = target.prototype; source = source.prototype;

  Object.getOwnPropertyNames(source).forEach(name => {
    if (name !== 'constructor') {
      Object.defineProperty(target, name,
        Object.getOwnPropertyDescriptor(source, name));
    }
 });
};
<|endoftext|>


<commit_before>function Module() {
//모듈의 constructor
	this.digitalValue = new Array(20);
	this.analogValue = new Array(8);

	this.remoteDigitalValue = new Array(20);
	this.readablePorts = null;
	this.remainValue = null;

	this.sensory = {
		temperature: 0,
		geomagnetic: 0,
		sonar: 0,
		leftwheel: 0,
		rightwheel: 0
	};
}

//필요시 Handler Data 초기값 설정
Module.prototype.init = function(handler, config) {
};

//필요시 연결직후 Hardware에 보내는 초기값 설정
Module.prototype.requestInitialData = function() {
	return null;
};

//연결직후 Hardware에서보내는 Inital데이터의 Vaildation
Module.prototype.checkInitialData = function(data, config) {
	return true;
};

//Hardware에서 보내는 모든 데이터의 Vaildation
Module.prototype.validateLocalData = function(data) {
	return true;
};

// 서버에서 보내온 데이터 세팅
Module.prototype.handleRemoteData = function(handler) {
	this.readablePorts = handler.read('readablePorts');
	var digitalValue = this.remoteDigitalValue;
	for (var port = 0; port < 20; port++) {
		var value = handler.read(port);
		//console.log('port: '+port+', value: '+value)
		digitalValue[port] = value;
	}
};

// Hardware에서 보내온 데이터 세팅
Module.prototype.handleLocalData = function(data) { // data: Native Buffer
	for (var i = 0; i < 32; i++) {
		var chunk;
		if(!this.remainValue) {
			chunk = data[i];
		} else {
			chunk = this.remainValue;
			i--;
		}
		if (chunk >> 7) {
			if ((chunk >> 6) & 1) {
				var nextChunk = data[i + 1];
				if(!nextChunk && nextChunk !== 0) {
					this.remainValue = chunk;
				} else {
					this.remainValue = null;

					var port = (chunk >> 3) & 7;
					this.analogValue[port] = ((chunk & 7) << 7) +
						(nextChunk & 127);
					if(port == 7){
						var sensory = this.sensory;
						sensory.temperature = ((chunk & 7) << 7) +
						(nextChunk & 127);
					}
					else if(port == 1){
						var sensory = this.sensory;
						sensory.sonar = ((chunk & 7) << 7) +
						(nextChunk & 127);
					}
				}
		    i++;
			} else {
				var port = (chunk >> 2) & 15;
				this.digitalValue[port] = chunk & 1;
			}
		}
	}
};

// 서버에 보낼 데이터 세팅
Module.prototype.requestRemoteData = function(handler) {
	for (var i = 0; i < this.analogValue.length; i++) {
		var value = this.analogValue[i];
		handler.write('a' + i, value);
	}
	for (var i = 0; i < this.digitalValue.length; i++) {
		var value = this.digitalValue[i];
		handler.write(i, value);
	}
	var sensory = this.sensory;
	for(var key in sensory) {
		handler.write(key, sensory[key]);
	}
};

// Hardware에 보낼 데이터 세팅
Module.prototype.requestLocalData = function() {
	var queryString = [];
	var query = (5 << 5) + (4 << 1); // remocon
	queryString.push(query);
	query = (5 << 5) + (12 << 1); // push button
	queryString.push(query);

	var digitalValue = this.remoteDigitalValue;
	var value = digitalValue[7];	// red led
	query = (7 << 5) + (7 << 1) + (value == 255 ? 1 : 0);
	queryString.push(query);

	value = digitalValue[8];	// blue led
	query = (7 << 5) + (8 << 1) + (value == 255 ? 1 : 0);
	queryString.push(query);

	value = digitalValue[13];	// vibration motor
	query = (7 << 5) + (13 << 1) + (value == 255 ? 1 : 0);
	queryString.push(query);

	for(var i = 9; i < 12; i++){
		value = digitalValue[i];	// 3color r, g, b
		if( value > 0 && value < 255 ){
			query = (6 << 5) + (i << 1) + (value >> 7);
			queryString.push(query);
			query = value & 127;
			queryString.push(query);
		} else if(value == 0){	// returned by system
			// digitalWrite(LOW);
			query = (7 << 5) + (i << 1) + (value == 255 ? 1 : 0);
			queryString.push(query);
		}
	}

	value = digitalValue[14];	// drive
	if( value > 0 && value < 255 ){
		query = (6 << 5) + (14 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	} else if(value == 0){ // returned by system
		// drive stop
		value = 1;
		query = (6 << 5) + (14 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	}

	value = digitalValue[15];	// buzzer
	if( value > 0 && value < 255 ){
		query = (6 << 5) + (15 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	} else if(value == 0){ // returned by system
		// buzzer stop
		value = 24;
		query = (6 << 5) + (15 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	}
	return queryString;
};

// 서버 Connect 종료시 값 세팅
Module.prototype.reset = function() {
};

module.exports = new Module();<commit_msg>Edit Codestar temperature sensor, tilt sensor<commit_after>function Module() {
//모듈의 constructor
	this.digitalValue = new Array(20);
	this.analogValue = new Array(8);

	this.remoteDigitalValue = new Array(20);
	this.readablePorts = null;
	this.remainValue = null;

	this.sensory = {
		temperature: 0,
		geomagnetic: 0,
		sonar: 0,
		leftwheel: 0,
		rightwheel: 0,
		tilt: 0
	};
}

//필요시 Handler Data 초기값 설정
Module.prototype.init = function(handler, config) {
};

//필요시 연결직후 Hardware에 보내는 초기값 설정
Module.prototype.requestInitialData = function() {
	return null;
};

//연결직후 Hardware에서보내는 Inital데이터의 Vaildation
Module.prototype.checkInitialData = function(data, config) {
	return true;
};

//Hardware에서 보내는 모든 데이터의 Vaildation
Module.prototype.validateLocalData = function(data) {
	return true;
};

// 서버에서 보내온 데이터 세팅
Module.prototype.handleRemoteData = function(handler) {
	this.readablePorts = handler.read('readablePorts');
	var digitalValue = this.remoteDigitalValue;
	for (var port = 0; port < 20; port++) {
		var value = handler.read(port);
		//console.log('port: '+port+', value: '+value)
		digitalValue[port] = value;
	}
};

// Hardware에서 보내온 데이터 세팅
Module.prototype.handleLocalData = function(data) { // data: Native Buffer
	for (var i = 0; i < 32; i++) {
		var chunk;
		if(!this.remainValue) {
			chunk = data[i];
		} else {
			chunk = this.remainValue;
			i--;
		}
		if (chunk >> 7) {
			if ((chunk >> 6) & 1) {
				var nextChunk = data[i + 1];
				if(!nextChunk && nextChunk !== 0) {
					this.remainValue = chunk;
				} else {
					this.remainValue = null;

					var port = (chunk >> 3) & 7;
					this.analogValue[port] = ((chunk & 7) << 7) +
						(nextChunk & 127);
					if(port == 5){
						var sensory = this.sensory;
						sensory.temperature = Math.round((500 * (((chunk & 7) << 7) +
						(nextChunk & 127))) / 1024);
					}
					else if(port == 1){
						var sensory = this.sensory;
						sensory.sonar = ((chunk & 7) << 7) +
						(nextChunk & 127);
					}
					else if(port == 4){
						var sensory = this.sensory;
						sensory.tilt = (((chunk & 7) << 7) +
						(nextChunk & 127)) == 0 ? 0 : 1;
					}
				}
		    i++;
			} else {
				var port = (chunk >> 2) & 15;
				this.digitalValue[port] = chunk & 1;
			}
		}
	}
};

// 서버에 보낼 데이터 세팅
Module.prototype.requestRemoteData = function(handler) {
	for (var i = 0; i < this.analogValue.length; i++) {
		var value = this.analogValue[i];
		handler.write('a' + i, value);
	}
	for (var i = 0; i < this.digitalValue.length; i++) {
		var value = this.digitalValue[i];
		handler.write(i, value);
	}
	var sensory = this.sensory;
	for(var key in sensory) {
		handler.write(key, sensory[key]);
	}
};

// Hardware에 보낼 데이터 세팅
Module.prototype.requestLocalData = function() {
	var queryString = [];
	var query = (5 << 5) + (4 << 1); // remocon
	queryString.push(query);
	query = (5 << 5) + (12 << 1); // push button
	queryString.push(query);

	var digitalValue = this.remoteDigitalValue;
	var value = digitalValue[7];	// red led
	query = (7 << 5) + (7 << 1) + (value == 255 ? 1 : 0);
	queryString.push(query);

	value = digitalValue[8];	// blue led
	query = (7 << 5) + (8 << 1) + (value == 255 ? 1 : 0);
	queryString.push(query);

	value = digitalValue[13];	// vibration motor
	query = (7 << 5) + (13 << 1) + (value == 255 ? 1 : 0);
	queryString.push(query);

	for(var i = 9; i < 12; i++){
		value = digitalValue[i];	// 3color r, g, b
		if( value > 0 && value < 255 ){
			query = (6 << 5) + (i << 1) + (value >> 7);
			queryString.push(query);
			query = value & 127;
			queryString.push(query);
		} else if(value == 0){	// returned by system
			// digitalWrite(LOW);
			query = (7 << 5) + (i << 1) + (value == 255 ? 1 : 0);
			queryString.push(query);
		}
	}

	value = digitalValue[14];	// drive
	if( value > 0 && value < 255 ){
		query = (6 << 5) + (14 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	} else if(value == 0){ // returned by system
		// drive stop
		value = 1;
		query = (6 << 5) + (14 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	}

	value = digitalValue[15];	// buzzer
	if( value > 0 && value < 255 ){
		query = (6 << 5) + (15 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	} else if(value == 0){ // returned by system
		// buzzer stop
		value = 24;
		query = (6 << 5) + (15 << 1) + (value >> 7);
		queryString.push(query);
		query = value & 127;
		queryString.push(query);
	}
	return queryString;
};

// 서버 Connect 종료시 값 세팅
Module.prototype.reset = function() {
};

module.exports = new Module();<|endoftext|>


<commit_before>/**
 * dev-server - serves static resources for developing "earth" locally
 */

"use strict";

console.log("============================================================");
console.log(new Date().toISOString() + " - Starting");

var util = require("util");

/**
 * Returns true if the response should be compressed.
 */
function compressionFilter(req, res) {
    return (/json|text|javascript|font/).test(res.getHeader('Content-Type'));
}

/**
 * Adds headers to a response to enable caching.
 */
function cacheControl() {
    return function(req, res, next) {
        res.setHeader("Cache-Control", "public, max-age=300");
        return next();
    };
}

function logger() {
    express.logger.token("date", function() {
        return new Date().toISOString();
    });
    express.logger.token("response-all", function(req, res) {
        return (res._header ? res._header : "").trim();
    });
    express.logger.token("request-all", function(req, res) {
        return util.inspect(req.headers);
    });
    return express.logger(
        ':date - info: :remote-addr :req[cf-connecting-ip] :req[cf-ipcountry] :method :url HTTP/:http-version ' +
        '":user-agent" :referrer :req[cf-ray] :req[accept-encoding]\\n:request-all\\n\\n:response-all\\n');
}

var port = process.argv[2];
var express = require("express");
var app = express();

app.use(cacheControl());
app.use(express.compress({filter: compressionFilter}));
app.use(logger());
app.use(express.static("public"));

app.listen(port);
console.log("Listening on port " + port + "...");
<commit_msg>commit code<commit_after>/**
 * dev-server - serves static resources for developing "earth" locally
 */

"use strict";

console.log("============================================================");
console.log(new Date().toISOString() + " - 后天服务开始");

var util = require("util");

/**
 * Returns true if the response should be compressed.
 */
function compressionFilter(req, res) {
    return (/json|text|javascript|font/).test(res.getHeader('Content-Type'));
}

/**
 * Adds headers to a response to enable caching.
 */
function cacheControl() {
    return function(req, res, next) {
        res.setHeader("Cache-Control", "public, max-age=300");
        return next();
    };
}

function logger() {
    express.logger.token("date", function() {
        return new Date().toISOString();
    });
    express.logger.token("response-all", function(req, res) {
        return (res._header ? res._header : "").trim();
    });
    express.logger.token("request-all", function(req, res) {
        return util.inspect(req.headers);
    });
    return express.logger(
        ':date - info: :remote-addr :req[cf-connecting-ip] :req[cf-ipcountry] :method :url HTTP/:http-version ' +
        '":user-agent" :referrer :req[cf-ray] :req[accept-encoding]\\n:request-all\\n\\n:response-all\\n');
}
// 启动服务时调用第二个参数作为启动服务的端口
var port = process.argv[2];
var express = require("express");
var app = express();

app.use(cacheControl());
app.use(express.compress({filter: compressionFilter}));
app.use(logger());
app.use(express.static("public"));

app.listen(port);
console.log("Listening on port " + port + "...");
<|endoftext|>


<commit_before>const _ = require('lodash');
const BaseModule = require('./baseModule');

/**
 * NOTE
 * microbit getGesture 결과 목록표
 * MICROBIT_ACCELEROMETER_EVT_TILT_UP	1
 * MICROBIT_ACCELEROMETER_EVT_TILT_DOWN	2
 * MICROBIT_ACCELEROMETER_EVT_TILT_LEFT	3
 * MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT	4
 * MICROBIT_ACCELEROMETER_EVT_FACE_UP	5
 * MICROBIT_ACCELEROMETER_EVT_FACE_DOWN	6
 * MICROBIT_ACCELEROMETER_EVT_FREEFALL	7
 * MICROBIT_ACCELEROMETER_EVT_3G	8
 * MICROBIT_ACCELEROMETER_EVT_6G	9
 * MICROBIT_ACCELEROMETER_EVT_8G	10
 * MICROBIT_ACCELEROMETER_EVT_SHAKE	11
 *
 */

const MICROBIT_BUFFER_SIZE = 16;
const functionKeys = {
    TEST_MESSAGE: 0xfa,
    RESET: 0xfe,
    CHECK_READY: 0xff,
    SET_LED: 0x01,
    SET_STRING: 0x02,
    SET_IMAGE: 0x03,
    SET_TONE: 0x04,
    SET_TEMPO: 0x05,
    SET_RELATIVE_TEMPO: 0x06,
    SET_DIGITAL: 0x07,
    SET_ANALOG: 0x08,
    RESET_SCREEN: 0x09,
    SET_ANALOG_PERIOD: 0x10,
    SET_SERVO: 0x11,
    SET_SERVO_PERIOD: 0x12,
    SET_CUSTOM_IMAGE: 0x13,

    GET_LED: 0x31,
    GET_ANALOG: 0x32,
    GET_DIGITAL: 0x33,
    GET_BUTTON: 0x34,
    GET_LIGHT_LEVEL: 0x35,
    GET_TEMPERATURE: 0x36,
    GET_COMPASS_HEADING: 0x37,
    GET_ACCELEROMETER: 0x38,
    GET_PITCH: 0x39,
    GET_ROLL: 0x40,
    GET_GESTURE: 0x41,
    PLAY_NOTE: 0x04,
    CHANGE_BPM: 0x05,
    SET_BPM: 0x06,
    ACC_REGULAR: 0xaa,
    SENSOR_REGULAR: 0xab,
};

const EXCEPTION_COMMAND_CODE = [170, 171, 172];

class Microbit extends BaseModule {
    constructor() {
        super();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.startChecksum = [0xff, 0x01];
        this.endChecksum = [0x0d, 0x0a];

        this.resetMicrobitStatusMap();
        this.commandQueue = [];
    }

    resetMicrobitStatusMap() {
        this.microbitStatusMap = {
            payload: undefined,
            sensorData: {
                digital: {},
                analog: {},
                led: {
                    0: [0, 0, 0, 0, 0],
                    1: [0, 0, 0, 0, 0],
                    2: [0, 0, 0, 0, 0],
                    3: [0, 0, 0, 0, 0],
                    4: [0, 0, 0, 0, 0],
                },
                button: false,
                lightLevel: 0,
                temperature: 0,
                compassHeading: 0,
                accelerometer: {
                    x: 0,
                    y: 0,
                    z: 0,
                    strength: 0,
                },
            },
        };
    }

    /**
     * payload 에 앞, 뒤 고정 버퍼를 추가한다.
     * @param {number} key
     * @param {number[]?} payload
     * @returns {Buffer}
     */
    makeBuffer(key, payload) {
        const payloadLength =
            MICROBIT_BUFFER_SIZE -
            this.startChecksum.length -
            this.endChecksum.length -
            1; // key length

        // payload 는 최대 payloadLength 까지. 이보다 적은 경우 0 으로 fill.
        // payload 가 없는 경우는 빈 배열로 대체한다.
        const slicedPayload = _.chain(_.fill(Array(payloadLength), 0))
            .zipWith(payload || [], (original, input) => input || 0)
            .slice(0, payloadLength)
            .value();

        return Buffer.from(
            this.startChecksum
                .concat([key])
                .concat(...slicedPayload)
                .concat(this.endChecksum)
        );
    }

    // socketReconnection() {
    //     this.socket.send(this.handler.encode());
    // }

    requestInitialData() {
        const aa = this.makeBuffer(functionKeys.CHECK_READY);
        console.log('send to : ', aa);
        return aa;
    }

    // validateLocalData(data) {
    //     return data[0] === this.BUFFER_END_ACK_FLAG;
    // }

    setSerialPort(sp) {
        this.sp = sp;
    }

    setSocket(socket) {
        this.socket = socket;
    }

    /**
     * 정상 handshake response 는 [ff 11 22 33 ~ 0d 0a] 이다.
     * @param {Buffer} data
     * @param config
     * @returns {boolean}
     */
    checkInitialData(data, config) {
        // data[1~4] 는 commandId 로, 체크하지 않는다.
        console.log('checkInitialData : ', data);
        // return true;
        return (
            data[0] === functionKeys.CHECK_READY &&
            data[1] === 0x11 &&
            data[2] === 0x22 &&
            data[3] === 0x33
        );
    }

    requestRemoteData(handler) {
        handler.write('payload', this.microbitStatusMap);
    }

    lostController(connector, stateCallback) {
        // 아무일도 안하지만, 해당 함수가 선언되면 lostTimer 가 선언되지 않음.
    }

    handleRemoteData(handler) {
        const type = handler.read('type') || undefined;
        const payload = handler.read('payload') || {};

        // 리퀘스트 목록이 마지막으로 확인한 버전과 다르기 때문에, 업데이트한다.
        // 업데이트는 중복되지 않는 id 의 커맨드만 뒤에 추가한다.
        this.commandQueue.push({
            type,
            payload,
        });
    }

    requestLocalData() {
        if (this.commandQueue.length !== 0 && !this.pending) {
            this.pending = true;
            const { type, payload } = this.commandQueue.shift();
            switch (type) {
                case functionKeys.SET_LED: {
                    const { x, y, value } = payload;
                    const valueType = {
                        on: 1,
                        off: 0,
                        toggle: 2,
                    };
                    // 임시로 statusMap 을 업데이트 한다.
                    // 실제 값은 getLED 시 다시 업데이트 된다.
                    let dummyCacheLedValue = 0;
                    if (value === 'toggle') {
                        dummyCacheLedValue =
                            _.get(this.microbitStatusMap, [
                                'sensorData',
                                'led',
                                x,
                                y,
                            ]) === 0
                                ? 1
                                : 0;
                    } else {
                        dummyCacheLedValue = valueType[value];
                    }
                    _.set(
                        this.microbitStatusMap,
                        ['sensorData', 'led', x, y],
                        dummyCacheLedValue
                    );
                    return this.makeBuffer(functionKeys.SET_LED, [
                        x,
                        y,
                        valueType[value],
                    ]);
                }

                case functionKeys.SET_CUSTOM_IMAGE: {
                    const { value } = payload;
                    let dataToSend = [];
                    let temp = 0;
                    for (let i = 0; i < 25; i++) {
                        let x = parseInt(i / 5);
                        let y = i % 5;
                        if (value[x][y] == 1) {
                            temp += value[x][y] * Math.pow(2, 24 - i);
                        }
                    }
                    for (let i = 0; i < 4; i++) {
                        if (temp < 1) {
                            dataToSend.unshift(0);
                        } else {
                            let remainder = temp % 256;
                            temp = parseInt(temp / 256);
                            dataToSend.unshift(remainder);
                        }
                    }

                    return this.makeBuffer(
                        functionKeys.SET_CUSTOM_IMAGE,
                        dataToSend
                    );
                }

                case functionKeys.GET_LED: {
                    const { x, y } = payload;
                    return this.makeBuffer(functionKeys.GET_LED, [x, y]);
                }
                case functionKeys.RESET:
                    this.resetMicrobitStatusMap();
                    return this.makeBuffer(functionKeys.RESET);
                case functionKeys.SET_STRING:
                    return this.makeBuffer(
                        functionKeys.SET_STRING,
                        Buffer.from(payload).toJSON().data
                    );
                case functionKeys.SET_DIGITAL: {
                    const { pinNumber, value } = payload;
                    return this.makeBuffer(functionKeys.SET_DIGITAL, [
                        pinNumber,
                        value,
                    ]);
                }
                // 전달값이 uint8_t 이상인 경우
                case functionKeys.SET_ANALOG:
                case functionKeys.SET_SERVO_PERIOD:
                case functionKeys.SET_ANALOG_PERIOD: {
                    const { pinNumber, value } = payload;
                    const uInt8Value = [];
                    let targetValue = value;
                    while (targetValue) {
                        uInt8Value.push(targetValue & 0xff);
                        targetValue >>= 8;
                    }
                    return this.makeBuffer(type, [pinNumber, ...uInt8Value]);
                }
                case functionKeys.SET_TONE: {
                    const noteValue = payload.noteValue;
                    const beatValue = payload.beatValue;
                    const noteValue1 = noteValue / 256;
                    const noteValue2 = noteValue % 256;
                    const beatValue1 = beatValue / (256 * 256 * 256);
                    const beatValue2 = beatValue / (256 * 256);
                    const beatValue3 = beatValue / 256;
                    const beatValue4 = beatValue % 256;

                    return this.makeBuffer(type, [
                        noteValue1,
                        noteValue2,
                        beatValue1,
                        beatValue2,
                        beatValue3,
                        beatValue4,
                    ]);
                }
                case functionKeys.SET_RELATIVE_TEMPO:
                case functionKeys.SET_TEMPO: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }

                // 필요한 값이 value property 하나인 경우 전부
                case functionKeys.SET_SERVO: {
                    const { pinNumber, value } = payload;
                    return this.makeBuffer(type, [pinNumber, value]);
                }
                case functionKeys.SET_IMAGE: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }

                case functionKeys.GET_ANALOG:
                case functionKeys.GET_DIGITAL: {
                    const value = payload[0];
                    return this.makeBuffer(type, [value]);
                }
                case functionKeys.GET_LIGHT_LEVEL: {
                    return this.makeBuffer(type);
                }
                // 그냥 값 없이 바로 커맨드만 보내는 경우

                case functionKeys.GET_ACCELEROMETER:
                case functionKeys.GET_BUTTON:
                case functionKeys.GET_TEMPERATURE:
                case functionKeys.GET_COMPASS_HEADING:
                case functionKeys.RESET_SCREEN:
                case functionKeys.GET_PITCH:
                case functionKeys.GET_ROLL:
                case functionKeys.GET_GESTURE:
                    return null;
                default:
                    return null;
            }
        }
    }

    /**
     *
     * @param {string|string[]} path
     * @param value
     */
    setStatusMap(path, value) {
        _.set(this.microbitStatusMap, path, value);
    }

    handleLocalData(data) {
        this.pending = false;
        // show only reqs not status updates
        if (data[0] != 171 && data[0] != 170 && data[0] != 0) {
            console.log('received from microbit : ', data);
        }

        const receivedCommandType = data[0];
        switch (receivedCommandType) {
            case functionKeys.RST:
            case functionKeys.SET_BPM:
            case functionKeys.PLAY_NOTE:
            case functionKeys.GET_ACCELEROMETER:
            case functionKeys.GET_SENSOR:
            case functionKeys.GET_BUTTON:
            case functionKeys.SET_LED:
            case functionKeys.SET_STRING:
            case functionKeys.SET_IMAGE: {
                break;
            }
            case functionKeys.GET_LED: {
                this.setStatusMap(
                    ['sensorData', 'led', data[1], data[2]],
                    data[3]
                );
                break;
            }
            case functionKeys.GET_ANALOG: {
                let result = data[2] * 256 + data[3];
                this.setStatusMap(['sensorData', 'analog', data[1]], result);
                break;
            }

            case functionKeys.GET_LIGHT_LEVEL: {
                this.setStatusMap(
                    ['sensorData', 'lightLevel'],
                    Buffer.from([data[1]]).readUInt8(0)
                );
            }

            case functionKeys.GET_DIGITAL: {
                this.setStatusMap(['sensorData', 'digital', data[1]], data[2]);
                break;
            }

            case functionKeys.ACC_REGULAR: {
                const x = Number(
                    Buffer.from([data[1], data[2]]).readInt16LE(0)
                );
                const y = Number(
                    Buffer.from([data[3], data[4]]).readInt16LE(0)
                );
                const z = Number(
                    Buffer.from([data[5], data[6]]).readInt16LE(0)
                );
                const strength = Math.sqrt(x * x + y * y + z * z);
                const pitch = Number(
                    Buffer.from([data[7], data[8]]).readInt16LE(0)
                );
                const roll = Number(
                    Buffer.from([data[9], data[10]]).readInt16LE(0)
                );
                const temperature = Number(
                    Buffer.from([data[11]]).readUInt8(0)
                );
                const button = Number(Buffer.from([data[12]]).readUInt8(0));

                this.setStatusMap(['sensorData', 'accelerometer', 'x'], x);
                this.setStatusMap(['sensorData', 'accelerometer', 'y'], y);
                this.setStatusMap(['sensorData', 'accelerometer', 'z'], z);

                this.setStatusMap(
                    ['sensorData', 'accelerometer', 'strength'],
                    strength
                );
                this.setStatusMap(['sensorData', 'tilt', 'pitch'], pitch);
                this.setStatusMap(['sensorData', 'tilt', 'roll'], roll);
                this.setStatusMap(['sensorData', 'temperature'], temperature);
                this.setStatusMap(['sensorData', 'button'], button);
                break;
            }
            case functionKeys.SENSOR_REGULAR: {
                this.setStatusMap(
                    ['sensorData', 'compassHeading'],
                    Buffer.from([data[1], data[2]]).readUInt16LE(0)
                );
                this.setStatusMap(
                    ['sensorData', 'gesture'],
                    Buffer.from([data[3]]).readUInt8(0)
                );
                break;
            }
        }

        return true;
    }

    disconnect(connect) {
        connect.close();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.isDraing = false;
        this.sp = null;
    }

    reset() {
        this.sp = null;
    }
}

module.exports = new Microbit();
<commit_msg>codeIdQueue 생성<commit_after>const _ = require('lodash');
const BaseModule = require('./baseModule');

/**
 * NOTE
 * microbit getGesture 결과 목록표
 * MICROBIT_ACCELEROMETER_EVT_TILT_UP	1
 * MICROBIT_ACCELEROMETER_EVT_TILT_DOWN	2
 * MICROBIT_ACCELEROMETER_EVT_TILT_LEFT	3
 * MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT	4
 * MICROBIT_ACCELEROMETER_EVT_FACE_UP	5
 * MICROBIT_ACCELEROMETER_EVT_FACE_DOWN	6
 * MICROBIT_ACCELEROMETER_EVT_FREEFALL	7
 * MICROBIT_ACCELEROMETER_EVT_3G	8
 * MICROBIT_ACCELEROMETER_EVT_6G	9
 * MICROBIT_ACCELEROMETER_EVT_8G	10
 * MICROBIT_ACCELEROMETER_EVT_SHAKE	11
 *
 */

const MICROBIT_BUFFER_SIZE = 16;
const functionKeys = {
    TEST_MESSAGE: 0xfa,
    RESET: 0xfe,
    CHECK_READY: 0xff,
    SET_LED: 0x01,
    SET_STRING: 0x02,
    SET_IMAGE: 0x03,
    SET_TONE: 0x04,
    SET_TEMPO: 0x05,
    SET_RELATIVE_TEMPO: 0x06,
    SET_DIGITAL: 0x07,
    SET_ANALOG: 0x08,
    RESET_SCREEN: 0x09,
    SET_ANALOG_PERIOD: 0x10,
    SET_SERVO: 0x11,
    SET_SERVO_PERIOD: 0x12,
    SET_CUSTOM_IMAGE: 0x13,

    GET_LED: 0x31,
    GET_ANALOG: 0x32,
    GET_DIGITAL: 0x33,
    GET_BUTTON: 0x34,
    GET_LIGHT_LEVEL: 0x35,
    GET_TEMPERATURE: 0x36,
    GET_COMPASS_HEADING: 0x37,
    GET_ACCELEROMETER: 0x38,
    GET_PITCH: 0x39,
    GET_ROLL: 0x40,
    GET_GESTURE: 0x41,
    PLAY_NOTE: 0x04,
    CHANGE_BPM: 0x05,
    SET_BPM: 0x06,
    ACC_REGULAR: 0xaa,
    SENSOR_REGULAR: 0xab,
};

const EXCEPTION_COMMAND_CODE = [170, 171, 172];

class Microbit extends BaseModule {
    constructor() {
        super();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.startChecksum = [0xff, 0x01];
        this.endChecksum = [0x0d, 0x0a];

        this.resetMicrobitStatusMap();
        this.commandQueue = [];
        this.codeIdQueue = [];
    }

    resetMicrobitStatusMap() {
        this.commandQueue = [];
        this.codeIdQueue = [];
        this.microbitStatusMap = {
            payload: undefined,
            sensorData: {
                digital: {},
                analog: {},
                led: {
                    0: [0, 0, 0, 0, 0],
                    1: [0, 0, 0, 0, 0],
                    2: [0, 0, 0, 0, 0],
                    3: [0, 0, 0, 0, 0],
                    4: [0, 0, 0, 0, 0],
                },
                button: false,
                lightLevel: 0,
                temperature: 0,
                compassHeading: 0,
                accelerometer: {
                    x: 0,
                    y: 0,
                    z: 0,
                    strength: 0,
                },
            },
            isSensorMap: false,
            codeId: null,
        };
    }

    /**
     * payload 에 앞, 뒤 고정 버퍼를 추가한다.
     * @param {number} key
     * @param {number[]?} payload
     * @returns {Buffer}
     */
    makeBuffer(key, payload) {
        const payloadLength =
            MICROBIT_BUFFER_SIZE -
            this.startChecksum.length -
            this.endChecksum.length -
            1; // key length

        // payload 는 최대 payloadLength 까지. 이보다 적은 경우 0 으로 fill.
        // payload 가 없는 경우는 빈 배열로 대체한다.
        const slicedPayload = _.chain(_.fill(Array(payloadLength), 0))
            .zipWith(payload || [], (original, input) => input || 0)
            .slice(0, payloadLength)
            .value();

        return Buffer.from(
            this.startChecksum
                .concat([key])
                .concat(...slicedPayload)
                .concat(this.endChecksum)
        );
    }

    // socketReconnection() {
    //     this.socket.send(this.handler.encode());
    // }

    requestInitialData() {
        const aa = this.makeBuffer(functionKeys.CHECK_READY);
        console.log('send to : ', aa);
        return aa;
    }

    // validateLocalData(data) {
    //     return data[0] === this.BUFFER_END_ACK_FLAG;
    // }

    setSerialPort(sp) {
        this.sp = sp;
    }

    setSocket(socket) {
        this.socket = socket;
    }

    /**
     * 정상 handshake response 는 [ff 11 22 33 ~ 0d 0a] 이다.
     * @param {Buffer} data
     * @param config
     * @returns {boolean}
     */
    checkInitialData(data, config) {
        // data[1~4] 는 commandId 로, 체크하지 않는다.
        console.log('checkInitialData : ', data);
        // return true;
        return (
            data[0] === functionKeys.CHECK_READY &&
            data[1] === 0x11 &&
            data[2] === 0x22 &&
            data[3] === 0x33
        );
    }

    requestRemoteData(handler) {
        handler.write('payload', this.microbitStatusMap);
    }

    lostController(connector, stateCallback) {
        // 아무일도 안하지만, 해당 함수가 선언되면 lostTimer 가 선언되지 않음.
    }

    handleRemoteData(handler) {
        const type = handler.read('type') || undefined;
        const payload = handler.read('payload') || {};
        const codeId = handler.read('codeId') || null;

        // 리퀘스트 목록이 마지막으로 확인한 버전과 다르기 때문에, 업데이트한다.
        // 업데이트는 중복되지 않는 id 의 커맨드만 뒤에 추가한다.
        this.commandQueue.push({
            type,
            payload,
            codeId,
        });
        if (codeId) {
            this.codeIdQueue.push({ type, payload, codeId });
        }
    }

    requestLocalData() {
        if (this.commandQueue.length !== 0 && !this.pending) {
            this.pending = true;
            const { type, payload, codeId } = this.commandQueue.shift();
            switch (type) {
                case functionKeys.SET_LED: {
                    const { x, y, value } = payload;
                    const valueType = {
                        on: 1,
                        off: 0,
                        toggle: 2,
                    };
                    // 임시로 statusMap 을 업데이트 한다.
                    // 실제 값은 getLED 시 다시 업데이트 된다.
                    let dummyCacheLedValue = 0;
                    if (value === 'toggle') {
                        dummyCacheLedValue =
                            _.get(this.microbitStatusMap, [
                                'sensorData',
                                'led',
                                x,
                                y,
                            ]) === 0
                                ? 1
                                : 0;
                    } else {
                        dummyCacheLedValue = valueType[value];
                    }
                    _.set(
                        this.microbitStatusMap,
                        ['sensorData', 'led', x, y],
                        dummyCacheLedValue
                    );
                    return this.makeBuffer(functionKeys.SET_LED, [
                        x,
                        y,
                        valueType[value],
                    ]);
                }

                case functionKeys.SET_CUSTOM_IMAGE: {
                    const { value } = payload;
                    let dataToSend = [];
                    let temp = 0;
                    for (let i = 0; i < 25; i++) {
                        let x = parseInt(i / 5);
                        let y = i % 5;
                        if (value[x][y] == 1) {
                            temp += value[x][y] * Math.pow(2, 24 - i);
                        }
                    }
                    for (let i = 0; i < 4; i++) {
                        if (temp < 1) {
                            dataToSend.unshift(0);
                        } else {
                            let remainder = temp % 256;
                            temp = parseInt(temp / 256);
                            dataToSend.unshift(remainder);
                        }
                    }

                    return this.makeBuffer(
                        functionKeys.SET_CUSTOM_IMAGE,
                        dataToSend
                    );
                }

                case functionKeys.GET_LED: {
                    const { x, y } = payload;
                    return this.makeBuffer(functionKeys.GET_LED, [x, y]);
                }
                case functionKeys.RESET:
                    this.resetMicrobitStatusMap();
                    return this.makeBuffer(functionKeys.RESET);
                case functionKeys.SET_STRING:
                    return this.makeBuffer(
                        functionKeys.SET_STRING,
                        Buffer.from(payload).toJSON().data
                    );
                case functionKeys.SET_DIGITAL: {
                    const { pinNumber, value } = payload;
                    return this.makeBuffer(functionKeys.SET_DIGITAL, [
                        pinNumber,
                        value,
                    ]);
                }
                // 전달값이 uint8_t 이상인 경우
                case functionKeys.SET_ANALOG:
                case functionKeys.SET_SERVO_PERIOD:
                case functionKeys.SET_ANALOG_PERIOD: {
                    const { pinNumber, value } = payload;
                    const uInt8Value = [];
                    let targetValue = value;
                    while (targetValue) {
                        uInt8Value.push(targetValue & 0xff);
                        targetValue >>= 8;
                    }
                    return this.makeBuffer(type, [pinNumber, ...uInt8Value]);
                }
                case functionKeys.SET_TONE: {
                    const noteValue = payload.noteValue;
                    const beatValue = payload.beatValue;
                    const noteValue1 = noteValue / 256;
                    const noteValue2 = noteValue % 256;
                    const beatValue1 = beatValue / (256 * 256 * 256);
                    const beatValue2 = beatValue / (256 * 256);
                    const beatValue3 = beatValue / 256;
                    const beatValue4 = beatValue % 256;

                    return this.makeBuffer(type, [
                        noteValue1,
                        noteValue2,
                        beatValue1,
                        beatValue2,
                        beatValue3,
                        beatValue4,
                    ]);
                }
                case functionKeys.SET_RELATIVE_TEMPO:
                case functionKeys.SET_TEMPO: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }

                // 필요한 값이 value property 하나인 경우 전부
                case functionKeys.SET_SERVO: {
                    const { pinNumber, value } = payload;
                    return this.makeBuffer(type, [pinNumber, value]);
                }
                case functionKeys.SET_IMAGE: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }

                case functionKeys.GET_ANALOG:
                case functionKeys.GET_DIGITAL: {
                    const value = payload[0];
                    return this.makeBuffer(type, [value]);
                }
                case functionKeys.GET_LIGHT_LEVEL: {
                    return this.makeBuffer(type);
                }
                // 그냥 값 없이 바로 커맨드만 보내는 경우

                case functionKeys.GET_ACCELEROMETER:
                case functionKeys.GET_BUTTON:
                case functionKeys.GET_TEMPERATURE:
                case functionKeys.GET_COMPASS_HEADING:
                case functionKeys.RESET_SCREEN:
                case functionKeys.GET_PITCH:
                case functionKeys.GET_ROLL:
                case functionKeys.GET_GESTURE:
                    return null;
                default:
                    return null;
            }
        }
    }

    /**
     *
     * @param {string|string[]} path
     * @param value
     */
    setStatusMap(path, value) {
        _.set(this.microbitStatusMap, path, value);
    }

    handleLocalData(data) {
        this.pending = false;
        let codeId = null;
        // show only reqs not status updates
        if (data[0] != 171 && data[0] != 170 && data[0] != 0) {
            console.log('received from microbit : ', data);
        }

        const receivedCommandType = data[0];
        switch (receivedCommandType) {
            case functionKeys.SET_DIGITAL:
            case functionKeys.SET_TONE:
            case functionKeys.SET_LED: {
                codeId = this.codeIdQueue.shift().codeId;
                this.setStatusMap(['isSensorMap'], false);
                break;
            }
            case functionKeys.RST:
            case functionKeys.SET_BPM:
            case functionKeys.PLAY_NOTE:
            case functionKeys.GET_ACCELEROMETER:
            case functionKeys.GET_SENSOR:
            case functionKeys.GET_BUTTON:

            case functionKeys.SET_STRING:
            case functionKeys.SET_IMAGE: {
                this.setStatusMap(['isSensorMap'], false);
                break;
            }
            case functionKeys.GET_LED: {
                codeId = this.codeIdQueue.shift().codeId;
                this.setStatusMap(
                    ['sensorData', 'led', data[1], data[2]],
                    data[3]
                );
                this.setStatusMap(['isSensorMap'], false);
                break;
            }
            case functionKeys.GET_ANALOG: {
                codeId = this.codeIdQueue.shift().codeId;
                let result = data[2] * 256 + data[3];
                this.setStatusMap(['sensorData', 'analog', data[1]], result);
                this.setStatusMap(['isSensorMap'], false);
                break;
            }

            case functionKeys.GET_LIGHT_LEVEL: {
                codeId = this.codeIdQueue.shift().codeId;
                this.setStatusMap(
                    ['sensorData', 'lightLevel'],
                    Buffer.from([data[1]]).readUInt8(0)
                );
                this.setStatusMap(['isSensorMap'], false);
                break;
            }

            case functionKeys.GET_DIGITAL: {
                codeId = this.codeIdQueue.shift().codeId;
                this.setStatusMap(['sensorData', 'digital', data[1]], data[2]);
                this.setStatusMap(['isSensorMap'], false);
                break;
            }

            case functionKeys.ACC_REGULAR: {
                const x = Number(
                    Buffer.from([data[1], data[2]]).readInt16LE(0)
                );
                const y = Number(
                    Buffer.from([data[3], data[4]]).readInt16LE(0)
                );
                const z = Number(
                    Buffer.from([data[5], data[6]]).readInt16LE(0)
                );
                const strength = Math.sqrt(x * x + y * y + z * z);
                const pitch = Number(
                    Buffer.from([data[7], data[8]]).readInt16LE(0)
                );
                const roll = Number(
                    Buffer.from([data[9], data[10]]).readInt16LE(0)
                );
                const temperature = Number(
                    Buffer.from([data[11]]).readUInt8(0)
                );
                const button = Number(Buffer.from([data[12]]).readUInt8(0));

                this.setStatusMap(['sensorData', 'accelerometer', 'x'], x);
                this.setStatusMap(['sensorData', 'accelerometer', 'y'], y);
                this.setStatusMap(['sensorData', 'accelerometer', 'z'], z);

                this.setStatusMap(
                    ['sensorData', 'accelerometer', 'strength'],
                    strength
                );
                this.setStatusMap(['sensorData', 'tilt', 'pitch'], pitch);
                this.setStatusMap(['sensorData', 'tilt', 'roll'], roll);
                this.setStatusMap(['sensorData', 'temperature'], temperature);
                this.setStatusMap(['sensorData', 'button'], button);
                this.setStatusMap(['isSensorMap'], true);
                break;
            }
            case functionKeys.SENSOR_REGULAR: {
                this.setStatusMap(
                    ['sensorData', 'compassHeading'],
                    Buffer.from([data[1], data[2]]).readUInt16LE(0)
                );
                this.setStatusMap(
                    ['sensorData', 'gesture'],
                    Buffer.from([data[3]]).readUInt8(0)
                );
                this.setStatusMap(['isSensorMap'], true);
                break;
            }
        }
        this.setStatusMap(['codeId'], codeId);
        return true;
    }

    disconnect(connect) {
        connect.close();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.isDraing = false;
        this.sp = null;
    }

    reset() {
        this.sp = null;
    }
}

module.exports = new Microbit();
<|endoftext|>


<commit_before>/**
Simple username/password field detector and auto-filler.

When page is loaded, we try to find any input fields with specific name
attributes. If we find something useful, we dispatch an IPC event
'password-autofill' to signal that we want to check if there is auto-fill data
available.

When we receive back an IPC event 'password-autofill-match' with auto-fill
data, we do one of two things:

- If there's a single credentials match, we fill the input fields with that
  data.

- If there's more than one match, we add a focus event listener on the
  username/email fields that will display a small overlay div with available
  options. When user selects one of the options, we fill the input fields with
  credentials data from the selection.

This code doesn't work with JS-based forms. We don't listen to all DOM changes,
we expect the login form to be present in the HTML code at page load. We can
add a MutationObserver to the document, or DOMNodeInserted listener, but I
wanted to keep it lightweight and not impact browser performace too much.
*/

// "carbon:password"
const keyIcon = '<svg width="22px" height="22px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="vertical-align: -0.125em;-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M21 2a9 9 0 0 0-9 9a8.87 8.87 0 0 0 .39 2.61L2 24v6h6l10.39-10.39A9 9 0 0 0 30 11.74a8.77 8.77 0 0 0-1.65-6A9 9 0 0 0 21 2zm0 16a7 7 0 0 1-2-.3l-1.15-.35l-.85.85l-3.18 3.18L12.41 20L11 21.41l1.38 1.38l-1.59 1.59L9.41 23L8 24.41l1.38 1.38L7.17 28H4v-3.17L13.8 15l.85-.85l-.29-.95a7.14 7.14 0 0 1 3.4-8.44a7 7 0 0 1 10.24 6a6.69 6.69 0 0 1-1.09 4A7 7 0 0 1 21 18z" fill="currentColor"/><circle cx="22" cy="10" r="2" fill="currentColor"/></svg>'

// Ref to added unlock button.
var currentUnlockButton = null
var currentAutocompleteList = null

// Creates an unlock button element.
//
// - input: Input element to 'attach' unlock button to.
function createUnlockButton (input) {
  var inputRect = input.getBoundingClientRect()

  // Container.
  var unlockDiv = document.createElement('div')

  // Style.
  unlockDiv.style.width = '20px'
  unlockDiv.style.height = '20px'
  unlockDiv.style.zIndex = 999999999999999

  // Position.
  unlockDiv.style.position = 'absolute'
  unlockDiv.style.left = (window.scrollX + (inputRect.left + inputRect.width - 20 - 10)) + 'px'
  unlockDiv.style.top = (window.scrollY + (inputRect.top + (inputRect.height - 20) / 2.0)) + 'px'

  // Button.
  var button = document.createElement('div')

  // Button style.
  button.style.width = '20px'
  button.style.height = '20px'
  button.style.opacity = 0.7
  button.style.color = window.getComputedStyle(input).color
  button.style.transition = '0.1s color'
  button.innerHTML = keyIcon

  // Button hover.
  button.addEventListener('mouseenter', (event) => {
    button.style.opacity = 1.0
  })
  button.addEventListener('mouseleave', (event) => {
    button.style.opacity = 0.7
  })

  // Click event.
  button.addEventListener('mousedown', (event) => {
    event.preventDefault()
    checkInputs()
  })

  unlockDiv.appendChild(button)

  return unlockDiv
}

// Tries to find if an element has a specific attribute value that contains at
// least one of the values from 'matches' array.
function checkAttribute (element, attribute, matches) {
  const value = element.getAttribute(attribute)
  if (value == null) { return false }
  return matches.some(match => value.toLowerCase().includes(match))
}

// Gets all input fields on a page that contain at least one of the provided
// strings in their name attribute.
function getInputs (names, types) {
  const allFields = document.getElementsByTagName('input')

  const matchedFields = []
  for (const field of allFields) {
    // checkAttribute won't work here because type can be a property but not an attribute
    if (!types.includes(field.type)) {
      continue
    }

    // We expect the field to have either 'name', 'formcontrolname' or 'id' attribute
    // that we can use to identify it as a login form input field.
    if (checkAttribute(field, 'name', names) ||
        checkAttribute(field, 'formcontrolname', names) ||
        checkAttribute(field, 'id', names)) {
      matchedFields.push(field)
    }
  }

  return matchedFields
}

// Shortcut to get username fields from a page.
function getUsernameFields () {
  return getInputs(['user', 'name', 'mail', 'login', 'auth', 'identifier'], ['text', 'email'])
}

// Shortcut to get password fields from a page.
function getPasswordFields () {
  return getInputs(['pass'], ['password'])
}

// Removes credentials list overlay.
function removeAutocompleteList () {
  if (currentAutocompleteList && currentAutocompleteList.parentNode) {
    currentAutocompleteList.parentNode.removeChild(currentAutocompleteList)
  }
}

// Populates username/password fields with provided credentials.
function fillCredentials (credentials) {
  const { username, password } = credentials
  const inputEvents = ['keydown', 'keypress', 'keyup', 'input', 'change']

  for (const field of getUsernameFields()) {
    field.value = username
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }

  for (const field of getPasswordFields()) {
    field.value = password
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }
}

// Setup a focus/click listener on the username input fields.
//
// When those events happen, we add a small overlay with a list of matching
// credentials. Clicking on an item in a list populates the input fields with
// selected username/password pair.
//
// - element: input field to add a listener to
// - credentials: an array of { username, password } objects
function addFocusListener (element, credentials) {
  const inputRect = element.getBoundingClientRect()
  // Creates an options list container.
  function buildContainer () {
    const suggestionsDiv = document.createElement('div')
    suggestionsDiv.style = 'position: absolute; border: 1px solid #d4d4d4; z-index: 999999; border-bottom: none; background: #FFFFFF; transform: scale(0); opacity: 0; transform-origin: top left; transition: 0.15s; color: #000000;'
    suggestionsDiv.style.top = (inputRect.y + inputRect.height) + 'px'
    suggestionsDiv.style.left = (inputRect.x) + 'px'
    suggestionsDiv.id = 'password-autocomplete-list'
    requestAnimationFrame(function () {
      suggestionsDiv.style.opacity = '1'
      suggestionsDiv.style.transform = 'scale(1)'
    })
    return suggestionsDiv
  }

  // Adds an option row to the list container.
  function addOption (parent, username) {
    const suggestionItem = document.createElement('div')
    suggestionItem.innerHTML = username
    suggestionItem.style = 'padding: 10px; cursor: pointer; background-color: #fff; border-bottom: 1px solid #d4d4d4;'

    // Hover.
    suggestionItem.addEventListener('mouseenter', (event) => {
      suggestionItem.style.backgroundColor = '#e4e4e4'
    })
    suggestionItem.addEventListener('mouseleave', (event) => {
      suggestionItem.style.backgroundColor = '#fff'
    })

    // When user clicks on the suggestion, we populate the form inputs with selected credentials.
    suggestionItem.addEventListener('click', function (e) {
      const selectedCredentials = credentials.filter(el => { return el.username === username })[0]
      fillCredentials(selectedCredentials)
      removeAutocompleteList()
      element.focus()
    })

    parent.appendChild(suggestionItem)
  }

  // Creates autocomplete list and adds it below the activated field.
  function showAutocompleteList (e) {
    removeAutocompleteList()
    const container = buildContainer()
    for (const cred of credentials) {
      addOption(container, cred.username)
    }
    document.body.appendChild(container)
    currentAutocompleteList = container
  }

  element.addEventListener('focus', showAutocompleteList)
  element.addEventListener('click', showAutocompleteList)

  // Hide options overlay when user clicks out of the input field.
  document.addEventListener('click', function (e) {
    if (e.target !== element) {
      removeAutocompleteList()
    }
  })

  // Show the autocomplete list right away if field is already focused.
  // Userful for login pages which auto-focus the input field on page load.
  if (element === document.activeElement) {
    showAutocompleteList()
  }
}

function checkInputs () {
  if (getUsernameFields().length > 0 || getPasswordFields().length > 0) {
    ipc.send('password-autofill', document.location.hostname)
  }
}

function addUnlockButton (target) {
  if (getUsernameFields().includes(target) || getPasswordFields().includes(target)) {
    const unlockButton = createUnlockButton(target)
    document.body.appendChild(unlockButton)

    currentUnlockButton = unlockButton
  }
}

function checkInitialFocus () {
  if (getUsernameFields().includes(document.activeElement) || getPasswordFields().includes(document.activeElement)) {
    addUnlockButton(document.activeElement)
  }
}

function handleFocus (event) {
  addUnlockButton(event.target)
}

function handleBlur (event) {
  if (currentUnlockButton !== null && currentUnlockButton.parentElement != null) {
    currentUnlockButton.parentElement.removeChild(currentUnlockButton)
    currentUnlockButton = null
  }
}

// Handle credentials fetched from the backend. Credentials are expected to be
// an array of { username, password, manager } objects.
ipc.on('password-autofill-match', (event, data) => {
  if (data.hostname !== window.location.hostname) {
    throw new Error('password origin must match current page origin')
  }

  if (data.credentials.length === 0) {
    if (currentUnlockButton && currentUnlockButton.children.length > 0) {
      currentUnlockButton.children[0].style.color = 'rgb(180, 0, 0)'
    }
  } else if (data.credentials.length === 1) {
    fillCredentials(data.credentials[0])
  } else {
    const firstField = getUsernameFields().filter(field => field.type !== 'hidden')[0]
    addFocusListener(firstField, data.credentials)
    firstField.focus()
  }
})

// Trigger autofill check from keyboard shortcut.
ipc.on('password-autofill-shortcut', (event) => {
  checkInputs(true)
})

// Autofill enabled event handler. Initializes focus listeners for input fields.
ipc.on('password-autofill-enabled', (event) => {
  checkInitialFocus()

  // Add default focus event listeners.
  window.addEventListener('blur', handleBlur, true)
  window.addEventListener('focus', handleFocus, true)
})

// Check if password autofill is configured.
window.addEventListener('load', function (event) {
  ipc.send('password-autofill-check')
})

// send passwords back to the main process so they can be saved to storage

window.addEventListener('submit', function () {
  var usernameValues = getUsernameFields().map(f => f.value)
  var passwordValues = getPasswordFields().map(f => f.value)

  if (usernameValues.some(v => v.length > 0) || passwordValues.some(v => v.length > 0)) {
    ipc.send('password-form-filled', [window.location.hostname, usernameValues, passwordValues])
  }
})
<commit_msg>handle JS form submissions<commit_after>/**
Simple username/password field detector and auto-filler.

When page is loaded, we try to find any input fields with specific name
attributes. If we find something useful, we dispatch an IPC event
'password-autofill' to signal that we want to check if there is auto-fill data
available.

When we receive back an IPC event 'password-autofill-match' with auto-fill
data, we do one of two things:

- If there's a single credentials match, we fill the input fields with that
  data.

- If there's more than one match, we add a focus event listener on the
  username/email fields that will display a small overlay div with available
  options. When user selects one of the options, we fill the input fields with
  credentials data from the selection.

This code doesn't work with JS-based forms. We don't listen to all DOM changes,
we expect the login form to be present in the HTML code at page load. We can
add a MutationObserver to the document, or DOMNodeInserted listener, but I
wanted to keep it lightweight and not impact browser performace too much.
*/

// "carbon:password"
const keyIcon = '<svg width="22px" height="22px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="vertical-align: -0.125em;-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M21 2a9 9 0 0 0-9 9a8.87 8.87 0 0 0 .39 2.61L2 24v6h6l10.39-10.39A9 9 0 0 0 30 11.74a8.77 8.77 0 0 0-1.65-6A9 9 0 0 0 21 2zm0 16a7 7 0 0 1-2-.3l-1.15-.35l-.85.85l-3.18 3.18L12.41 20L11 21.41l1.38 1.38l-1.59 1.59L9.41 23L8 24.41l1.38 1.38L7.17 28H4v-3.17L13.8 15l.85-.85l-.29-.95a7.14 7.14 0 0 1 3.4-8.44a7 7 0 0 1 10.24 6a6.69 6.69 0 0 1-1.09 4A7 7 0 0 1 21 18z" fill="currentColor"/><circle cx="22" cy="10" r="2" fill="currentColor"/></svg>'

// Ref to added unlock button.
var currentUnlockButton = null
var currentAutocompleteList = null

// Creates an unlock button element.
//
// - input: Input element to 'attach' unlock button to.
function createUnlockButton (input) {
  var inputRect = input.getBoundingClientRect()

  // Container.
  var unlockDiv = document.createElement('div')

  // Style.
  unlockDiv.style.width = '20px'
  unlockDiv.style.height = '20px'
  unlockDiv.style.zIndex = 999999999999999

  // Position.
  unlockDiv.style.position = 'absolute'
  unlockDiv.style.left = (window.scrollX + (inputRect.left + inputRect.width - 20 - 10)) + 'px'
  unlockDiv.style.top = (window.scrollY + (inputRect.top + (inputRect.height - 20) / 2.0)) + 'px'

  // Button.
  var button = document.createElement('div')

  // Button style.
  button.style.width = '20px'
  button.style.height = '20px'
  button.style.opacity = 0.7
  button.style.color = window.getComputedStyle(input).color
  button.style.transition = '0.1s color'
  button.innerHTML = keyIcon

  // Button hover.
  button.addEventListener('mouseenter', (event) => {
    button.style.opacity = 1.0
  })
  button.addEventListener('mouseleave', (event) => {
    button.style.opacity = 0.7
  })

  // Click event.
  button.addEventListener('mousedown', (event) => {
    event.preventDefault()
    checkInputs()
  })

  unlockDiv.appendChild(button)

  return unlockDiv
}

// Tries to find if an element has a specific attribute value that contains at
// least one of the values from 'matches' array.
function checkAttribute (element, attribute, matches) {
  const value = element.getAttribute(attribute)
  if (value == null) { return false }
  return matches.some(match => value.toLowerCase().includes(match))
}

// Gets all input fields on a page that contain at least one of the provided
// strings in their name attribute.
function getInputs (names, types) {
  const allFields = document.getElementsByTagName('input')

  const matchedFields = []
  for (const field of allFields) {
    // checkAttribute won't work here because type can be a property but not an attribute
    if (!types.includes(field.type)) {
      continue
    }

    // We expect the field to have either 'name', 'formcontrolname' or 'id' attribute
    // that we can use to identify it as a login form input field.
    if (checkAttribute(field, 'name', names) ||
        checkAttribute(field, 'formcontrolname', names) ||
        checkAttribute(field, 'id', names)) {
      matchedFields.push(field)
    }
  }

  return matchedFields
}

// Shortcut to get username fields from a page.
function getUsernameFields () {
  return getInputs(['user', 'name', 'mail', 'login', 'auth', 'identifier'], ['text', 'email'])
}

// Shortcut to get password fields from a page.
function getPasswordFields () {
  return getInputs(['pass'], ['password'])
}

// Removes credentials list overlay.
function removeAutocompleteList () {
  if (currentAutocompleteList && currentAutocompleteList.parentNode) {
    currentAutocompleteList.parentNode.removeChild(currentAutocompleteList)
  }
}

// Populates username/password fields with provided credentials.
function fillCredentials (credentials) {
  const { username, password } = credentials
  const inputEvents = ['keydown', 'keypress', 'keyup', 'input', 'change']

  for (const field of getUsernameFields()) {
    field.value = username
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }

  for (const field of getPasswordFields()) {
    field.value = password
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }
}

// Setup a focus/click listener on the username input fields.
//
// When those events happen, we add a small overlay with a list of matching
// credentials. Clicking on an item in a list populates the input fields with
// selected username/password pair.
//
// - element: input field to add a listener to
// - credentials: an array of { username, password } objects
function addFocusListener (element, credentials) {
  const inputRect = element.getBoundingClientRect()
  // Creates an options list container.
  function buildContainer () {
    const suggestionsDiv = document.createElement('div')
    suggestionsDiv.style = 'position: absolute; border: 1px solid #d4d4d4; z-index: 999999; border-bottom: none; background: #FFFFFF; transform: scale(0); opacity: 0; transform-origin: top left; transition: 0.15s; color: #000000;'
    suggestionsDiv.style.top = (inputRect.y + inputRect.height) + 'px'
    suggestionsDiv.style.left = (inputRect.x) + 'px'
    suggestionsDiv.id = 'password-autocomplete-list'
    requestAnimationFrame(function () {
      suggestionsDiv.style.opacity = '1'
      suggestionsDiv.style.transform = 'scale(1)'
    })
    return suggestionsDiv
  }

  // Adds an option row to the list container.
  function addOption (parent, username) {
    const suggestionItem = document.createElement('div')
    suggestionItem.innerHTML = username
    suggestionItem.style = 'padding: 10px; cursor: pointer; background-color: #fff; border-bottom: 1px solid #d4d4d4;'

    // Hover.
    suggestionItem.addEventListener('mouseenter', (event) => {
      suggestionItem.style.backgroundColor = '#e4e4e4'
    })
    suggestionItem.addEventListener('mouseleave', (event) => {
      suggestionItem.style.backgroundColor = '#fff'
    })

    // When user clicks on the suggestion, we populate the form inputs with selected credentials.
    suggestionItem.addEventListener('click', function (e) {
      const selectedCredentials = credentials.filter(el => { return el.username === username })[0]
      fillCredentials(selectedCredentials)
      removeAutocompleteList()
      element.focus()
    })

    parent.appendChild(suggestionItem)
  }

  // Creates autocomplete list and adds it below the activated field.
  function showAutocompleteList (e) {
    removeAutocompleteList()
    const container = buildContainer()
    for (const cred of credentials) {
      addOption(container, cred.username)
    }
    document.body.appendChild(container)
    currentAutocompleteList = container
  }

  element.addEventListener('focus', showAutocompleteList)
  element.addEventListener('click', showAutocompleteList)

  // Hide options overlay when user clicks out of the input field.
  document.addEventListener('click', function (e) {
    if (e.target !== element) {
      removeAutocompleteList()
    }
  })

  // Show the autocomplete list right away if field is already focused.
  // Userful for login pages which auto-focus the input field on page load.
  if (element === document.activeElement) {
    showAutocompleteList()
  }
}

function checkInputs () {
  if (getUsernameFields().length > 0 || getPasswordFields().length > 0) {
    ipc.send('password-autofill', document.location.hostname)
  }
}

function addUnlockButton (target) {
  if (getUsernameFields().includes(target) || getPasswordFields().includes(target)) {
    const unlockButton = createUnlockButton(target)
    document.body.appendChild(unlockButton)

    currentUnlockButton = unlockButton
  }
}

function checkInitialFocus () {
  if (getUsernameFields().includes(document.activeElement) || getPasswordFields().includes(document.activeElement)) {
    addUnlockButton(document.activeElement)
  }
}

function handleFocus (event) {
  addUnlockButton(event.target)
}

function handleBlur (event) {
  if (currentUnlockButton !== null && currentUnlockButton.parentElement != null) {
    currentUnlockButton.parentElement.removeChild(currentUnlockButton)
    currentUnlockButton = null
  }
}

// Handle credentials fetched from the backend. Credentials are expected to be
// an array of { username, password, manager } objects.
ipc.on('password-autofill-match', (event, data) => {
  if (data.hostname !== window.location.hostname) {
    throw new Error('password origin must match current page origin')
  }

  if (data.credentials.length === 0) {
    if (currentUnlockButton && currentUnlockButton.children.length > 0) {
      currentUnlockButton.children[0].style.color = 'rgb(180, 0, 0)'
    }
  } else if (data.credentials.length === 1) {
    fillCredentials(data.credentials[0])
  } else {
    const firstField = getUsernameFields().filter(field => field.type !== 'hidden')[0]
    addFocusListener(firstField, data.credentials)
    firstField.focus()
  }
})

// Trigger autofill check from keyboard shortcut.
ipc.on('password-autofill-shortcut', (event) => {
  checkInputs(true)
})

// Autofill enabled event handler. Initializes focus listeners for input fields.
ipc.on('password-autofill-enabled', (event) => {
  checkInitialFocus()

  // Add default focus event listeners.
  window.addEventListener('blur', handleBlur, true)
  window.addEventListener('focus', handleFocus, true)
})

// Check if password autofill is configured.
window.addEventListener('load', function (event) {
  ipc.send('password-autofill-check')
})

// send passwords back to the main process so they can be saved to storage
function handleFormSubmit () {
  var usernameValues = getUsernameFields().map(f => f.value)
  var passwordValues = getPasswordFields().map(f => f.value)

  if (usernameValues.some(v => v.length > 0) || passwordValues.some(v => v.length > 0)) {
    ipc.send('password-form-filled', [window.location.hostname, usernameValues, passwordValues])
  }
}

window.addEventListener('submit', handleFormSubmit)

electron.webFrame.executeJavaScript(`
var origSubmit = HTMLFormElement.prototype.submit;
HTMLFormElement.prototype.submit = function () {
  window.postMessage({message: 'formSubmit'})
  origSubmit.apply(this, arguments)
}
`)

window.addEventListener('message', function (e) {
  if (e.data && e.data.message && e.data.message === 'formSubmit') {
    handleFormSubmit()
  }
})
<|endoftext|>