diff --git a/bin/remixd.ts b/bin/remixd.ts index 0308228884..c65e840a15 100644 --- a/bin/remixd.ts +++ b/bin/remixd.ts @@ -23,11 +23,10 @@ console.log('\x1b[33m%s\x1b[0m', '[WARN] You may now only use IDE at ' + program if (program.sharedFolder) { console.log('\x1b[33m%s\x1b[0m', '[WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.') console.log('\x1b[33m%s\x1b[0m', '[WARN] Symbolic links are not forwarded to Remix IDE\n') - const remixdClient = new RemixdClient() - const websocketHandler = new WebSocket(65520, { remixIdeUrl: program.remixIde }, remixdClient) + const websocketHandler = new WebSocket(65520, { remixIdeUrl: program.remixIde }, new RemixdClient()) websocketHandler.start() - killCallBack.push(websocketHandler.close) + killCallBack.push(websocketHandler.close.bind(websocketHandler)) } // kill diff --git a/lib/bin/remixd.d.ts b/lib/bin/remixd.d.ts deleted file mode 100644 index b7988016da..0000000000 --- a/lib/bin/remixd.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -export {}; diff --git a/lib/bin/remixd.js b/lib/bin/remixd.js deleted file mode 100755 index 8107adc8aa..0000000000 --- a/lib/bin/remixd.js +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env node -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var websocket_1 = require("../src/websocket"); -var remixdClient_1 = require("../src/services/remixdClient"); -var program = require('commander'); -program - .usage('-s ') - .description('Provide a two-way connection between the local computer and Remix IDE') - .option('--remix-ide ', 'URL of remix instance allowed to connect to this web sockect connection') - .option('-s, --shared-folder ', 'Folder to share with Remix IDE') - .option('--read-only', 'Treat shared folder as read-only (experimental)') - .on('--help', function () { - console.log('\nExample:\n\n remixd -s ./ --remix-ide http://localhost:8080'); -}).parse(process.argv); -var killCallBack = []; -if (!program.remixIde) { - console.log('\x1b[31m%s\x1b[0m', '[ERR] URL Remix IDE instance has to be provided.'); -} -console.log('\x1b[33m%s\x1b[0m', '[WARN] You may now only use IDE at ' + program.remixIde + ' to connect to that instance'); -if (program.sharedFolder) { - console.log('\x1b[33m%s\x1b[0m', '[WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.'); - console.log('\x1b[33m%s\x1b[0m', '[WARN] Symbolic links are not forwarded to Remix IDE\n'); - var remixdClient = new remixdClient_1.default(); - var websocketHandler = new websocket_1.default(65520, { remixIdeUrl: program.remixIde }, remixdClient); - websocketHandler.start(); - killCallBack.push(websocketHandler.close); -} -// kill -function kill() { - for (var k in killCallBack) { - try { - killCallBack[k](); - } - catch (e) { - console.log(e); - } - } -} -process.on('SIGINT', kill); // catch ctrl-c -process.on('SIGTERM', kill); // catch kill -process.on('exit', kill); diff --git a/lib/src/index.d.ts b/lib/src/index.d.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/src/index.js b/lib/src/index.js deleted file mode 100644 index 84996a9542..0000000000 --- a/lib/src/index.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; -module.exports = { - Router: require('./router'), - utils: require('./utils'), - services: { - sharedFolder: require('./services/sharedFolder') - } -}; diff --git a/lib/src/services/remixdClient.d.ts b/lib/src/services/remixdClient.d.ts deleted file mode 100644 index 016ddd9fcc..0000000000 --- a/lib/src/services/remixdClient.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -import WebSocket from '../websocket'; -import { PluginClient } from '@remixproject/plugin'; -export default class RemixdClient extends PluginClient { - trackDownStreamUpdate: { - [key: string]: string; - }; - websocket: WebSocket | null; - currentSharedFolder: string; - readOnly: boolean; - setWebSocket(websocket: WebSocket): void; - sharedFolder(currentSharedFolder: string, readOnly: boolean): void; - list(args: { - [key: string]: string; - }, cb: Function): void; - resolveDirectory(args: { - [key: string]: string; - }, cb: Function): void; - folderIsReadOnly(args: { - [key: string]: string; - }, cb: Function): any; - get(args: { - [key: string]: string; - }, cb: Function): any; - exists(args: { - [key: string]: string; - }, cb: Function): void; - set(args: { - [key: string]: string; - }, cb: Function): any; - rename(args: { - [key: string]: string; - }, cb: Function): any; - remove(args: { - [key: string]: string; - }, cb: Function): any; - isDirectory(args: { - [key: string]: string; - }, cb: Function): void; - isFile(args: { - [key: string]: string; - }, cb: Function): void; -} diff --git a/lib/src/services/remixdClient.js b/lib/src/services/remixdClient.js deleted file mode 100644 index 194c819e43..0000000000 --- a/lib/src/services/remixdClient.js +++ /dev/null @@ -1,157 +0,0 @@ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var plugin_1 = require("@remixproject/plugin"); -var utils = require('../utils'); -var isbinaryfile = require('isbinaryfile'); -var fs = require('fs-extra'); -var RemixdClient = /** @class */ (function (_super) { - __extends(RemixdClient, _super); - function RemixdClient() { - return _super !== null && _super.apply(this, arguments) || this; - } - RemixdClient.prototype.setWebSocket = function (websocket) { - this.websocket = websocket; - }; - RemixdClient.prototype.sharedFolder = function (currentSharedFolder, readOnly) { - this.currentSharedFolder = currentSharedFolder; - this.readOnly = readOnly; - }; - RemixdClient.prototype.list = function (args, cb) { - try { - cb(null, utils.walkSync(this.currentSharedFolder, {}, this.currentSharedFolder)); - } - catch (e) { - cb(e.message); - } - }; - RemixdClient.prototype.resolveDirectory = function (args, cb) { - try { - var path_1 = utils.absolutePath(args.path, this.currentSharedFolder); - cb(null, utils.resolveDirectory(path_1, this.currentSharedFolder)); - } - catch (e) { - cb(e.message); - } - }; - RemixdClient.prototype.folderIsReadOnly = function (args, cb) { - return cb(null, this.readOnly); - }; - RemixdClient.prototype.get = function (args, cb) { - var path = utils.absolutePath(args.path, this.currentSharedFolder); - if (!fs.existsSync(path)) { - return cb('File not found ' + path); - } - if (!isRealPath(path, cb)) - return; - isbinaryfile(path, function (error, isBinary) { - if (error) - console.log(error); - if (isBinary) { - cb(null, { content: '', readonly: true }); - } - else { - fs.readFile(path, 'utf8', function (error, data) { - if (error) - console.log(error); - cb(error, { content: data, readonly: false }); - }); - } - }); - }; - RemixdClient.prototype.exists = function (args, cb) { - var path = utils.absolutePath(args.path, this.currentSharedFolder); - cb(null, fs.existsSync(path)); - }; - RemixdClient.prototype.set = function (args, cb) { - if (this.readOnly) - return cb('Cannot write file: read-only mode selected'); - var isFolder = args.path.endsWith('/'); - var path = utils.absolutePath(args.path, this.currentSharedFolder); - if (fs.existsSync(path) && !isRealPath(path, cb)) - return; - if (args.content === 'undefined') { // no !!!!! - console.log('trying to write "undefined" ! stopping.'); - return; - } - this.trackDownStreamUpdate[path] = path; - if (isFolder) { - fs.mkdirp(path).then(function () { return cb(); }).catch(function (e) { return cb(e); }); - } - else { - fs.ensureFile(path).then(function () { - fs.writeFile(path, args.content, 'utf8', function (error, data) { - if (error) - console.log(error); - cb(error, data); - }); - }).catch(function (e) { return cb(e); }); - } - }; - RemixdClient.prototype.rename = function (args, cb) { - if (this.readOnly) - return cb('Cannot rename file: read-only mode selected'); - var oldpath = utils.absolutePath(args.oldPath, this.currentSharedFolder); - if (!fs.existsSync(oldpath)) { - return cb('File not found ' + oldpath); - } - var newpath = utils.absolutePath(args.newPath, this.currentSharedFolder); - if (!isRealPath(oldpath, cb)) - return; - fs.move(oldpath, newpath, function (error, data) { - if (error) - console.log(error); - cb(error, data); - }); - }; - RemixdClient.prototype.remove = function (args, cb) { - if (this.readOnly) - return cb('Cannot remove file: read-only mode selected'); - var path = utils.absolutePath(args.path, this.currentSharedFolder); - if (!fs.existsSync(path)) { - return cb('File not found ' + path); - } - if (!isRealPath(path, cb)) - return; - fs.remove(path, function (error) { - if (error) { - console.log(error); - return cb('Failed to remove file/directory: ' + error); - } - cb(error, true); - }); - }; - RemixdClient.prototype.isDirectory = function (args, cb) { - var path = utils.absolutePath(args.path, this.currentSharedFolder); - cb(null, fs.statSync(path).isDirectory()); - }; - RemixdClient.prototype.isFile = function (args, cb) { - var path = utils.absolutePath(args.path, this.currentSharedFolder); - cb(null, fs.statSync(path).isFile()); - }; - return RemixdClient; -}(plugin_1.PluginClient)); -exports.default = RemixdClient; -function isRealPath(path, cb) { - var realPath = fs.realpathSync(path); - var isRealPath = path === realPath; - var mes = '[WARN] Symbolic link modification not allowed : ' + path + ' | ' + realPath; - if (!isRealPath) { - console.log('\x1b[33m%s\x1b[0m', mes); - } - if (cb && !isRealPath) - cb(mes); - return isRealPath; -} diff --git a/lib/src/websocket.d.ts b/lib/src/websocket.d.ts deleted file mode 100644 index ba7774563c..0000000000 --- a/lib/src/websocket.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/// -import * as WS from 'ws'; -import * as http from 'http'; -import RemixdClient from './services/remixdClient'; -export default class WebSocket { - port: number; - opt: { - [key: string]: string; - }; - server: http.Server; - wsServer: WS.Server; - remixdClient: RemixdClient; - constructor(port: number, opt: { - [key: string]: string; - }, remixdClient: RemixdClient); - start(callback?: Function): void; - close(): void; -} diff --git a/lib/src/websocket.js b/lib/src/websocket.js deleted file mode 100644 index 5a56511900..0000000000 --- a/lib/src/websocket.js +++ /dev/null @@ -1,36 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var WS = require("ws"); -var http = require("http"); -var buildWebsocketClient = require('@remixproject/plugin-ws').buildWebsocketClient; -var WebSocket = /** @class */ (function () { - function WebSocket(port, opt, remixdClient) { - this.port = port; - this.opt = opt; - this.remixdClient = remixdClient; - } - WebSocket.prototype.start = function (callback) { - var obj = this; - this.server = http.createServer(function (request, response) { - console.log((new Date()) + ' Received request for ' + request.url); - response.writeHead(404); - response.end(); - }); - var loopback = '127.0.0.1'; - this.server.listen(this.port, loopback, function () { - console.log((new Date()) + ' Remixd is listening on ' + loopback + ':65520'); - }); - this.wsServer = new WS.Server({ server: this.server }); - this.wsServer.on('connection', function connection(ws) { - var client = buildWebsocketClient(ws, obj.remixdClient); - if (callback) - callback(client); - }); - }; - WebSocket.prototype.close = function () { - console.log('this.server: ', this.server); - this.server.close(); - }; - return WebSocket; -}()); -exports.default = WebSocket; diff --git a/package.json b/package.json index de235610ad..4045533899 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ }, "scripts": { "test": "echo \"Error: no test specified\"", - "start": "./lib/bin/remixd.js -s ./shared --remix-ide http://127.0.0.1:8080", + "start": "./lib/bin/remixd.js", "npip": "npip", "lint": "eslint ./src", - "build": "tsc -p ./; chmod +x ./lib/bin/remixd.js", + "build": "tsc -p ./ && chmod +x ./lib/bin/remixd.js", "dev": "nodemon" }, "repository": { diff --git a/src/services/remixdClient.ts b/src/services/remixdClient.ts index 7f1dbf9a00..458a618ca0 100644 --- a/src/services/remixdClient.ts +++ b/src/services/remixdClient.ts @@ -1,14 +1,14 @@ import WebSocket from '../websocket' import { PluginClient } from '@remixproject/plugin' +import { SharedFolderArgs, TrackDownStreamUpdate } from '../../types' const utils = require('../utils') const isbinaryfile = require('isbinaryfile') const fs = require('fs-extra') export default class RemixdClient extends PluginClient { - trackDownStreamUpdate: { - [key: string]: string - } + methods: [] + trackDownStreamUpdate: TrackDownStreamUpdate websocket: WebSocket | null currentSharedFolder: string readOnly: boolean @@ -22,9 +22,7 @@ export default class RemixdClient extends PluginClient { this.readOnly = readOnly } - list (args: { - [key: string]: string - }, cb: Function) { + list (args: SharedFolderArgs, cb: Function) { try { cb(null, utils.walkSync(this.currentSharedFolder, {}, this.currentSharedFolder)) } catch (e) { @@ -32,27 +30,23 @@ export default class RemixdClient extends PluginClient { } } - resolveDirectory (args: { - [key: string]: string - }, cb: Function) { + resolveDirectory (args: SharedFolderArgs, cb: Function) { try { const path = utils.absolutePath(args.path, this.currentSharedFolder) + cb(null, utils.resolveDirectory(path, this.currentSharedFolder)) } catch (e) { cb(e.message) } } - folderIsReadOnly (args: { - [key: string]: string - }, cb: Function) { + folderIsReadOnly (args: SharedFolderArgs, cb: Function) { return cb(null, this.readOnly) } - get (args: { - [key: string]: string - }, cb: Function) { + get (args: SharedFolderArgs, cb: Function) { const path = utils.absolutePath(args.path, this.currentSharedFolder) + if (!fs.existsSync(path)) { return cb('File not found ' + path) } @@ -70,20 +64,17 @@ export default class RemixdClient extends PluginClient { }) } - exists (args: { - [key: string]: string - }, cb: Function) { + exists (args: SharedFolderArgs, cb: Function) { const path = utils.absolutePath(args.path, this.currentSharedFolder) cb(null, fs.existsSync(path)) } - set (args: { - [key: string]: string - }, cb: Function) { + set (args: SharedFolderArgs, cb: Function) { if (this.readOnly) return cb('Cannot write file: read-only mode selected') const isFolder = args.path.endsWith('/') const path = utils.absolutePath(args.path, this.currentSharedFolder) + if (fs.existsSync(path) && !isRealPath(path, cb)) return if (args.content === 'undefined') { // no !!!!! console.log('trying to write "undefined" ! stopping.') @@ -102,15 +93,15 @@ export default class RemixdClient extends PluginClient { } } - rename (args: { - [key: string]: string - }, cb: Function) { + rename (args: SharedFolderArgs, cb: Function) { if (this.readOnly) return cb('Cannot rename file: read-only mode selected') const oldpath = utils.absolutePath(args.oldPath, this.currentSharedFolder) + if (!fs.existsSync(oldpath)) { return cb('File not found ' + oldpath) } const newpath = utils.absolutePath(args.newPath, this.currentSharedFolder) + if (!isRealPath(oldpath, cb)) return fs.move(oldpath, newpath, (error: Error, data: string) => { if (error) console.log(error) @@ -118,11 +109,10 @@ export default class RemixdClient extends PluginClient { }) } - remove (args: { - [key: string]: string - }, cb: Function) { + remove (args: SharedFolderArgs, cb: Function) { if (this.readOnly) return cb('Cannot remove file: read-only mode selected') const path = utils.absolutePath(args.path, this.currentSharedFolder) + if (!fs.existsSync(path)) { return cb('File not found ' + path) } @@ -136,17 +126,13 @@ export default class RemixdClient extends PluginClient { }) } - isDirectory (args: { - [key: string]: string - }, cb: Function) { + isDirectory (args: SharedFolderArgs, cb: Function) { const path = utils.absolutePath(args.path, this.currentSharedFolder) cb(null, fs.statSync(path).isDirectory()) } - isFile (args: { - [key: string]: string - }, cb: Function) { + isFile (args: SharedFolderArgs, cb: Function) { const path = utils.absolutePath(args.path, this.currentSharedFolder) cb(null, fs.statSync(path).isFile()) @@ -157,6 +143,7 @@ function isRealPath (path: string, cb: Function) { const realPath = fs.realpathSync(path) const isRealPath = path === realPath const mes = '[WARN] Symbolic link modification not allowed : ' + path + ' | ' + realPath + if (!isRealPath) { console.log('\x1b[33m%s\x1b[0m', mes) } diff --git a/src/utils.ts b/src/utils.ts index 36d6891e06..b914c7ce36 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +import { ResolveDirectory } from '../types' + const fs = require('fs-extra') const path = require('path') const isbinaryfile = require('isbinaryfile') @@ -32,12 +34,12 @@ function absolutePath (path: string, sharedFolder:string) { * @param {String} sharedFolder - absolute shared path. platform dependent representation * @return {String} relative path (Unix style which is the one used by Remix IDE) */ -function relativePath (path: string, sharedFolder: string) { - const relative = pathModule.relative(sharedFolder, path) +function relativePath (path: string, sharedFolder: string): string { + const relative = pathModule.relative(sharedFolder, path) return normalizePath(relative) } -function normalizePath (path: string) { +function normalizePath (path: string): string { if (process.platform === 'win32') { return path.replace(/\\/g, '/') } @@ -48,6 +50,7 @@ function walkSync (dir: string, filelist: { [key: string]: string }, sharedFolder: string) { const files: string[] = fs.readdirSync(dir) + filelist = filelist || {} files.forEach(function (file) { var subElement = path.join(dir, file) @@ -63,11 +66,10 @@ function walkSync (dir: string, filelist: { return filelist } -function resolveDirectory (dir: string, sharedFolder: string) { - const ret: { - [key: string]: any - } = {} +function resolveDirectory (dir: string, sharedFolder: string): ResolveDirectory { + const ret: ResolveDirectory = {} const files: string[] = fs.readdirSync(dir) + files.forEach(function (file) { const subElement = path.join(dir, file) if (!fs.lstatSync(subElement).isSymbolicLink()) { diff --git a/src/websocket.ts b/src/websocket.ts index 965395571a..58fea0ac74 100644 --- a/src/websocket.ts +++ b/src/websocket.ts @@ -1,29 +1,20 @@ import * as WS from 'ws' import * as http from 'http' import RemixdClient from './services/remixdClient' +import { WebsocketOpt } from '../types' + const { buildWebsocketClient } = require('@remixproject/plugin-ws') export default class WebSocket { - port: number - opt: { - [key: string]: string - } server: http.Server wsServer: WS.Server - remixdClient: RemixdClient - - constructor (port: number, opt: { - [key: string]: string - }, remixdClient: RemixdClient) { - this.port = port - this.opt = opt - this.remixdClient = remixdClient - } + + constructor (public port: number, public opt: WebsocketOpt, public remixdClient: RemixdClient) {} start (callback?: Function) { const obj = this - this.server = http.createServer(function (request, response) { + this.server = http.createServer((request, response) => { console.log((new Date()) + ' Received request for ' + request.url) response.writeHead(404) response.end() @@ -33,8 +24,18 @@ export default class WebSocket { this.server.listen(this.port, loopback, function () { console.log((new Date()) + ' Remixd is listening on ' + loopback + ':65520') }) - this.wsServer = new WS.Server({ server: this.server }) - this.wsServer.on('connection', function connection(ws) { + this.wsServer = new WS.Server({ + server: this.server, + verifyClient: (info, done) => { + if (!originIsAllowed(info.origin, this)) { + done(false) + console.log((new Date()) + ' Connection from origin ' + info.origin + ' rejected.') + return + } + done(true) + } + }) + this.wsServer.on('connection', function connection(ws, request) { const client = buildWebsocketClient(ws, obj.remixdClient) if(callback) callback(client) @@ -42,7 +43,14 @@ export default class WebSocket { } close () { - console.log('this.server: ', this.server) - this.server.close() + if (this.wsServer) { + this.wsServer.close(() => { + this.server.close() + }) + } } } + +function originIsAllowed (origin: string, self: WebSocket) { + return origin === self.opt.remixIdeUrl +} diff --git a/types/index.ts b/types/index.ts new file mode 100644 index 0000000000..18fbfca1cb --- /dev/null +++ b/types/index.ts @@ -0,0 +1,20 @@ +export type WebsocketOpt = { + remixIdeUrl: string +} + +export type SharedFolderArgs = { + path: string, + [key: string]: string | number | boolean +} + +export type KeyPairString = { + [key: string]: string +} + +export type ResolveDirectory = { + [key: string]: { + isDirectory: boolean + } +} + +export type TrackDownStreamUpdate = KeyPairString \ No newline at end of file