diff --git a/lib/bin/remixd.d.ts b/lib/bin/remixd.d.ts new file mode 100644 index 0000000000..b7988016da --- /dev/null +++ b/lib/bin/remixd.d.ts @@ -0,0 +1,2 @@ +#!/usr/bin/env node +export {}; diff --git a/lib/remixd.js b/lib/bin/remixd.js old mode 100644 new mode 100755 similarity index 70% rename from lib/remixd.js rename to lib/bin/remixd.js index 1bb4cd83a5..8107adc8aa --- a/lib/remixd.js +++ b/lib/bin/remixd.js @@ -1,7 +1,8 @@ #!/usr/bin/env node "use strict"; -var Router = require('../src/router'); -var servicesList = require('../src/servicesList'); +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 ') @@ -20,13 +21,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'); - var sharedFolderrouter = new Router(65520, servicesList['sharedfolder'], { remixIdeUrl: program.remixIde }, function (webSocket) { - servicesList['sharedfolder'].setWebSocket(webSocket); - servicesList['sharedfolder'].setupNotifications(program.sharedFolder); - servicesList['sharedfolder'].sharedFolder(program.sharedFolder, program.readOnly || false); - // buildWebsocketClient(webSocket.connection, new servicesList['sharedfolder']()) - }); - killCallBack.push(sharedFolderrouter.start()); + 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() { diff --git a/lib/remixd.d.ts b/lib/remixd.d.ts deleted file mode 100644 index deedf7ccd9..0000000000 --- a/lib/remixd.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env node -declare const Router: any; -declare const servicesList: any; -declare const program: any; -declare const killCallBack: Array; -declare function kill(): void; diff --git a/lib/src/index.d.ts b/lib/src/index.d.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/src/index.js b/lib/src/index.js new file mode 100644 index 0000000000..84996a9542 --- /dev/null +++ b/lib/src/index.js @@ -0,0 +1,8 @@ +'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 new file mode 100644 index 0000000000..016ddd9fcc --- /dev/null +++ b/lib/src/services/remixdClient.d.ts @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000000..18bc4220d3 --- /dev/null +++ b/lib/src/services/remixdClient.js @@ -0,0 +1,158 @@ +"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 utils = require('../utils'); +var isbinaryfile = require('isbinaryfile'); +var fs = require('fs-extra'); +var chokidar = require('chokidar'); +var plugin_1 = require("@remixproject/plugin"); +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 = utils.absolutePath(args.path, this.currentSharedFolder); + cb(null, utils.resolveDirectory(path, 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 new file mode 100644 index 0000000000..56127edcf1 --- /dev/null +++ b/lib/src/websocket.d.ts @@ -0,0 +1,20 @@ +/// +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; + connection: WS; + remixdClient: RemixdClient; + constructor(port: number, opt: { + [key: string]: string; + }, remixdClient: RemixdClient); + start(callback?: Function): void; + send(data: any): void; + close(): void; +} diff --git a/lib/src/websocket.js b/lib/src/websocket.js new file mode 100644 index 0000000000..f1135cb60c --- /dev/null +++ b/lib/src/websocket.js @@ -0,0 +1,44 @@ +"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) { + obj.connection = ws; + var client = buildWebsocketClient(obj.connection, obj.remixdClient); + if (callback) + callback(client); + }); + }; + WebSocket.prototype.send = function (data) { + this.connection.send(data); + }; + WebSocket.prototype.close = function () { + if (this.connection) { + this.connection.close(); + } + if (this.server) { + this.server.close(); + } + }; + return WebSocket; +}()); +exports.default = WebSocket; diff --git a/nodemon.json b/nodemon.json index 3f47b7222d..0f98d8b913 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,5 +1,5 @@ { - "watch": ["./src"], + "watch": ["./src", "./bin"], "ext": "ts", - "exec": "npm run build" + "exec": "npm run build && npm run start" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 69b5c2f643..9fd152d5eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,19 @@ "@babel/helper-validator-identifier": "^7.9.0", "chalk": "^2.0.0", "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@remixproject/plugin": { @@ -68,8 +81,7 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/node": { "version": "14.0.5", @@ -539,14 +551,49 @@ "dev": true }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "chardet": { @@ -601,6 +648,11 @@ } } }, + "clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==" + }, "cli-boxes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", @@ -955,6 +1007,17 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1328,6 +1391,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "figlet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.4.0.tgz", + "integrity": "sha512-CxxIjEKHlqGosgXaIA+sikGDdV6KZOOlzPJnYuPgQlOSHZP5h9WIghYI30fyXnwEVeSH7Hedy72gC6zJrFC+SQ==" + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", diff --git a/package.json b/package.json index d0ffee4a38..325e4c3fee 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,15 @@ "description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)", "main": "./lib/src/index.js", "bin": { - "remixd": "./lib/bin/remixd" + "remixd": "./lib/bin/remixd.js" }, "scripts": { "test": "echo \"Error: no test specified\"", - "start": "./lib/bin/remixd", + "start": "./lib/bin/remixd.js -s ./shared --remix-ide http://127.0.0.1:8080", "npip": "npip", "lint": "eslint ./src", - "build": "tsc -p ./" + "build": "tsc -p ./; chmod +x ./lib/bin/remixd.js", + "dev": "nodemon" }, "repository": { "type": "git", @@ -32,8 +33,11 @@ "dependencies": { "@remixproject/plugin": "^0.1.18", "@remixproject/plugin-ws": "^0.2.0-alpha.1", + "chalk": "^4.0.0", "chokidar": "^2.0.2", - "commander": "^2.9.0", + "clear": "^0.1.0", + "commander": "^2.20.3", + "figlet": "^1.4.0", "fs-extra": "^3.0.1", "isbinaryfile": "^3.0.2", "ws": "^7.3.0" diff --git a/src/websocket.ts b/src/websocket.ts index 717efe0695..1a287c50f9 100644 --- a/src/websocket.ts +++ b/src/websocket.ts @@ -1,7 +1,7 @@ import * as WS from 'ws' import * as http from 'http' import RemixdClient from './services/remixdClient' -import { buildWebsocketClient } from '@remixproject/plugin-ws' +const { buildWebsocketClient } = require('@remixproject/plugin-ws') export default class WebSocket { port: number diff --git a/tsconfig.json b/tsconfig.json index 8601b5564d..da86154558 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,6 @@ /* Experimental Options */ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - "typeRoots": [ "./types"] - }, - "exclude": ["./lib"] + "skipLibCheck": true, + } } \ No newline at end of file diff --git a/types/@remixproject/plugin-ws/index.d.ts b/types/@remixproject/plugin-ws/index.d.ts deleted file mode 100644 index 06fc3d2219..0000000000 --- a/types/@remixproject/plugin-ws/index.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare module '@remixproject/plugin-ws' { -import { PluginApi, ApiMap, ProfileMap, Api, RemixApi } from '../utils'; -import { PluginClient, PluginOptions } from '@remixproject/plugin/client'; -export interface WS { - send(data: string): void; - on(type: 'message', cb: (event: WSData) => any): this; -} -export interface WSData { - toString(): string; -} -export declare function connectWS(socket: WS, client: PluginClient): void; -/** - * Connect the client to the socket - * @param client A plugin client - */ -export declare function buildWebsocketClient(socket: WS, client: PluginClient): PluginApi> & PluginClient; -/** - * Create a plugin client that listen on socket messages - * @param options The options for the client - */ -export declare function createWebsocketClient(socket: WS, options?: Partial>): PluginApi> & PluginClient; -declare type GetApi = T extends ProfileMap ? I : never; -export {}; -}