diff --git a/src/backend/exports.js b/src/backend/exports.js index 0ed27f99..3367710e 100644 --- a/src/backend/exports.js +++ b/src/backend/exports.js @@ -28,6 +28,7 @@ const { Context } = require("./src/util/context.js"); const { TestDriversModule } = require("./src/modules/test-drivers/TestDriversModule.js"); const { PuterAIModule } = require("./src/modules/puterai/PuterAIModule.js"); const { BroadcastModule } = require("./src/modules/broadcast/BroadcastModule.js"); +const { WebModule } = require("./src/modules/web/WebModule.js"); module.exports = { @@ -42,9 +43,15 @@ module.exports = { Context, Kernel, + + EssentialModules: [ + CoreModule, + WebModule, + ], // Pre-built modules CoreModule, + WebModule, DatabaseModule, PuterDriversModule, LocalDiskStorageModule, diff --git a/src/backend/src/CoreModule.js b/src/backend/src/CoreModule.js index a482892e..bd84efbf 100644 --- a/src/backend/src/CoreModule.js +++ b/src/backend/src/CoreModule.js @@ -129,7 +129,6 @@ const install = async ({ services, app, useapi, modapi }) => { const { ConfigurableCountingService } = require('./services/ConfigurableCountingService'); const { FSLockService } = require('./services/fs/FSLockService'); const { StrategizedService } = require('./services/StrategizedService'); - const WebServerService = require('./services/WebServerService'); const FilesystemAPIService = require('./services/FilesystemAPIService'); const ServeGUIService = require('./services/ServeGUIService'); const PuterAPIService = require('./services/PuterAPIService'); @@ -143,7 +142,6 @@ const install = async ({ services, app, useapi, modapi }) => { services.registerService('server-health', ServerHealthService); services.registerService('log-service', LogService); services.registerService('commands', CommandService); - services.registerService('web-server', WebServerService, { app }); services.registerService('__api-filesystem', FilesystemAPIService); services.registerService('__api', PuterAPIService); services.registerService('__gui', ServeGUIService); @@ -354,6 +352,9 @@ const install = async ({ services, app, useapi, modapi }) => { const { UserService } = require('./services/UserService'); services.registerService('user', UserService); + + const { WSPushService } = require('./services/WSPushService'); + services.registerService('__event-push-ws', WSPushService); } const install_legacy = async ({ services }) => { @@ -361,7 +362,6 @@ const install_legacy = async ({ services }) => { // const { FilesystemService } = require('./filesystem/FilesystemService'); const PerformanceMonitor = require('./monitor/PerformanceMonitor'); const { OperationTraceService } = require('./services/OperationTraceService'); - const { WSPushService } = require('./services/WSPushService'); const { ClientOperationService } = require('./services/ClientOperationService'); const { EngPortalService } = require('./services/EngPortalService'); const { AppInformationService } = require('./services/AppInformationService'); @@ -371,7 +371,6 @@ const install_legacy = async ({ services }) => { services.registerService('process-event', ProcessEventService); // services.registerService('filesystem', FilesystemService); services.registerService('operationTrace', OperationTraceService); - services.registerService('__event-push-ws', WSPushService); services.registerService('file-cache', FileCacheService); services.registerService('client-operation', ClientOperationService); services.registerService('app-information', AppInformationService); diff --git a/src/backend/src/filesystem/FilesystemService.js b/src/backend/src/filesystem/FilesystemService.js index 48da46c7..a22b1947 100644 --- a/src/backend/src/filesystem/FilesystemService.js +++ b/src/backend/src/filesystem/FilesystemService.js @@ -44,7 +44,6 @@ class FilesystemService extends BaseService { static MODULES = { _path: require('path'), uuidv4: require('uuid').v4, - socketio: require('../socketio.js'), config: require('../config.js'), } diff --git a/src/backend/src/filesystem/hl_operations/hl_mkdir.js b/src/backend/src/filesystem/hl_operations/hl_mkdir.js index 648c783b..41e656a9 100644 --- a/src/backend/src/filesystem/hl_operations/hl_mkdir.js +++ b/src/backend/src/filesystem/hl_operations/hl_mkdir.js @@ -242,7 +242,6 @@ class HLMkdir extends HLFilesystemOperation { static MODULES = { _path: require('path'), - socketio: require('../../socketio.js'), } static PROPERTIES = { @@ -258,7 +257,7 @@ class HLMkdir extends HLFilesystemOperation { async _run () { const { context, values } = this; - const { _path, socketio } = this.modules; + const { _path } = this.modules; const fs = context.get('services').get('filesystem'); if ( ! is_valid_path(values.path, { diff --git a/src/backend/src/filesystem/hl_operations/hl_write.js b/src/backend/src/filesystem/hl_operations/hl_write.js index 70b5b929..c0f71101 100644 --- a/src/backend/src/filesystem/hl_operations/hl_write.js +++ b/src/backend/src/filesystem/hl_operations/hl_write.js @@ -116,7 +116,6 @@ class HLWrite extends HLFilesystemOperation { static MODULES = { _path: require('path'), - socketio: require('../../socketio.js'), mime: require('mime-types'), } diff --git a/src/backend/src/modules/web/SocketioService.js b/src/backend/src/modules/web/SocketioService.js new file mode 100644 index 00000000..1f98a10c --- /dev/null +++ b/src/backend/src/modules/web/SocketioService.js @@ -0,0 +1,51 @@ +const BaseService = require('../../services/BaseService'); + +class SocketioService extends BaseService { + static MODULES = { + socketio: require('socket.io'), + }; + + ['__on_install.socketio'] (_, { server }) { + const require = this.require; + + const socketio = require('socket.io'); + /** + * @type {import('socket.io').Server} + */ + this.io = socketio(server, { + cors: { + origin: '*', + } + }); + } + + async send (socket_specifiers, key, data) { + const svc_getUser = this.services.get('get-user'); + + if ( ! Array.isArray(socket_specifiers) ) { + socket_specifiers = [socket_specifiers]; + } + + for ( const socket_specifier of socket_specifiers ) { + if ( socket_specifier.room ) { + this.io.to(socket_specifier.room).emit(key, data); + } else if ( socket_specifier.socket ) { + const io = this.io.sockets.sockets.get(socket_specifier.socket) + if ( ! io ) continue; + io.emit(key, data); + } + } + } + + has (socket_specifier) { + if ( socket_specifier.room ) { + const room = this.io.sockets.adapter.rooms.get(socket_specifier.room); + return (!!room) && room.size > 0; + } + if ( socket_specifier.socket ) { + return this.io.sockets.sockets.has(socket_specifier.socket); + } + } +} + +module.exports = SocketioService; diff --git a/src/backend/src/modules/web/WebModule.js b/src/backend/src/modules/web/WebModule.js new file mode 100644 index 00000000..e7d3b166 --- /dev/null +++ b/src/backend/src/modules/web/WebModule.js @@ -0,0 +1,17 @@ +const { AdvancedBase } = require("@heyputer/putility"); + +class WebModule extends AdvancedBase { + async install (context) { + const services = context.get('services'); + + const SocketioService = require("./SocketioService"); + services.registerService('socketio', SocketioService); + + const WebServerService = require("./WebServerService"); + services.registerService('web-server', WebServerService); + } +} + +module.exports = { + WebModule, +}; diff --git a/src/backend/src/services/WebServerService.js b/src/backend/src/modules/web/WebServerService.js similarity index 95% rename from src/backend/src/services/WebServerService.js rename to src/backend/src/modules/web/WebServerService.js index a0f7fd93..a9fd059a 100644 --- a/src/backend/src/services/WebServerService.js +++ b/src/backend/src/modules/web/WebServerService.js @@ -18,18 +18,19 @@ * along with this program. If not, see . */ const express = require('express'); -const eggspress = require("../api/eggspress"); -const { Context, ContextExpressMiddleware } = require("../util/context"); -const BaseService = require("./BaseService"); +const eggspress = require("../../api/eggspress.js"); +const { Context, ContextExpressMiddleware } = require("../../util/context.js"); +const BaseService = require("../../services/BaseService.js"); -const config = require('../config'); +const config = require('../../config.js'); const https = require('https') var http = require('http'); const fs = require('fs'); -const auth = require('../middleware/auth'); -const { osclink } = require('../util/strutil'); -const { surrounding_box, es_import_promise } = require('../fun/dev-console-ui-utils'); +const auth = require('../../middleware/auth.js'); +const { osclink } = require('../../util/strutil.js'); +const { surrounding_box, es_import_promise } = require('../../fun/dev-console-ui-utils.js'); +const relative_require = require; /** * This class, WebServerService, is responsible for starting and managing the Puter web server. @@ -103,9 +104,9 @@ class WebServerService extends BaseService { // error handling middleware goes last, as per the // expressjs documentation: // https://expressjs.com/en/guide/error-handling.html - this.app.use(require('../api/api_error_handler')); + this.app.use(require('../../api/api_error_handler.js')); - const { jwt_auth } = require('../helpers'); + const { jwt_auth } = require('../../helpers.js'); config.http_port = process.env.PORT ?? config.http_port; @@ -224,7 +225,11 @@ class WebServerService extends BaseService { // server.keepAliveTimeout = 1000 * 60 * 60 * 2; // 2 hours // Socket.io server instance - const socketio = require('../socketio.js').init(server); + // const socketio = require('../../socketio.js').init(server); + + // TODO: ^ Replace above line with the following code: + await this.services.emit('install.socketio', { server }); + const socketio = this.services.get('socketio').io; // Socket.io middleware for authentication socketio.use(async (socket, next) => { @@ -305,13 +310,13 @@ class WebServerService extends BaseService { const require = this.require; - + const config = this.global_config; new ContextExpressMiddleware({ parent: globalThis.root_context.sub({ puter_environment: Context.create({ env: config.env, - version: require('../../package.json').version, + version: relative_require('../../../package.json').version, }), }, 'mw') }).install(app); @@ -580,6 +585,8 @@ class WebServerService extends BaseService { app.options('/*', (_, res) => { return res.sendStatus(200); }); + + console.log('WEB SERVER INIT DONE'); } _register_commands (commands) { @@ -611,7 +618,7 @@ class WebServerService extends BaseService { // comment above line 497 print_puter_logo_() { if ( this.global_config.env !== 'dev' ) return; - const logos = require('../fun/logos.js'); + const logos = require('../../fun/logos.js'); let last_logo = undefined; for ( const logo of logos ) { if ( logo.sz <= (process.stdout.columns ?? 0) ) { diff --git a/src/backend/src/routers/_default.js b/src/backend/src/routers/_default.js index 2ec82d5e..f9813989 100644 --- a/src/backend/src/routers/_default.js +++ b/src/backend/src/routers/_default.js @@ -289,10 +289,8 @@ router.all('*', async function(req, res, next) { invalidate_cached_user(user); // send realtime success msg to client - let socketio = require('../socketio.js').getio(); - if(socketio){ - socketio.to(user.id).emit('user.email_confirmed', {}) - } + const svc_socketio = req.services.get('socketio'); + svc_socketio.send({ room: user.id }, 'user.email_confirmed', {}); // return results h += `

Your email has been successfully confirmed.

`; diff --git a/src/backend/src/routers/change_email.js b/src/backend/src/routers/change_email.js index 328dea99..52870eba 100644 --- a/src/backend/src/routers/change_email.js +++ b/src/backend/src/routers/change_email.js @@ -83,10 +83,8 @@ const CHANGE_EMAIL_CONFIRM = eggspress('/change_email/confirm', { }); invalidate_cached_user_by_id(user_id); - let socketio = require('../socketio.js').getio(); - if(socketio){ - socketio.to(user_id).emit('user.email_changed', {}) - } + const svc_socketio = req.services.get('socketio'); + svc_socketio.send({ room: user_id }, 'user.email_changed', {}); const h = `

Your email has been successfully confirmed.

`; return res.send(h); diff --git a/src/backend/src/routers/confirm-email.js b/src/backend/src/routers/confirm-email.js index d8269e2b..922e71d6 100644 --- a/src/backend/src/routers/confirm-email.js +++ b/src/backend/src/routers/confirm-email.js @@ -87,14 +87,14 @@ router.post('/confirm-email', auth, express.json(), async (req, res, next)=>{ // Send realtime success msg to client if(req.body.code === req.user.email_confirm_code){ - let socketio = require('../socketio.js').getio(); - if(socketio){ - socketio.to(req.user.id).emit('user.email_confirmed', {original_client_socket_id: req.body.original_client_socket_id}) - } + const svc_socketio = req.services.get('socketio'); + svc_socketio.send({ room: req.user.id }, 'user.email_confirmed', { + original_client_socket_id: req.body.original_client_socket_id + }); } // return results return res.send(res_obj) }) -module.exports = router \ No newline at end of file +module.exports = router diff --git a/src/backend/src/routers/filesystem_api/delete.js b/src/backend/src/routers/filesystem_api/delete.js index 076ff79a..d680d806 100644 --- a/src/backend/src/routers/filesystem_api/delete.js +++ b/src/backend/src/routers/filesystem_api/delete.js @@ -48,8 +48,6 @@ module.exports = eggspress('/delete', { else if(paths.length === 0) return res.status(400).send('paths cannot be empty') - const socketio = require('../../socketio.js').getio(); - // try to delete each path in the array one by one (if glob, resolve first) // TODO: remove this pseudo-batch for(let j=0; j < paths.length; j++){ @@ -67,9 +65,11 @@ module.exports = eggspress('/delete', { }); // send realtime success msg to client - if(socketio){ - socketio.to(req.user.id).emit('item.removed', {path: item_path, descendants_only: descendants_only}) - } + const svc_socketio = req.services.get('socketio'); + svc_socketio.send({ room: req.user.id }, 'item.removed', { + path: item_path, + descendants_only: descendants_only, + }); } res.send({}); diff --git a/src/backend/src/routers/filesystem_api/rename.js b/src/backend/src/routers/filesystem_api/rename.js index b2632178..ac416e8e 100644 --- a/src/backend/src/routers/filesystem_api/rename.js +++ b/src/backend/src/routers/filesystem_api/rename.js @@ -174,10 +174,8 @@ module.exports = eggspress('/rename', { }; // send realtime success msg to client - let socketio = require('../../socketio.js').getio(); - if(socketio){ - socketio.to(req.user.id).emit('item.renamed', return_obj) - } + const svc_socketio = req.services.get('socketio'); + svc_socketio.send({ room: req.user.id }, 'item.renamed', return_obj); return res.send(return_obj); }); diff --git a/src/backend/src/routers/rao.js b/src/backend/src/routers/rao.js index 0dfb8f59..68b1aaca 100644 --- a/src/backend/src/routers/rao.js +++ b/src/backend/src/routers/rao.js @@ -90,8 +90,8 @@ router.post('/rao', auth, express.json(), async (req, res, next)=>{ } // Update clients - const socketio = require('../socketio.js').getio(); - socketio.to(req.user.id).emit('app.opened', { + const svc_socketio = req.services.get('socketio'); + svc_socketio.send({ room: req.user.id }, 'app.opened', { uuid: opened_app.uid, uid: opened_app.uid, name: opened_app.name, diff --git a/src/backend/src/routers/writeFile.js b/src/backend/src/routers/writeFile.js index b913a925..02a78d8d 100644 --- a/src/backend/src/routers/writeFile.js +++ b/src/backend/src/routers/writeFile.js @@ -457,10 +457,8 @@ module.exports = eggspress('/writeFile', { }; // send realtime success msg to client - let socketio = require('../socketio.js').getio(); - if(socketio){ - socketio.to(fsentry.user_id).emit('item.renamed', return_obj) - } + const svc_socketio = req.services.get('socketio'); + svc_socketio.send({ room: req.user.id }, 'item.renamed', return_obj); return res.send(return_obj); } diff --git a/src/backend/src/services/EngPortalService.js b/src/backend/src/services/EngPortalService.js index 478e83c1..54859429 100644 --- a/src/backend/src/services/EngPortalService.js +++ b/src/backend/src/services/EngPortalService.js @@ -32,7 +32,6 @@ const { AdvancedBase } = require("@heyputer/putility"); */ class EngPortalService extends AdvancedBase { static MODULES = { - socketio: require('../socketio.js'), uuidv4: require('uuid').v4, }; diff --git a/src/backend/src/services/WSPushService.js b/src/backend/src/services/WSPushService.js index 6fb0c669..7b92cc72 100644 --- a/src/backend/src/services/WSPushService.js +++ b/src/backend/src/services/WSPushService.js @@ -17,17 +17,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); - -class WSPushService extends AdvancedBase { - static MODULES = { - socketio: require('../socketio.js'), - } - - constructor ({ services }) { - super(); - this.log = services.get('log-service').create('WSPushService'); - this.svc_event = services.get('event'); +const BaseService = require("./BaseService"); +class WSPushService extends BaseService { + async _init () { + this.svc_event = this.services.get('event'); this.svc_event.on('fs.create.*', this._on_fs_create.bind(this)); this.svc_event.on('fs.write.*', this._on_fs_update.bind(this)); @@ -50,7 +43,6 @@ class WSPushService extends AdvancedBase { */ async _on_fs_create (key, data) { const { node, context } = data; - const { socketio } = this.modules; const metadata = { from_new_service: true, @@ -111,7 +103,6 @@ class WSPushService extends AdvancedBase { */ async _on_fs_update (key, data) { const { node, context } = data; - const { socketio } = this.modules; const metadata = { from_new_service: true, @@ -177,7 +168,6 @@ class WSPushService extends AdvancedBase { */ async _on_fs_move (key, data) { const { moved, old_path, context } = data; - const { socketio } = this.modules; const metadata = { from_new_service: true, @@ -235,7 +225,6 @@ class WSPushService extends AdvancedBase { */ async _on_fs_pending (key, data) { const { fsentry, context } = data; - const { socketio } = this.modules; const metadata = { from_new_service: true, @@ -292,7 +281,6 @@ class WSPushService extends AdvancedBase { */ async _on_upload_progress (key, data) { this.log.info('got upload progress event'); - const { socketio } = this.modules; const { upload_tracker, context, meta } = data; const metadata = { @@ -320,25 +308,23 @@ class WSPushService extends AdvancedBase { } this.log.info('socket id: ' + socket_id); - - const io = socketio.getio() - .sockets.sockets - .get(socket_id); - - // socket disconnected; that's allowed - if ( ! io ) return; + + const svc_socketio = context.get('services').get('socketio'); + if ( ! svc_socketio.has({ socket: socket_id }) ) { + return; + } const ws_event_name = metadata.call_it_download ? 'download.progress' : 'upload.progress' ; upload_tracker.sub(delta => { this.log.info('emitting progress event'); - io.emit(ws_event_name, { + svc_socketio.send({ socket: socket_id }, ws_event_name, { ...metadata, total: upload_tracker.total_, loaded: upload_tracker.progress_, loaded_diff: delta, - }) + }); }) } @@ -357,16 +343,13 @@ class WSPushService extends AdvancedBase { async _on_outer_gui (key, { user_id_list, response }, meta) { key = key.slice('outer.gui.'.length); - const { socketio } = this.modules; - - const io = socketio.getio(); + const svc_socketio = this.services.get('socketio'); for ( const user_id of user_id_list ) { - const room = io.sockets.adapter.rooms.get(user_id); - if ( ! room || room.size <= 0 ) { + if ( ! svc_socketio.has({ room: user_id }) ) { continue; } - io.to(user_id).emit(key, response); + svc_socketio.send({ room: user_id }, key, response); this.svc_event.emit(`sent-to-user.${key}`, { user_id, response, diff --git a/src/backend/src/services/web/UserProtectedEndpointsService.js b/src/backend/src/services/web/UserProtectedEndpointsService.js index ea108248..9ffa5e74 100644 --- a/src/backend/src/services/web/UserProtectedEndpointsService.js +++ b/src/backend/src/services/web/UserProtectedEndpointsService.js @@ -25,11 +25,6 @@ const { UserActorType } = require("../auth/Actor"); const { Endpoint } = require("../../util/expressutil"); const APIError = require("../../api/APIError.js"); -/** - * This service registers endpoints that are protected by password authentication, - * excluding login. These endpoints are typically for actions that affect - * security settings on the user's account. - */ /** * @class UserProtectedEndpointsService * @extends BaseService diff --git a/src/backend/src/socketio.js b/src/backend/src/socketio.js deleted file mode 100644 index 8b86a419..00000000 --- a/src/backend/src/socketio.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2024 Puter Technologies Inc. - * - * This file is part of Puter. - * - * Puter is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -let io; -module.exports = { - init: function(server) { - // start socket.io server and cache io value - io = require('socket.io')(server, { - cors: { - origin: '*', - } - }); - // io.origins('*:*'); - return io; - }, - getio: function() { - // return previously cached value - if (!io) { - throw new Error("must call .init(server) before you can call .getio()"); - } - return io; - } -} \ No newline at end of file diff --git a/tools/run-selfhosted.js b/tools/run-selfhosted.js index 9253b21e..8334be6f 100644 --- a/tools/run-selfhosted.js +++ b/tools/run-selfhosted.js @@ -80,7 +80,7 @@ if ( ! import.meta.filename ) { const main = async () => { const { Kernel, - CoreModule, + EssentialModules, DatabaseModule, LocalDiskStorageModule, SelfHostedModule, @@ -92,7 +92,9 @@ const main = async () => { const k = new Kernel({ entry_path: import.meta.filename }); - k.add_module(new CoreModule()); + for ( const mod of EssentialModules ) { + k.add_module(new mod()); + } k.add_module(new DatabaseModule()); k.add_module(new LocalDiskStorageModule()); k.add_module(new SelfHostedModule());