From 552b28b4fc1ed42e3362c1826acf94c349425b1c Mon Sep 17 00:00:00 2001 From: ken Date: Sun, 13 Aug 2017 23:04:34 +0000 Subject: [PATCH] 0.0.4 --- skel.html => bootstrapp.html | 0 index.html | 12 - npm-debug.log | 99 --- bootstrap.js => old/bootstrap.js | 0 client-test.js => old/client-test.js | 0 client.js => old/client.js | 0 host-test.js => old/host-test.js | 0 host.js => old/host.js | 0 main.js => old/main.js | 0 remote-server.html => old/remote-server.html | 0 old/router-new.js | 649 ++++++++++++++++++ router.js => old/router.js | 0 rollup.config.js | 6 + src/strapp.js | 98 +++ strappCrypto.js => src/strappCrypto.js | 0 .../strappFileManager.js | 0 .../strappFileSystem.js | 0 .../strappPeerConnection.js | 0 .../strappProtocolManager.js | 0 strapp.js | 27 - 20 files changed, 753 insertions(+), 138 deletions(-) rename skel.html => bootstrapp.html (100%) delete mode 100644 index.html delete mode 100644 npm-debug.log rename bootstrap.js => old/bootstrap.js (100%) rename client-test.js => old/client-test.js (100%) rename client.js => old/client.js (100%) rename host-test.js => old/host-test.js (100%) rename host.js => old/host.js (100%) rename main.js => old/main.js (100%) rename remote-server.html => old/remote-server.html (100%) create mode 100644 old/router-new.js rename router.js => old/router.js (100%) create mode 100644 rollup.config.js create mode 100644 src/strapp.js rename strappCrypto.js => src/strappCrypto.js (100%) rename strappFileManager.js => src/strappFileManager.js (100%) rename strappFileSystem.js => src/strappFileSystem.js (100%) rename strappPeerConnection.js => src/strappPeerConnection.js (100%) rename strappProtocolManager.js => src/strappProtocolManager.js (100%) delete mode 100644 strapp.js diff --git a/skel.html b/bootstrapp.html similarity index 100% rename from skel.html rename to bootstrapp.html diff --git a/index.html b/index.html deleted file mode 100644 index b06516b..0000000 --- a/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Strapp.io - - - - -

Strapp.io

- - diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index c971f8e..0000000 --- a/npm-debug.log +++ /dev/null @@ -1,99 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ 'C:\\Program Files\\nodejs\\node.exe', -1 verbose cli 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js', -1 verbose cli 'install', -1 verbose cli 'n', -1 verbose cli '-g' ] -2 info using npm@3.10.10 -3 info using node@v6.11.0 -4 silly loadCurrentTree Starting -5 silly install loadCurrentTree -6 silly install readGlobalPackageData -7 silly fetchPackageMetaData n -8 silly fetchNamedPackageData n -9 silly mapToRegistry name n -10 silly mapToRegistry using default registry -11 silly mapToRegistry registry https://registry.npmjs.org/ -12 silly mapToRegistry data Result { -12 silly mapToRegistry raw: 'n', -12 silly mapToRegistry scope: null, -12 silly mapToRegistry escapedName: 'n', -12 silly mapToRegistry name: 'n', -12 silly mapToRegistry rawSpec: '', -12 silly mapToRegistry spec: 'latest', -12 silly mapToRegistry type: 'tag' } -13 silly mapToRegistry uri https://registry.npmjs.org/n -14 verbose request uri https://registry.npmjs.org/n -15 verbose request no auth needed -16 info attempt registry request try #1 at 1:42:44 PM -17 verbose request id 3f5b5b88b169934f -18 verbose etag W/"59501789-10f80" -19 verbose lastModified Sun, 25 Jun 2017 20:05:29 GMT -20 http request GET https://registry.npmjs.org/n -21 http 304 https://registry.npmjs.org/n -22 verbose headers { date: 'Tue, 27 Jun 2017 20:42:43 GMT', -22 verbose headers via: '1.1 varnish', -22 verbose headers 'cache-control': 'max-age=300', -22 verbose headers etag: 'W/"59501789-10f80"', -22 verbose headers age: '0', -22 verbose headers connection: 'keep-alive', -22 verbose headers 'x-served-by': 'cache-lax8631-LAX', -22 verbose headers 'x-cache': 'HIT', -22 verbose headers 'x-cache-hits': '1', -22 verbose headers 'x-timer': 'S1498596163.275721,VS0,VE158', -22 verbose headers vary: 'Accept-Encoding' } -23 silly get cb [ 304, -23 silly get { date: 'Tue, 27 Jun 2017 20:42:43 GMT', -23 silly get via: '1.1 varnish', -23 silly get 'cache-control': 'max-age=300', -23 silly get etag: 'W/"59501789-10f80"', -23 silly get age: '0', -23 silly get connection: 'keep-alive', -23 silly get 'x-served-by': 'cache-lax8631-LAX', -23 silly get 'x-cache': 'HIT', -23 silly get 'x-cache-hits': '1', -23 silly get 'x-timer': 'S1498596163.275721,VS0,VE158', -23 silly get vary: 'Accept-Encoding' } ] -24 verbose etag https://registry.npmjs.org/n from cache -25 verbose get saving n to C:\Users\Jordan\AppData\Roaming\npm-cache\registry.npmjs.org\n\.cache.json -26 verbose correctMkdir C:\Users\Jordan\AppData\Roaming\npm-cache correctMkdir not in flight; initializing -27 silly install normalizeTree -28 silly loadCurrentTree Finishing -29 silly loadIdealTree Starting -30 silly install loadIdealTree -31 silly cloneCurrentTree Starting -32 silly install cloneCurrentTreeToIdealTree -33 silly cloneCurrentTree Finishing -34 silly loadShrinkwrap Starting -35 silly install loadShrinkwrap -36 silly loadShrinkwrap Finishing -37 silly loadAllDepsIntoIdealTree Starting -38 silly install loadAllDepsIntoIdealTree -39 silly rollbackFailedOptional Starting -40 silly rollbackFailedOptional Finishing -41 silly runTopLevelLifecycles Finishing -42 silly install printInstalled -43 verbose stack Error: Unsupported platform for n@2.1.7: wanted {"name":"n","description":"Interactively Manage All Your Node Versions","version":"2.1.7","author":{"name":"TJ Holowaychuk","email":"tj@vision-media.ca"},"homepage":"https://github.com/tj/n","bugs":{"url":"https://github.com/tj/n/issues"},"contributors":[{"name":"Travis Webb","email":"me@traviswebb.com","url":"tjw.io"},{"name":"Nimit Kalra","email":"me@nimit.io","url":"http://nimit.io"},{"name":"Troy Connor","email":"troy0820@gmail.com","url":"https://github.com/troy0820"}],"keywords":["nvm","node","version","manager","switcher","node","binary","env"],"bin":{"n":"./bin/n"},"repository":{"type":"git","url":"git://github.com/tj/n.git"},"preferGlobal":true,"os":["!win32"],"engines":{"node":"*"},"license":"MIT","gitHead":"bcec70577549a9a11d89d10284f8890f0debf6d6","_id":"n@2.1.7","scripts":{},"_shasum":"13fe032a5bed0797983bddf27eb0606517c73c74","_from":"n","_npmVersion":"4.4.1","_nodeVersion":"7.7.3","_npmUser":{"name":"troy0820","email":"troy0820@gmail.com"},"dist":{"shasum":"13fe032a5bed0797983bddf27eb0606517c73c74","tarball":"https://registry.npmjs.org/n/-/n-2.1.7.tgz"},"maintainers":[{"name":"bat","email":"ben@benatkin.com"},{"name":"qw3rtman","email":"nimit@nimitkalra.com"},{"name":"tedgaydos","email":"tedgaydos@gmail.com"},{"name":"tjholowaychuk","email":"tj@vision-media.ca"},{"name":"tjwebb","email":"me@traviswebb.com"},{"name":"troy0820","email":"troy.connor@yahoo.com"}],"_npmOperationalInternal":{"host":"packages-12-west.internal.npmjs.com","tmp":"tmp/n-2.1.7.tgz_1490918508873_0.5233936926815659"},"directories":{},"_resolved":"https://registry.npmjs.org/n/-/n-2.1.7.tgz","_requested":{"raw":"n","scope":null,"escapedName":"n","name":"n","rawSpec":"","spec":"latest","type":"tag"},"_spec":"n","_where":"C:\\Users\\Jordan\\strapp","_args":[[{"raw":"n","scope":null,"escapedName":"n","name":"n","rawSpec":"","spec":"latest","type":"tag"},"C:\\Users\\Jordan\\strapp"]],"readme":"ERROR: No README data found!"} (current: {"os":"win32","cpu":"x64"}) -43 verbose stack at checkPlatform (C:\Program Files\nodejs\node_modules\npm\node_modules\npm-install-checks\index.js:45:14) -43 verbose stack at thenWarnEngineIssues (C:\Program Files\nodejs\node_modules\npm\lib\install\validate-args.js:41:5) -43 verbose stack at C:\Program Files\nodejs\node_modules\npm\node_modules\iferr\index.js:13:50 -43 verbose stack at checkEngine (C:\Program Files\nodejs\node_modules\npm\node_modules\npm-install-checks\index.js:24:10) -43 verbose stack at module.exports.isInstallable (C:\Program Files\nodejs\node_modules\npm\lib\install\validate-args.js:38:3) -43 verbose stack at Array. (C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\bind-actor.js:15:8) -43 verbose stack at LOOP (C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\chain.js:15:14) -43 verbose stack at C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\chain.js:18:7 -43 verbose stack at checkSelf (C:\Program Files\nodejs\node_modules\npm\lib\install\validate-args.js:46:72) -43 verbose stack at Array. (C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\bind-actor.js:15:8) -44 verbose pkgid n@2.1.7 -45 verbose cwd C:\Users\Jordan\strapp -46 error Windows_NT 10.0.14393 -47 error argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "install" "n" "-g" -48 error node v6.11.0 -49 error npm v3.10.10 -50 error code EBADPLATFORM -51 error notsup Unsupported platform for n@2.1.7: wanted {"os":"!win32","arch":"any"} (current: {"os":"win32","arch":"x64"}) -52 error notsup Valid OS: !win32 -52 error notsup Valid Arch: any -52 error notsup Actual OS: win32 -52 error notsup Actual Arch: x64 -53 verbose exit [ 1, true ] diff --git a/bootstrap.js b/old/bootstrap.js similarity index 100% rename from bootstrap.js rename to old/bootstrap.js diff --git a/client-test.js b/old/client-test.js similarity index 100% rename from client-test.js rename to old/client-test.js diff --git a/client.js b/old/client.js similarity index 100% rename from client.js rename to old/client.js diff --git a/host-test.js b/old/host-test.js similarity index 100% rename from host-test.js rename to old/host-test.js diff --git a/host.js b/old/host.js similarity index 100% rename from host.js rename to old/host.js diff --git a/main.js b/old/main.js similarity index 100% rename from main.js rename to old/main.js diff --git a/remote-server.html b/old/remote-server.html similarity index 100% rename from remote-server.html rename to old/remote-server.html diff --git a/old/router-new.js b/old/router-new.js new file mode 100644 index 0000000..0407198 --- /dev/null +++ b/old/router-new.js @@ -0,0 +1,649 @@ +/** + * @file HTTP(S) Router that treats the first directory in a URL's path as + * a route to a host. + * @author Ken Grimes + * @version 0.0.2 + * @license AGPL-3.0 + * @copyright Strapp.io + */ + +const dlog = (msg) => console.log(msg) + + +exports = { + +} + +exports = { + /** Regular expression for valid routes + * @prop {Object.RegEx} validRoutes - matches valid route names + */ + validRoutes: /[a-zA-Z][a-zA-Z0-9\-_]*/, + + /** HTML to distribute initially during bootstrapping process + * @prop {string} bootStrapp - raw HTML distributed to all clients + * @prop {string} bootStrappJS - raw JS distributed to all clients + */ + bootStrapp: '', + bootStrappJS: '', + + /** A map of routes + * @prop {Object.Map} routes - all the routes! + */ + routes: {}, + + /** Parameters set on bootup (startHttpServer) + * @prop {string[2]} skelPage - html document split in twain for JS injection + * @prop {string} clientJS - jerverscripps to inject in skelPage for clients + * @prop {string} hostJS - jerverscripps for the hosts + * @prop {string} httpdRoot - a normalized path for http-servable files + * @prop {string} bindJail - jail bindings to this path + */ + skelPage: undefined, + clientJS: undefined, + hostJS: undefined, + httpdRoot: undefined, + bindJail: undefined, + + /** @func + * @summary Start main HTTP server + * @desc starts up an HTTP or HTTPS server used for routing + * @arg {Object} conf - object containing configuration properties + * @prop {number|string} conf.port - local system port to bind to + * @prop {string} conf.skelFile - location of the skeleton HTML page + * @prop {string} conf.clientJS - client JS file + * @prop {string} conf.hostJS - host JS file + * @prop {string} [conf.httpdRoot] - root path of http-accessible files, if + * undefined no files are accessible + * @prop {Object} [conf.tls] - if present, startHttpServer will use tls + * @prop {string} [conf.tls.certFile] - tls certificate file + * @prop {string} [conf.tls.keyFile] - tls public key file + */ + startHttpServer: function (conf) { + if ('httpd' in this) + throw new Error('httpd already running') + if (conf.tls == undefined) + this.httpd = require('http').createServer((req, res) => + this.httpdListener(req, res)) + else if (!('keyFile' in conf.tls) || !('certFile' in conf.tls)) + throw new Error('HTTPS requires a valid key and cert') + else + this.syncReads([conf.tls.keyFile, conf.tls.certFile]).then((results) => { + Object.defineProperty(this, 'httpsOpts', { + value: { + key: results[conf.tls.keyFile], + cert: results[conf.tls.certFile] + } + }) + this.httpd = + require('https').createServer(this.httpsOpts, (request,response) => + this.httpdListener(request,response)) + .listen(conf.port) + }) + if (conf.httpdRoot) { + this.httpdRoot = require('path').normalize(conf.httpdRoot) + while (this.httpdRoot[this.httpdRoot.length - 1] == require('path').sep) + this.httpdRoot = this.httpdRoot.slice(0,-1) + } + this.syncReads([conf.skelFile, conf.clientJS, conf.hostJS]) + .then((results) => { + this.skelPage = results[conf.skelFile].split('') + this.clientJS = results[conf.clientJS] + this.hostJS = results[conf.hostJS] + }) + .catch((err) => { + console.log(err) + }) + console.log(`HTTP${(conf.tls == undefined) ? '' : 'S'} ` + + `Server Started on port ${conf.port}${this.httpdRoot ? + `, serving files from ${this.httpdRoot}`:''}`) + }, + + /** @func + * @summary Create a binding for the server + * @desc makes a new route which is bound to a file or a path. routes + * bound to files always serve that file, regardless of any + * additional path parameters provided by the URI + * @arg {string} routeName - the route to create + * @arg {string} path - the path to the file or directory to bind + */ + createBind: function (routeName, path) { + dlog(`Binding ${routeName} to ${path}`) + if (routeName in this.routes) + throw new Error(`route ${routeName} already exists`) + path = require('path').normalize(path) + if (this.bindJail + && path.indexOf(`${this.bindJail}/`) !== 0 + && this.bindJail != path) + throw new Error(`${routeName}:${path} jailed to ${this.bindJail}`) + if (require('fs').existsSync(path)) { + this.routes[routeName] = { + bind: { + path: path, + dir: require('fs').lstatSync(path).isDirectory() + } + } + } + else + throw new Error(`${path} not found, ${routeName} not bound`) + }, + + /** @func + * @summary Router + * @desc listens for http client requests, authorization for hosts, message + * relaying with strapp API over HTTP + * @arg {http.ClientRequest} request + * @arg {http.ServerResponse} response + */ + httpdListener: function (request,response) { + dlog(`Received request ${request.method} ${request.url}`) + + /* No strapp type: serve bootstrapp.html and bootstrapp.js only */ + if (route.url) { + if (request.method !== 'GET') + response.writeHead(405) + else if (/^\/bootstrapp\.js[^/]*/.test(request.url)) { + response.writeHead(200, { 'Content-Type': 'application/javascript' }) + response.write(this.bootStrappJS) + } + else { + response.writeHead(200, { 'Content-Type': 'text/html' }) + response.write(this.bootStrapp) + } + response.end() + return + } + + const uri = request.url.slice(1).split('?') + const routeName = uri[0].split('/')[0] + + /* Process strapp types that make sense without a route */ + if (routeName === '') { + switch (type) { + case 'route-list': //GET + this.serveRouteList() + break + case 'register-account': //PUT + break + default: + dlog(`x-strapp-type: ${type} not valid without a route`) + response.writeHead(400) + response.end() + } + return + } + /* Handle invalid routenames (serve files if httpdRoot is defined) */ + else if (!this.validRoutes.test(routeName)) { + if (this.httpdRoot) { + let realPath = require('path').join(this.httpdRoot, uri[0]) + if (realPath == this.httpdRoot) + realPath += '/index.html' + if (realPath.indexOf(`${this.httpdRoot}/`) == 0) { + const stat_cb = (err, stat) => { + if (err) { + dlog(err) + response.writeHead(404) + response.end() + } + else if (stat.isDirectory()) { + realPath += '/index.html' + require('fs').lstat(realPath, stat_cb) + } + else if (stat.isFile()) + this.serveFile(response, realPath) + else { + response.writeHead(403) + response.end() + } + } + require('fs').lstat(realPath, stat_cb) + return + } + dlog(`Erroneous file location ${realPath} from ${request.url}`) + } + response.writeHead(400) + response.end() + return + } + /* Route does not exist */ + else if (!(routeName in this.routes)) { + response.writeHead(404) + response.end() + return + } + + const route = this.routes[routeName] + const authData = request.headers['Authorization'] + switch (request.method) { + /* Public Requests */ + case 'POST': //forward message to route + this.forward(route, authData, type, request.headers['x-strapp-data']) + if (route.online) { + route.socket.send(JSON.stringify( + ['sdpOffer', { + sdp: request.headers['x-sdp'], + pubKey: pubKey, + responseID: route.nextResponseID(response) + }])) + + } + break + /* Authorization Required */ + case 'make-socket': //CONNECT + this.authRouteOwner(route, authData) + .then(() => this.servePort(route, response)) + .catch(() => this.serveHead(response, 400)) + break + case 'app-list': //TRACE + this.authRouteOwner(route, authData) + .then(() => this.serveAppList(response)) + .catch(() => this.serveHead(response, 400)) + break + case 'app': //GET + const app = request.headers['x-strapp-app'] + if (app && app !== '' && app in this.appList) { + this.authRouteOwner(route, authData) + .then(() => this.serveApp(request.headers['x-strapp-app'], response)) + .catch(() => this.serveHead(response, 400)) + } + else + this.serveHead(response, 404) + break + default: + this.serveHead(response, 400) + dlog(`Unrecognized x-strapp-type: ${type}`) + break + } + + let htArgv = request.url.slice(1).split('?') +// const routeName = htArgv[0].split('/')[0] + + /* At the root (no route selected) */ + if (routeName === '') { + if (type === 'init') + this.serveSplash(response) + else { + dlog(`${type} request at null route`) + response.writeHead(400) + response.end() + } + return + } + + /* TODO: A new client account is registered */ + if (type === 'register-account') { + dlog("Not implemented") + response.writeHead(501) + response.end() + return + } + + let route = this.routes[routeName] + + /* TODO: A host, who needs authed, is requesting a route registration */ + if (type === 'register-route') { + dlog("Not implemented") + response.writeHead(501) + response.end() + return + } + /* TODO: Register a route to a host account */ + if (type === 'register-route-answer') { + this.routes[routeName] = true + require('get-port')() + .then((port) => { + this.createHost(routeName, htArgv, port, request, response) + }) + .catch((err) => { + delete this.routes[routeName] + console.log(err) + }) + } + /* Route exists, but is bound to a directory */ + if (route && route.bind) { + htArgv[0] = htArgv[0].replace(`${routeName}`,'') + if (htArgv[0][0] === '/') + htArgv[0] = htArgv[0].slice(1) + this.serveBind(response, route.bind, htArgv) + return + } + + /* Route may or may not be registered, and may or may not be online */ + switch (type) { + case 'init': + if (!route) + this.serveSplash(response, routeName) + else if (route.online) + route.socket.send(JSON.stringify( + ['clientRequest', { + pubKey: pubKey, + responseID: route.nextResponseID(response), + argv: htArgv + }])) + else { + response.writeHead(503) + response.end() + } + break + case 'sdp-request': + if (route && route.online) + route.socket.send(JSON.stringify( + ['sdpOffer', { + sdp: request.headers['x-sdp'], + pubKey: pubKey, + responseID: route.nextResponseID(response) + }])) + else { + response.writeHead(503) + response.end() + } + break + case 'ice-candidate': + if (route && route.online) + route.socket.send(JSON.stringify( + ['iceCandidate', { + ice: request.headers['x-ice'], + pubKey: pubKey, + responseID: route.nextResponseID(response) + }])) + else { + response.writeHead(503) + response.end() + } + break + case 'account-create' : + dlog("Not implemented") + response.writeHead(501) + response.end() + break + case 'auth': + case 'host-login': + if (!route) + response.writeHead(404) + else if (pubKey != route.pubKey) + response.writeHead(401) + else if (route.pendingSecret) { + response.writeHead(409) + if (route.timeout === undefined) + route.timeout = setTimeout(() => { + delete route.pendingSecret + delete route.timeout + }, 30000) + } + else { + response.writeHead(200, { 'Content-Type': 'application/json' }) + route.pendingSecret = this.nextSecret() + response.write(JSON.stringify( + ['serverAuth', { + secret: this.encrypt(route.pendingSecret, route.pubKey), + pubKey: this.pubKey + }])) + } + response.end() + break + case 'auth-answer': + case 'host-login-answer': + const answer = request.headers['x-strapp-answer'] + if (!route || pubKey != route.pubKey || !route.pendingSecret) { + response.writeHead(400) + response.end() + } + else if (answer && this.decrypt(answer) === route.pendingSecret) { + route.socket.close(1,'host reconnected') + route.httpd.close() + this.serveSocket(response, route) + delete route.pendingSecret + } + else { + response.writeHead(401) + response.end() + } + break + case 'route-connect': + break + default: + dlog(`Unrecognized x-strapp-type: ${type}`) + break + } + }, + + /** @func + * @summary Serves a binding to a client + * @desc Resolves a binding and serves a response to the client + * @arg {http.ServerResponse} response - the response to use + * @arg {Object} bind - the binding to serve the client + * @arg {string[]} argv - path and arguments for the bind + */ + serveBind: function (response, bind, argv) { + dlog(`Serving binding ${bind.path}/${argv[0]}`) + if (bind.dir) { + if (argv[0] == '') + argv[0] = 'index.html' + this.serveFile(response, require('path').join(bind.path, argv[0])) + } + else + this.serveFile(response, bind.path) + }, + + /** @func + * @summary Serve a route to an http client + * @desc routes may be bound to the filesystem, or to an outgoing host + * @arg {http.ClientRequest} request - request from the client + * @arg {http.ServerResponse} response - response object to use + * @arg {Object} route - route associated with client request + */ + serveClient: function (request, response, route) { + const type = request.headers['x-strapp-type'] + const pubKey = request.headers['x-strapp-pubkey'] + dlog(`Client ${type || 'HT GET'} request routed to ${route.name}`) + switch (type) { + case null: + case undefined: + response.writeHead(200, { 'Content-Type': 'text/html' }) + response.write(`${this.skelPage[0]}${this.clientJS}${this.skelPage[1]}`) + response.end() + break + case 'ice-candidate-request': + case 'ice-candidate-submission': + case 'client-sdp-offer': + let data = '' + if (pubKey) { + let data = request.headers['x-strapp-offer'] + route.pendingResponses.addResponse(pubKey, response) + dlog(`${route.origin}=>\n${pubKey}\n${type}`) + dlog(JSON.parse(data)) + route.socket.send(`${pubKey} ${type} ${data}`) + } + else { + response.writeHead(401) + response.end() + } + break + default: + response.writeHead(400) + response.end() + } + }, + + /** @func + * @summary Create a new route for a host + * @desc makes a new route for the given route name + * @arg {string} routeName - name of the new route + * @arg {string[]} argv - Origin address from the request that made this + * route (for security verification on the socket) + * @arg {number|string} port - the port to listen on for websocket + * @arg {http.ClientRequest} request - host's request + * @arg {http.ServerResponse} response - responder + */ + createHost: function (routeName, argv, port, request, response) { + const origin = (request.headers['x-forwarded-for'] || + request.connection.remoteAddress) + dlog(`New ${this.httpsOpts?'TLS ':''}route ${routeName}:${port}=>${origin}`) + const httpd = this.httpsOpts + ? require('https').createServer(this.httpsOpts) + : require('http').createServer() + const route = { + pendingResponses: new Map([]), + origin: origin, + httpd: httpd, + name: routeName, + port: port, + wsd: undefined, + socket: undefined, + online: false + } + route.httpd.listen(port) + route.wsd = new (require('ws').Server)({ server: httpd }) + .on('connection', (socket) => { + route.socket = socket + socket.on('message', (msg) => + this.hostMessage(msg,route)) + }) + route.pendingResponses.addResponse = function (key, response_p) { + let responses = this.get(key) || [] + responses.push(response_p) + this.set(key, responses) + } + this.routes[routeName] = route + this.serveHost(response, route, argv) + }, + + /** @Func + * @summary Serve a route to an authorized http host + * @desc services host application to the client, establishing a socket + * @arg {http.ServerResponse} response - response object to use + * @arg {Object} route - the route that belongs to this host + * @arg {string[]} argv - vector of arguments sent to the host + */ + serveHost: function (response, route, argv) { + dlog(`Serving host ${route.origin}`) + response.writeHead(200, { 'Content-Type': 'text/html' }) + response.write(`${this.skelPage[0]}` + + `\tconst _strapp_port = ${route.port}\n` + + `\tconst _strapp_protocol = ` + + `'${this.httpsOpts ? 'wss' : 'ws'}'\n` + + `${this.hostJS}\n${this.skelPage[1]}`) + response.end() + }, + + /** @func + * @summary handle host message + * @desc receives a message from a host, handles the command (first character), + * and responds to either the host or the client, or both. Commands + * are whitespace separated strings. + * Commands: + * Forward Payload to Client) + * < clientKey payload [header] + * Route 'payload' to the client identified by 'clientKey'. + * The optional 'header' argument is a stringified JSON object, + * which will be written to the HTTP response header + * In case of multiple requests from a single client, the + * oldest request will be serviced on arrival of message + * Translate SDP and Forward to Client) + * ^ clientKey sdp [header] + * Route the JSON object 'sdp' to the client, after translating + * for interop between browsers using planB or Unified. Other + * than the interop step, this is identical to the '<' command + * Error) + * ! errorMessage errorCode [offendingMessage] + * Notify host that an error has occured, providing a message + * and error code. 'offendingMessage', if present, is the + * message received from the remote that triggered the error. + * @arg {string} message - raw string from the host + * @arg {Object} route - the route over + */ + hostMessage: function (message, route) { + let argv = message.split(' ') + const command = argv[0][0] + argv = argv.slice(1) + dlog(`Received host message from ${route.name}: ${command}`) + switch (command) { + case '^': + if (argv.length < 2) { + dlog(`Malformed '${command}' command from ${route.origin}`) + route.socket.send(`! "Insufficient arguments" 0 ${message}`) + break + } + argv[1] = JSON.parse(argv[1]) + //TODO: interop step + argv[1] = JSON.stringify(argv[1]) + //TODO: argv[1] = encryptForClient(argv[0], argv[1]) + /* Fallthrough to '<' behavior after translating argv[1] */ + case '<': + const response = route.pendingResponses.get(argv[0]).shift() + if (!response) + route.socket.send(`! "No pending responses for client ${argv[0]}" 0 ` + + message) + else if (argv.length === 2 || argv.length === 3) { + const header = argv.length === 3 ? JSON.parse(argv[2]) : {} + if (!('Content-Type' in header)) + header['Content-Type'] = 'application/octet-stream' + response.writeHead(200, header) + response.write(argv[1]) + response.end() + } + else + route.socket.send(`! "Insufficient arguments" 0 ${message}`) + break + case '!': + if (argv.length === 3) + argv[0] += `\nIn message: ${argv[2]}` + console.log(`Error[${route.origin}|${argv[1]}]:${argv[0]}`) + break + default: + route.socket.send(`! "Unknown command '${command}'" 0 ${message}`) + dlog(`Host ${route.origin} send unknown command: ${message}`) + break + } + }, + + /** @func + * @summary Serve a file to an http client after a request + * @desc reads files from the system to be distributed to clients, and + * buffers recently accessed files + * @arg {http.ServerResponse} response - the response object to use + * @arg {string} filePath - relative location of the file + */ + serveFile: function (response, filePath, rootPath) { + //TODO: Make a buffer to hold recently used files, and only read if we + // have to (don't forget to preserve mimetype) + require('fs').readFile(filePath, { encoding: 'utf8' }, (err, data) => { + if (err || data === undefined) + response.writeHead(404) + else { + response.writeHead(200, { + 'Content-Type': require('mime').lookup(filePath) + }) + response.write(data) + } + response.end() + }) + }, + + /** @func + * @summary Synchronize Reading Multiple Files + * @desc reads an array of files into an object, whose keys are the + * input filenames, and values are the data read + * @arg {string[]} files - array of file names to read + * @arg {Object} [readOpts] - options to pass to fs.readFile() + */ + syncReads: (files, readOpts) => new Promise((resolve,reject) => { + dlog(`syncing reads from ${files}`) + let count = 0 + let results = {} + const read_cb = (fileName) => (err, data) => { + if (err) + reject(err) + else + results[fileName] = data + if (++count === files.length) + resolve(results) + } + if (readOpts == undefined) + readOpts = { encoding: 'utf8' } + files.forEach((file) => + require('fs').readFile(file, readOpts, read_cb(file))) + }) +} + +module.exports = exports diff --git a/router.js b/old/router.js similarity index 100% rename from router.js rename to old/router.js diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..9b234ff --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,6 @@ +export default { + entry: 'src/strapp.js', + dest: 'www/strapp.min.js', + format: 'iife', + sourceMap: 'inline' +} diff --git a/src/strapp.js b/src/strapp.js new file mode 100644 index 0000000..9ec9d09 --- /dev/null +++ b/src/strapp.js @@ -0,0 +1,98 @@ +/** +* @file Strapp main driver +* @author Jordan Lavatai, Ken Grimes +* @version 0.0.4 +* @license AGPL-3.0 +* @copyright August 2017 - Ken Grimes, Jordan Lavatai +* @summmary Bootstrapper for the strapp.io mechanism +*/ + +const StrappFile = (() => { + const authorize = (pubKey, mode, stat) => { + let allowed + if (pubKey === stat.owner) + allowed = (stat.perms >>> 16) & 0xF + else { + let gAccess = false + let uGroups = StrappFile.get(`acct/${pubKey}/groups`).split(' ') + for (let i = 0; i < uGroups.length; i++) { + if (uGroups[i] === stat.group) { + gAccess = true + break + } + } + if (gAccess) + allowed = (stat.perms >>> 8) & 0xF + else + allowed = stat.perms & 0xF + } + switch(mode){ + case 'r+': + case 'rw': + case 'wr': + return (allowed & 0x6) === 0x6 + case 'w': + return (allowed & 0x2) === 0x2 + case 'r': + return (allowed & 0x4) === 0x4 + case 'x': + return (allowed & 0x1) === 0x1 + default: + console.log(`Unknown access mode: ${mode}`) + return false + } + } + class StrappFile extends Object { + constructor(...props) { + super() + return Object.assign(this, new.target.defaults, ...props) + } + static PermissionDenied() { + return new Promise((resolve, reject) => reject('Permission denied')) + } + HEAD(opt) { + if (authorize(opt.pubKey, 'r', this.stat)) + return new Promise((resolve, reject) => resolve('')) + else + return StrappFile.PermissionDenied() + } + GET(opt) { + if (authorize(opt.pubKey, 'r', this.stat)) + return StrappFile.get(this.path) + else return StrappFile.PermissionDenied() + } + PUT(opt) { + if (authorize(opt.pubKey, 'w', this.stat)) + return StrappFile.set(this.path, opt.data) + else return StrappFile.PermissionDenied() + } + POST(opt) { + return this.PUT(Object.assign(opt, { data: this.GET(opt) + opt.data })) + } + DELETE(opt) { + if (authorize(opt.pubKey, 'w', this.stat)) + return StrappFile.delete(this.path) + else return StrappFile.PermissionDenied() + } + OPTIONS(opt) { + return this.stat + } + CONNECT(opt) { + return this.GET(opt) + } + TRACE(opt) { + } + PATCH(opt) { + } + } + StrappFile.defaults = { + type: 'mime/type', + perm: 0, + owner: 'thisOwnerPubKey', + group: 'groupname', + changed: 'time', + created: 'time', + accessed: 'time - not saved' + } + return StrappFile +})() diff --git a/strappCrypto.js b/src/strappCrypto.js similarity index 100% rename from strappCrypto.js rename to src/strappCrypto.js diff --git a/strappFileManager.js b/src/strappFileManager.js similarity index 100% rename from strappFileManager.js rename to src/strappFileManager.js diff --git a/strappFileSystem.js b/src/strappFileSystem.js similarity index 100% rename from strappFileSystem.js rename to src/strappFileSystem.js diff --git a/strappPeerConnection.js b/src/strappPeerConnection.js similarity index 100% rename from strappPeerConnection.js rename to src/strappPeerConnection.js diff --git a/strappProtocolManager.js b/src/strappProtocolManager.js similarity index 100% rename from strappProtocolManager.js rename to src/strappProtocolManager.js diff --git a/strapp.js b/strapp.js deleted file mode 100644 index dc95479..0000000 --- a/strapp.js +++ /dev/null @@ -1,27 +0,0 @@ -/** -* @file Node entry and main driver -* @author Jordan Lavatai, Ken Grimes -* @version 0.0.1 -* @license AGPL-3.0 -* @copyright loljk 2017 -* @summ ary HTTP(S) Router that uses the first directory in the requested URL -* as the route name -*/ -const opts = require('./opts.js') -const router = require('./router.js') - -Object.keys(opts['bindings']).forEach((key) => { - router.createBind(key, opts['bindings'][key]) -}) - -router.startHttpServer({ - port: opts['port'], - skelFile: './skel.html', - clientJS: opts['client-js'], - hostJS: opts['host-js'], - httpdRoot: opts['file-dir'], - tls: { - certFile: opts['ca-cert'], - keyFile: opts['ca-key'] - } -}) -- 2.18.0