commit
cbb8e6f2af
@ -1,3 +1,5 @@ |
||||
node_modules |
||||
npm-debug.log |
||||
python_modules |
||||
lib |
||||
shared |
||||
|
@ -0,0 +1,5 @@ |
||||
{ |
||||
"watch": ["./src", "./bin"], |
||||
"ext": "ts", |
||||
"exec": "npm run build && npm run start" |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -1,52 +0,0 @@ |
||||
var Websocket = require('./websocket') |
||||
|
||||
class Router { |
||||
constructor (port, service, opt, initCallback) { |
||||
this.opt = opt |
||||
this.port = port |
||||
this.service = service |
||||
this.initCallback = initCallback |
||||
} |
||||
|
||||
start () { |
||||
var websocket = new Websocket(this.port, this.opt) |
||||
this.websocket = websocket |
||||
this.websocket.start((message) => { |
||||
this.call(message.id, message.service, message.fn, message.args) |
||||
}) |
||||
if (this.initCallback) this.initCallback(this.websocket) |
||||
return function () { |
||||
if (websocket) { |
||||
websocket.close() |
||||
} |
||||
} |
||||
} |
||||
|
||||
call (callid, name, fn, args) { |
||||
try { |
||||
this.service[fn](args, (error, data) => { |
||||
var response = { |
||||
id: callid, |
||||
type: 'reply', |
||||
scope: name, |
||||
result: data, |
||||
error: error |
||||
} |
||||
this.websocket.send(JSON.stringify(response)) |
||||
}) |
||||
} catch (e) { |
||||
var msg = 'Unexpected Error ' + e.message |
||||
console.log('\x1b[31m%s\x1b[0m', '[ERR] ' + msg) |
||||
if (this.websocket) { |
||||
this.websocket.send(JSON.stringify({ |
||||
id: callid, |
||||
type: 'reply', |
||||
scope: name, |
||||
error: msg |
||||
})) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
module.exports = Router |
@ -0,0 +1,3 @@ |
||||
import { RemixdClient as sharedfolder } from './services/remixdClient' |
||||
|
||||
export { sharedfolder } |
@ -0,0 +1,192 @@ |
||||
import { PluginClient } from '@remixproject/plugin' |
||||
import { SharedFolderArgs, TrackDownStreamUpdate, WS, Filelist, ResolveDirectory, FileContent } from '../../types' |
||||
import * as utils from '../utils' |
||||
|
||||
const isbinaryfile = require('isbinaryfile') |
||||
const fs = require('fs-extra') |
||||
|
||||
export class RemixdClient extends PluginClient { |
||||
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'list', 'isDirectory'] |
||||
trackDownStreamUpdate: TrackDownStreamUpdate = {} |
||||
websocket: WS |
||||
currentSharedFolder: string |
||||
readOnly: boolean |
||||
|
||||
setWebSocket (websocket: WS): void { |
||||
this.websocket = websocket |
||||
} |
||||
|
||||
sharedFolder (currentSharedFolder: string, readOnly: boolean): void { |
||||
this.currentSharedFolder = currentSharedFolder |
||||
this.readOnly = readOnly |
||||
} |
||||
|
||||
list (): Filelist { |
||||
try { |
||||
return utils.walkSync(this.currentSharedFolder, {}, this.currentSharedFolder) |
||||
} catch (e) { |
||||
throw new Error(e) |
||||
} |
||||
} |
||||
|
||||
resolveDirectory (args: SharedFolderArgs): ResolveDirectory { |
||||
try { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
const result = utils.resolveDirectory(path, this.currentSharedFolder) |
||||
|
||||
return result |
||||
} catch (e) { |
||||
throw new Error(e) |
||||
} |
||||
} |
||||
|
||||
folderIsReadOnly (): boolean { |
||||
return this.readOnly |
||||
} |
||||
|
||||
get (args: SharedFolderArgs): Promise<FileContent> { |
||||
try { |
||||
return new Promise((resolve, reject) => { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
if (!fs.existsSync(path)) { |
||||
reject('File not found ' + path) |
||||
} |
||||
if (!isRealPath(path)) return |
||||
isbinaryfile(path, (error: Error, isBinary: boolean) => { |
||||
if (error) console.log(error) |
||||
if (isBinary) { |
||||
resolve({ content: '<binary content not displayed>', readonly: true }) |
||||
} else { |
||||
fs.readFile(path, 'utf8', (error: Error, data: string) => { |
||||
if (error) console.log(error) |
||||
resolve({ content: data, readonly: false }) |
||||
}) |
||||
} |
||||
}) |
||||
}) |
||||
} catch (error) { |
||||
throw new Error(error) |
||||
} |
||||
} |
||||
|
||||
exists (args: SharedFolderArgs): boolean { |
||||
try { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
return fs.existsSync(path) |
||||
} catch(error) { |
||||
throw new Error(error) |
||||
} |
||||
} |
||||
|
||||
set (args: SharedFolderArgs): Promise<void> { |
||||
try { |
||||
return new Promise((resolve, reject) => { |
||||
if (this.readOnly) reject('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)) reject() |
||||
if (args.content === 'undefined') { // no !!!!!
|
||||
console.log('trying to write "undefined" ! stopping.') |
||||
reject('trying to write "undefined" ! stopping.') |
||||
} |
||||
this.trackDownStreamUpdate[path] = path |
||||
if (isFolder) { |
||||
fs.mkdirp(path).then(() => resolve()).catch((e: Error) => reject(e)) |
||||
} else { |
||||
fs.ensureFile(path).then(() => { |
||||
fs.writeFile(path, args.content, 'utf8', (error: Error) => { |
||||
if (error) { |
||||
console.log(error) |
||||
reject(error) |
||||
} |
||||
resolve() |
||||
}) |
||||
}).catch((e: Error) => reject(e)) |
||||
} |
||||
}) |
||||
} catch (error) { |
||||
throw new Error(error) |
||||
} |
||||
} |
||||
|
||||
rename (args: SharedFolderArgs): Promise<boolean> { |
||||
try { |
||||
return new Promise((resolve, reject) => { |
||||
if (this.readOnly) reject('Cannot rename file: read-only mode selected') |
||||
const oldpath = utils.absolutePath(args.oldPath, this.currentSharedFolder) |
||||
|
||||
if (!fs.existsSync(oldpath)) { |
||||
reject('File not found ' + oldpath) |
||||
} |
||||
const newpath = utils.absolutePath(args.newPath, this.currentSharedFolder) |
||||
|
||||
if (!isRealPath(oldpath)) return |
||||
fs.move(oldpath, newpath, (error: Error) => { |
||||
if (error) { |
||||
console.log(error) |
||||
reject(error.message) |
||||
} |
||||
resolve(true) |
||||
}) |
||||
}) |
||||
} catch (error) { |
||||
throw new Error(error) |
||||
} |
||||
} |
||||
|
||||
remove (args: SharedFolderArgs): Promise<boolean> { |
||||
try { |
||||
return new Promise((resolve, reject) => { |
||||
if (this.readOnly) reject('Cannot remove file: read-only mode selected') |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
if (!fs.existsSync(path)) reject('File not found ' + path) |
||||
if (!isRealPath(path)) return |
||||
return fs.remove(path, (error: Error) => { |
||||
if (error) { |
||||
console.log(error) |
||||
reject('Failed to remove file/directory: ' + error) |
||||
} |
||||
resolve(true) |
||||
}) |
||||
}) |
||||
} catch (error) { |
||||
throw new Error(error) |
||||
} |
||||
} |
||||
|
||||
isDirectory (args: SharedFolderArgs): boolean { |
||||
try { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
return fs.statSync(path).isDirectory() |
||||
} catch (error) { |
||||
throw new Error(error) |
||||
} |
||||
} |
||||
|
||||
isFile (args: SharedFolderArgs): boolean { |
||||
try { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
return fs.statSync(path).isFile() |
||||
} catch (error) { |
||||
throw new Error(error) |
||||
} |
||||
} |
||||
} |
||||
|
||||
function isRealPath (path: string): boolean { |
||||
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) |
||||
throw new Error(mes) |
||||
} |
||||
return isRealPath |
||||
} |
@ -1,180 +0,0 @@ |
||||
var utils = require('../utils') |
||||
var isbinaryfile = require('isbinaryfile') |
||||
var fs = require('fs-extra') |
||||
var chokidar = require('chokidar') |
||||
|
||||
module.exports = { |
||||
trackDownStreamUpdate: {}, |
||||
websocket: null, |
||||
alreadyNotified: {}, |
||||
|
||||
setWebSocket: function (websocket) { |
||||
this.websocket = websocket |
||||
}, |
||||
|
||||
sharedFolder: function (currentSharedFolder, readOnly) { |
||||
this.currentSharedFolder = currentSharedFolder |
||||
this.readOnly = readOnly |
||||
if (this.websocket.connection) this.websocket.send(message('rootFolderChanged', {})) |
||||
}, |
||||
|
||||
list: function (args, cb) { |
||||
try { |
||||
cb(null, utils.walkSync(this.currentSharedFolder, {}, this.currentSharedFolder)) |
||||
} catch (e) { |
||||
cb(e.message) |
||||
} |
||||
}, |
||||
|
||||
resolveDirectory: function (args, cb) { |
||||
try { |
||||
var path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
if (this.websocket && !this.alreadyNotified[path]) { |
||||
this.alreadyNotified[path] = 1 |
||||
this.setupNotifications(path) |
||||
} |
||||
cb(null, utils.resolveDirectory(path, this.currentSharedFolder)) |
||||
} catch (e) { |
||||
cb(e.message) |
||||
} |
||||
}, |
||||
|
||||
folderIsReadOnly: function (args, cb) { |
||||
return cb(null, this.readOnly) |
||||
}, |
||||
|
||||
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, (error, isBinary) => { |
||||
if (error) console.log(error) |
||||
if (isBinary) { |
||||
cb(null, { content: '<binary content not displayed>', readonly: true }) |
||||
} else { |
||||
fs.readFile(path, 'utf8', (error, data) => { |
||||
if (error) console.log(error) |
||||
cb(error, { content: data, readonly: false }) |
||||
}) |
||||
} |
||||
}) |
||||
}, |
||||
|
||||
exists: function (args, cb) { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
cb(null, fs.existsSync(path)) |
||||
}, |
||||
|
||||
set: function (args, cb) { |
||||
if (this.readOnly) return cb('Cannot write file: read-only mode selected') |
||||
const 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(_ => cb()).catch(e => cb(e)) |
||||
} else { |
||||
fs.ensureFile(path).then(() => { |
||||
fs.writeFile(path, args.content, 'utf8', (error, data) => { |
||||
if (error) console.log(error) |
||||
cb(error, data) |
||||
}) |
||||
}).catch(e => cb(e)) |
||||
} |
||||
}, |
||||
|
||||
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, (error, data) => { |
||||
if (error) console.log(error) |
||||
cb(error, data) |
||||
}) |
||||
}, |
||||
|
||||
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, (error) => { |
||||
if (error) { |
||||
console.log(error) |
||||
return cb('Failed to remove file/directory: ' + error) |
||||
} |
||||
cb(error, true) |
||||
}) |
||||
}, |
||||
|
||||
isDirectory: function (args, cb) { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
cb(null, fs.statSync(path).isDirectory()) |
||||
}, |
||||
|
||||
isFile: function (args, cb) { |
||||
const path = utils.absolutePath(args.path, this.currentSharedFolder) |
||||
|
||||
cb(null, fs.statSync(path).isFile()) |
||||
}, |
||||
|
||||
setupNotifications: function (path) { |
||||
if (!isRealPath(path)) return |
||||
var watcher = chokidar.watch(path, { depth: 0, ignorePermissionErrors: true }) |
||||
console.log('setup notifications for ' + path) |
||||
/* we can't listen on created file / folder |
||||
watcher.on('add', (f, stat) => { |
||||
isbinaryfile(f, (error, isBinary) => { |
||||
if (error) console.log(error) |
||||
console.log('add', f) |
||||
if (this.websocket.connection) this.websocket.send(message('created', { path: utils.relativePath(f, this.currentSharedFolder), isReadOnly: isBinary, isFolder: false })) |
||||
}) |
||||
}) |
||||
watcher.on('addDir', (f, stat) => { |
||||
if (this.websocket.connection) this.websocket.send(message('created', { path: utils.relativePath(f, this.currentSharedFolder), isReadOnly: false, isFolder: true })) |
||||
}) |
||||
*/ |
||||
watcher.on('change', (f, curr, prev) => { |
||||
if (this.trackDownStreamUpdate[f]) { |
||||
delete this.trackDownStreamUpdate[f] |
||||
return |
||||
} |
||||
if (this.websocket.connection) this.websocket.send(message('changed', utils.relativePath(f, this.currentSharedFolder))) |
||||
}) |
||||
watcher.on('unlink', (f) => { |
||||
if (this.websocket.connection) this.websocket.send(message('removed', { path: utils.relativePath(f, this.currentSharedFolder), isFolder: false })) |
||||
}) |
||||
watcher.on('unlinkDir', (f) => { |
||||
if (this.websocket.connection) this.websocket.send(message('removed', { path: utils.relativePath(f, this.currentSharedFolder), isFolder: true })) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
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 |
||||
} |
||||
|
||||
function message (name, value) { |
||||
return JSON.stringify({ type: 'notification', scope: 'sharedfolder', name: name, value: value }) |
||||
} |
@ -1,3 +0,0 @@ |
||||
module.exports = { |
||||
sharedfolder: require('./services/sharedFolder') |
||||
} |
@ -1,77 +0,0 @@ |
||||
#!/usr/bin/env node
|
||||
var WebSocketServer = require('websocket').server |
||||
var http = require('http') |
||||
|
||||
class WebSocket { |
||||
constructor (port, opt) { |
||||
this.connection = null |
||||
this.port = port |
||||
this.opt = opt |
||||
} |
||||
|
||||
start (callback) { |
||||
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 WebSocketServer({ |
||||
httpServer: this.server, |
||||
autoAcceptConnections: false, |
||||
maxReceivedFrameSize: 131072, |
||||
maxReceivedMessageSize: 10 * 1024 * 1024 |
||||
}) |
||||
|
||||
this.wsServer.on('request', (request) => { |
||||
if (!originIsAllowed(request.origin, this)) { |
||||
request.reject() |
||||
console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.') |
||||
return |
||||
} |
||||
if (this.connection) { |
||||
console.log('closing previous connection') |
||||
this.wsServer.closeAllConnections() |
||||
this.connection = null |
||||
return |
||||
} |
||||
|
||||
this.connection = request.accept('echo-protocol', request.origin) |
||||
console.log((new Date()) + ' Connection accepted.') |
||||
this.connection.on('message', (message) => { |
||||
if (message.type === 'utf8') { |
||||
callback(JSON.parse(message.utf8Data)) |
||||
} |
||||
}) |
||||
this.connection.on('close', (reasonCode, description) => { |
||||
console.log((new Date()) + ' Remix ' + this.connection.remoteAddress + ' disconnected.') |
||||
this.connection = null |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
send (data) { |
||||
this.connection.sendUTF(data) |
||||
} |
||||
|
||||
close () { |
||||
if (this.connection) { |
||||
this.connection.close() |
||||
this.connection = null |
||||
} |
||||
if (this.server) { |
||||
this.server.close() |
||||
this.server = null |
||||
} |
||||
} |
||||
} |
||||
|
||||
function originIsAllowed (origin, self) { |
||||
return origin === self.opt.remixIdeUrl |
||||
} |
||||
|
||||
module.exports = WebSocket |
@ -0,0 +1,56 @@ |
||||
import * as WS from 'ws' |
||||
import * as http from 'http' |
||||
import { WebsocketOpt, SharedFolderClient } from '../types' |
||||
|
||||
const { buildWebsocketClient } = require('@remixproject/plugin-ws') |
||||
|
||||
export default class WebSocket { |
||||
server: http.Server |
||||
wsServer: WS.Server |
||||
connection: WS |
||||
|
||||
constructor (public port: number, public opt: WebsocketOpt, public sharedFolder: SharedFolderClient) {} |
||||
|
||||
start (callback?: Function): void { |
||||
this.server = http.createServer((request, response) => { |
||||
console.log((new Date()) + ' Received request for ' + request.url) |
||||
response.writeHead(404) |
||||
response.end() |
||||
}) |
||||
const 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, |
||||
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', (ws) => { |
||||
const { sharedFolder } = this |
||||
|
||||
this.connection = ws |
||||
buildWebsocketClient(ws, sharedFolder) |
||||
if(callback) callback(ws) |
||||
}) |
||||
} |
||||
|
||||
close (): void { |
||||
if (this.wsServer) { |
||||
this.wsServer.close(() => { |
||||
this.server.close() |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
function originIsAllowed (origin: string, self: WebSocket): boolean { |
||||
return origin === self.opt.remixIdeUrl |
||||
} |
@ -0,0 +1,15 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
/* Basic Options */ |
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ |
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ |
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||
"outDir": "./lib", |
||||
"strict": true, /* Enable all strict type-checking options. */ |
||||
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ |
||||
/* Experimental Options */ |
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||
"skipLibCheck": true, |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
import * as ServiceList from '../src/serviceList' |
||||
import * as Websocket from 'ws' |
||||
|
||||
type ServiceListKeys = keyof typeof ServiceList; |
||||
|
||||
export type SharedFolder = typeof ServiceList[ServiceListKeys] |
||||
|
||||
export type SharedFolderClient = InstanceType<typeof ServiceList[ServiceListKeys]> |
||||
|
||||
export type WebsocketOpt = { |
||||
remixIdeUrl: string |
||||
} |
||||
|
||||
export type FolderArgs = { |
||||
path: string |
||||
} |
||||
|
||||
export type KeyPairString = { |
||||
[key: string]: string |
||||
} |
||||
|
||||
export type ResolveDirectory = { |
||||
[key: string]: { |
||||
isDirectory: boolean |
||||
} |
||||
} |
||||
|
||||
export type FileContent = { |
||||
content: string |
||||
readonly: boolean |
||||
} |
||||
|
||||
export type TrackDownStreamUpdate = KeyPairString |
||||
|
||||
export type SharedFolderArgs = FolderArgs & KeyPairString |
||||
|
||||
export type WS = typeof Websocket |
||||
|
||||
export type Filelist = KeyPairString |
Loading…
Reference in new issue