/** * \file * \defgroup tfapp Tilde Friends App JS * Tilde Friends server-side app wrapper. * @{ */ /** \cond */ import * as core from './core.js'; /** \endcond */ /** A sequence number of apps. */ let g_session_index = 0; /** ** App socket handler. ** @param request The HTTP request of the WebSocket connection. ** @param response The HTTP response. */ exports.app_socket = async function socket(request, response) { let process; 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; 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; } } response.send( JSON.stringify( Object.assign( { action: 'session', credentials: credentials, id: blobId, }, await ssb_internal.getIdentityInfo( credentials?.session?.name, packageOwner, packageName ) ) ), 0x1 ); if (blobId) { if (message.edit_only) { response.send( JSON.stringify({ action: 'ready', version: version(), edit_only: true, }), 0x1 ); } else { let sessionId = 'session_' + (g_session_index++).toString(); let options = { api: message.api || [], credentials: credentials, packageOwner: packageOwner, packageName: packageName, url: message.url, }; 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, {}); }; /** @} */