Merge pull request #109 from ethereum/remixd-websocket

Remixd Websocket Plugin
pull/3094/head
yann300 5 years ago committed by GitHub
commit a5669b257e
  1. 11
      .eslintrc.json
  2. 2
      .gitignore
  3. 30
      bin/remixd.ts
  4. 5
      nodemon.json
  5. 4060
      package-lock.json
  6. 31
      package.json
  7. 0
      src/index.ts
  8. 52
      src/router.js
  9. 3
      src/serviceList.ts
  10. 192
      src/services/remixdClient.ts
  11. 180
      src/services/sharedFolder.js
  12. 3
      src/servicesList.js
  13. 50
      src/utils.ts
  14. 77
      src/websocket.js
  15. 56
      src/websocket.ts
  16. 15
      tsconfig.json
  17. 39
      types/index.ts

@ -5,16 +5,17 @@
"es6": true
},
"extends": [
"standard"
"plugin:@typescript-eslint/recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"rules": {
"standard/no-callback-literal": 0
"standard/no-callback-literal": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/ban-types": 0
}
}

2
.gitignore vendored

@ -1,3 +1,5 @@
node_modules
npm-debug.log
python_modules
lib
shared

@ -1,7 +1,9 @@
#!/usr/bin/env node
var Router = require('../src/router')
var servicesList = require('../src/servicesList')
var program = require('commander')
import WebSocket from '../src/websocket'
import * as servicesList from '../src/serviceList'
import { WS } from '../types/index'
const program = require('commander')
program
.usage('-s <shared folder>')
@ -13,22 +15,28 @@ program
console.log('\nExample:\n\n remixd -s ./ --remix-ide http://localhost:8080')
}).parse(process.argv)
var killCallBack = []
const killCallBack: Array<Function> = []
if (!program.remixIde) {
return console.log('\x1b[31m%s\x1b[0m', '[ERR] URL Remix IDE instance has to be provided.')
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 sharedFolderrouter = new Router(65520, servicesList['sharedfolder'], { remixIdeUrl: program.remixIde }, (webSocket) => {
servicesList['sharedfolder'].setWebSocket(webSocket)
servicesList['sharedfolder'].setupNotifications(program.sharedFolder)
servicesList['sharedfolder'].sharedFolder(program.sharedFolder, program.readOnly || false)
})
killCallBack.push(sharedFolderrouter.start())
try {
const sharedFolderClient = new servicesList['sharedfolder']()
const websocketHandler = new WebSocket(65520, { remixIdeUrl: program.remixIde }, sharedFolderClient)
websocketHandler.start((ws: WS) => {
sharedFolderClient.setWebSocket(ws)
sharedFolderClient.sharedFolder(program.sharedFolder, program.readOnly || false)
})
killCallBack.push(websocketHandler.close.bind(websocketHandler))
} catch(error) {
throw new Error(error)
}
}
// kill

@ -0,0 +1,5 @@
{
"watch": ["./src", "./bin"],
"ext": "ts",
"exec": "npm run build && npm run start"
}

4060
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -2,15 +2,17 @@
"name": "remixd",
"version": "0.1.8-alpha.16",
"description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)",
"main": "./src/index.js",
"main": "./lib/src/index.js",
"bin": {
"remixd": "./bin/remixd"
"remixd": "./lib/bin/remixd.js"
},
"scripts": {
"test": "echo \"Error: no test specified\"",
"start": "./bin/remixd",
"start": "./lib/bin/remixd.js",
"npip": "npip",
"lint": "eslint ./src"
"lint": "eslint ./src --ext .ts",
"build": "tsc -p ./ && chmod +x ./lib/bin/remixd.js",
"dev": "nodemon"
},
"repository": {
"type": "git",
@ -29,16 +31,14 @@
},
"homepage": "https://github.com/ethereum/remixd#readme",
"dependencies": {
"@remixproject/plugin": "^0.3.0-alpha.3",
"@remixproject/plugin-ws": "^0.3.0-alpha.1",
"chalk": "^4.0.0",
"chokidar": "^2.0.2",
"commander": "^2.9.0",
"commander": "^2.20.3",
"fs-extra": "^3.0.1",
"isbinaryfile": "^3.0.2",
"lerna": "^2.9.0",
"nopy": "^0.2.6",
"serve": "7.0.0",
"stdout": "0.0.3",
"watch": "^1.0.2",
"websocket": "^1.0.24"
"ws": "^7.3.0"
},
"python": {
"execPath": "python3",
@ -47,11 +47,18 @@
}
},
"devDependencies": {
"@types/node": "^14.0.5",
"@types/ws": "^7.2.4",
"@typescript-eslint/eslint-plugin": "^3.2.0",
"@typescript-eslint/parser": "^3.2.0",
"eslint": "6.8.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.20.2",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "4.0.1"
"eslint-plugin-standard": "4.0.1",
"nodemon": "^2.0.4",
"ts-node": "^8.10.1",
"typescript": "^3.9.3"
}
}

