UNPKG

monaca-lib

Version:

Monaca cloud and localkit API bindings for JavaScript

245 lines (204 loc) 6.63 kB
(function() { 'use strict'; var SSE = require('sse'), shell = require('shelljs'), path = require('path'), fs = require('fs'), crc32 = require('buffer-crc32'), qs = require('querystring'), rc4 = require(path.join(__dirname, 'rc4')); var ProjectEvents = function(localkit) { this.localkit = localkit; this.sse = new SSE(localkit.server, { path: '/events' }); // Monkey patch to validate headers before starting SSE. var _handleRequest = this.sse.handleRequest.bind(this.sse); this.sse.handleRequest = function(req, res, query) { var clientIdHash = req.headers['x-monaca-client-id-hash'], pairingKey = this.localkit.pairingKeys[clientIdHash]; if (!clientIdHash || !pairingKey || !this.localkit.api.validatePairing(req)) { res.writeHead(401); res.end(JSON.stringify({message: 'Pairing key is invalid.'}) + '\n'); } else { _handleRequest(req, res, query); } }.bind(this); this.connectedClients = {}; this._clients = []; this.sse.on('connection', function(client) { var clientIdHash = client.req.headers['x-monaca-client-id-hash'], pairingKey = this.localkit.pairingKeys[clientIdHash]; var sendKeepaliveMessage = function() { var keepaliveMessage = JSON.stringify({ action: 'keepalive' }); client.send(rc4.encrypt(keepaliveMessage, pairingKey)); }; var interval = setInterval(function() { if (client === null) { clearInterval(interval); } else { sendKeepaliveMessage(); } }, 3000); sendKeepaliveMessage(); var clientObject = { client: client, pairingKey: pairingKey, clientId: clientIdHash }; this._clients.push(clientObject); client.on('close', function() { this._clients.splice(this._clients.indexOf(clientObject), 1); this.localkit.emit('debuggerDisconnected', this.connectedClients[clientIdHash]); delete this.connectedClients[clientIdHash]; }.bind(this)); if (!clientIdHash || !pairingKey || !this.localkit.api.validatePairing(client.req)) { client.send(JSON.stringify({ action: 'error', message: 'Device not paired with Localkit.' })); client.req.allowHalfOpen = false; return client.res.end(); } else { var data = ''; client.req.on('data', function(chunk) { data += chunk; }); client.req.on('end', function() { var decrypted = rc4.decrypt(data, pairingKey), clientData = qs.parse(decrypted); this.connectedClients[clientIdHash] = clientData; this.connectedClients[clientIdHash].clientId = clientIdHash; this.localkit.emit('debuggerConnected', clientData); }.bind(this)); } }.bind(this)); }; /** * Can be used to send to a specific client or, if client isn't defined, * it will send to all connected clients. */ ProjectEvents.prototype.sendMessage = function(message, client) { var i, l, pairingKey, _client; if (client) { for (i = 0, l = this._clients.length; i < l; i ++) { _client = this._clients[i]; if (client === _client) { pairingKey = client.pairingKey; client.client.send(rc4.encrypt(JSON.stringify(message), pairingKey)); break; } } } else { // Send to all connected clients. for (i = 0; i < this._clients.length; i++) { _client = this._clients[i].client; pairingKey = this._clients[i].pairingKey; _client.send(rc4.encrypt(JSON.stringify(message), pairingKey)); } } }; ProjectEvents.prototype.sendStartEvent = function(projectId, clientId) { if (clientId) { var client = this._clients.filter(function(client) { return client.clientId === clientId; })[0]; if (client) { this.sendMessage({ action: 'start', projectId: projectId }, client); } else { throw new Error('No such client: ' + clientId); } } else { this.sendMessage({ action: 'start', projectId: projectId }); } }; ProjectEvents.prototype.sendExitEvent = function(projectId) { this.sendMessage({ action: 'exit' }); }; ProjectEvents.prototype.sendFileEvent = function(projectId, changeType, filePath) { if (changeType === 'resync') { return this.sendMessage({ projectId: projectId, action: 'resync' }); } var isDir; try { isDir = fs.lstatSync(filePath).isDirectory(); } catch (error) { isDir = false; } var projectPath = this.localkit.projects.getProjectById(projectId).path, pathInProject = '/' + path.relative(projectPath, filePath).split(path.sep).join('/'); var sendSaveEvent = function(filePath, pathInProject) { fs.readFile(filePath, function(error, data) { if (error) { return; } else { var hash = crc32(data).toString('hex'); var base64Body = Buffer.from(data).toString('base64'); return this.sendMessage({ projectId: projectId, action: 'fileSave', data: base64Body, contentHash: hash, path: pathInProject }); } }.bind(this)); }.bind(this); if (changeType === 'create') { if (isDir) { var files = shell.ls('-R', filePath).map(function(file) { return path.join(filePath, file); }); files.unshift(filePath); for (var i = 0, l = files.length; i < l; i ++) { var file = files[i]; pathInProject = '/' + path.relative(projectPath, file).split(path.sep).join('/'); if (fs.lstatSync(file).isDirectory()) { return this.sendMessage({ projectId: projectId, action: 'makeDir', path: pathInProject }); } else { return sendSaveEvent(file, pathInProject); } } } else { return sendSaveEvent(filePath, pathInProject); } } else if (changeType === 'update') { return sendSaveEvent(filePath, pathInProject); } else if (changeType === 'delete') { return this.sendMessage({ projectId: projectId, action: 'fileDelete', path: pathInProject }); } }; module.exports = ProjectEvents; })();