import * as core from './core.js'; let gSessionIndex = 0; function App() { this._send_queue = []; this.calls = {}; this._next_call_id = 1; return this; } App.prototype.makeFunction = function (api) { let self = this; let result = function () { let id = self._next_call_id++; while (!id || self.calls[id]) { id = self._next_call_id++; } let promise = new Promise(function (resolve, reject) { self.calls[id] = {resolve: resolve, reject: reject}; }); let message = { action: 'tfrpc', method: api[0], params: [...arguments], id: id, }; self.send(message); return promise; }; Object.defineProperty(result, 'name', {value: api[0], writable: false}); return result; }; App.prototype.send = function (message) { if (this._send_queue) { if (this._on_output) { this._send_queue.forEach((x) => this._on_output(x)); this._send_queue = null; } else if (message) { this._send_queue.push(message); } } if (message && this._on_output) { this._on_output(message); } }; exports.app_socket = async function socket(request, response) { let process; let options = {}; let credentials = await httpd.auth_query(request.headers); response.onClose = async function () { if (process && process.task) { process.task.kill(); } if (process) { process.timeout = 0; } }; response.onMessage = async function (event) { if (event.opCode == 0x1 || event.opCode == 0x2) { let message; try { message = JSON.parse(event.data); } catch (error) { print('WebSocket error:', error, event.data, event.data.length, event.opCode); return; } if (!process && message.action == 'hello') { let packageOwner; let packageName; let blobId; let match; let parentApp; if ( (match = /^\/([&%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(message.path)) ) { blobId = match[1]; } else if ((match = /^\/\~([^\/]+)\/([^\/]+)\/$/.exec(message.path))) { packageOwner = match[1]; packageName = match[2]; blobId = await new Database(packageOwner).get('path:' + packageName); if (!blobId) { response.send( JSON.stringify({ action: 'tfrpc', method: 'error', params: [message.path + ' not found'], id: -1, }), 0x1 ); return; } if (packageOwner != 'core') { let coreId = await new Database('core').get('path:' + packageName); parentApp = { path: '/~core/' + packageName + '/', id: coreId, }; } } response.send( JSON.stringify( Object.assign( { action: 'session', credentials: credentials, parentApp: parentApp, id: blobId, }, await ssb.getIdentityInfo( credentials?.session?.name, packageOwner, packageName ) ) ), 0x1 ); options.api = message.api || []; options.credentials = credentials; options.packageOwner = packageOwner; options.packageName = packageName; options.url = message.url; let sessionId = 'session_' + (gSessionIndex++).toString(); if (blobId) { if (message.edit_only) { response.send( JSON.stringify({action: 'ready', edit_only: true}), 0x1 ); } else { process = await core.getProcessBlob(blobId, sessionId, options); } } if (process) { process.client_api.tfrpc = function(message) { if (message.id) { let calls = process?.app?.calls; if (calls) { let call = calls[message.id]; if (call) { if (message.error !== undefined) { call.reject(message.error); } else { call.resolve(message.result); } delete calls[message.id]; } } } }; process.app._on_output = (message) => response.send(JSON.stringify(message), 0x1); process.app.send(); } let ping = function () { let now = Date.now(); let again = true; if (now - process.lastActive < process.timeout) { // Active. } else if (process.lastPing > process.lastActive) { // We lost them. if (process.task) { process.task.kill(); } again = false; } else { // Idle. Ping them. response.send('', 0x9); process.lastPing = now; } if (again && process.timeout) { setTimeout(ping, process.timeout); } }; if (process && process.timeout > 0) { setTimeout(ping, process.timeout); } } else { if (process) { if (process.client_api[message.action]) { process.client_api[message.action](message); } else if (process.eventHandlers['message']) { await core.invoke(process.eventHandlers['message'], [message]); } } } } else if (event.opCode == 0x8) { // Close. if (process && process.task) { process.task.kill(); } response.send(event.data, 0x8); } else if (event.opCode == 0xa) { // PONG } if (process) { process.lastActive = Date.now(); } }; response.upgrade(100, {}); }; export {App};