@ -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,14 +1,9 @@
var fs = require('fs-extra')
var path = require('path')
var isbinaryfile = require('isbinaryfile')
var pathModule = require('path')
import { ResolveDirectory, Filelist } from '../types'
module.exports = {
absolutePath: absolutePath,
relativePath: relativePath,
walkSync: walkSync,
resolveDirectory: resolveDirectory
}
const fs = require('fs-extra')
const path = require('path')
const isbinaryfile = require('isbinaryfile')
const pathModule = require('path')
/**
* returns the absolute path of the given @arg path
@ -17,7 +12,7 @@ module.exports = {
* @param {String} sharedFolder - absolute shared path. platform dependent representation.
* @return {String} platform dependent absolute path (/home/user1/.../... for unix, c:\user\...\... for windows)
*/
function absolutePath (path, sharedFolder) {
function absolutePath (path: string, sharedFolder:string): string {
path = normalizePath(path)
if (path.indexOf(sharedFolder) !== 0) {
path = pathModule.resolve(sharedFolder, path)
@ -32,28 +27,32 @@ function absolutePath (path, sharedFolder) {
* @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, sharedFolder) {
var relative = pathModule.relative(sharedFolder, path)
function relativePath (path: string, sharedFolder: string): string {
const relative: string = pathModule.relative(sharedFolder, path)
return normalizePath(relative)
}
function normalizePath (path) {
function normalizePath (path: string): string {
if (process.platform === 'win32') {
return path.replace(/\\/g, '/')
}
return path
}
function walkSync (dir, filelist, sharedFolder) {
var files = fs.readdirSync(dir)
function walkSync (dir: string, filelist: Filelist, sharedFolder: string): Filelist {
const files: string[] = fs.readdirSync(dir)
filelist = filelist || {}
files.forEach(function (file) {
var subElement = path.join(dir, file)
const subElement = path.join(dir, file)
if (!fs.lstatSync(subElement).isSymbolicLink()) {
if (fs.statSync(subElement).isDirectory()) {
filelist = walkSync(subElement, filelist, sharedFolder)
} else {
var relative = relativePath(subElement, sharedFolder)
const relative = relativePath(subElement, sharedFolder)
filelist[relative] = isbinaryfile.sync(subElement)
}
}
@ -61,15 +60,20 @@ function walkSync (dir, filelist, sharedFolder) {
return filelist
}
function resolveDirectory (dir, sharedFolder) {
var ret = {}
var files = fs.readdirSync(dir)
function resolveDirectory (dir: string, sharedFolder: string): ResolveDirectory {
const ret: ResolveDirectory = {}
const files: string[] = fs.readdirSync(dir)
files.forEach(function (file) {
var subElement = path.join(dir, file)
const subElement = path.join(dir, file)
if (!fs.lstatSync(subElement).isSymbolicLink()) {
var relative = relativePath(subElement, sharedFolder)
const relative: string = relativePath(subElement, sharedFolder)
ret[relative] = { isDirectory: fs.statSync(subElement).isDirectory() }
}
})
return ret
}
export { absolutePath, relativePath, walkSync, resolveDirectory }

@ -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…
Cancel
Save