diff --git a/src/gui/src/IPC.js b/src/gui/src/IPC.js index 6e7234e4..7d4823e8 100644 --- a/src/gui/src/IPC.js +++ b/src/gui/src/IPC.js @@ -44,12 +44,12 @@ window.ipc_handlers = {}; * * Precautions are taken to ensure proper usage of appInstanceIDs and other sensitive information. */ -window.addEventListener('message', async (event) => { +const ipc_listener = async (event, handled) => { const app_env = event.data?.env ?? 'app'; // Only process messages from apps if(app_env !== 'app') - return; + return handled.resolve(false); // -------------------------------------------------------- // A response to a GUI message received from the app. @@ -61,7 +61,7 @@ window.addEventListener('message', async (event) => { delete window.appCallbackFunctions[event.data.original_msg_id]; // Done - return; + return handled.resolve(false); } // -------------------------------------------------------- @@ -70,18 +70,20 @@ window.addEventListener('message', async (event) => { // `data` and `msg` are required if(!event.data || !event.data.msg){ - return; + return handled.resolve(false); } // `appInstanceID` is required if(!event.data.appInstanceID){ console.error(`appInstanceID is needed`); - return; + return handled.resolve(false); }else if(!window.app_instance_ids.has(event.data.appInstanceID)){ console.error(`appInstanceID is invalid`); - return; + return handled.resolve(false); } + handled.resolve(true); + const $el_parent_window = $(window.window_for_app_instance(event.data.appInstanceID)); const parent_window_id = $el_parent_window.attr('data-id'); const $el_parent_disable_mask = $el_parent_window.find('.window-disable-mask'); @@ -1522,4 +1524,11 @@ window.addEventListener('message', async (event) => { status_code, }); } +}; + +if ( ! window.when_puter_happens ) window.when_puter_happens = []; +window.when_puter_happens.push(async () => { + await puter.services.wait_for_init(['xd-incoming']); + const svc_xdIncoming = puter.services.get('xd-incoming'); + svc_xdIncoming.register_filter_listener(ipc_listener); }); diff --git a/src/puter-js/src/index.js b/src/puter-js/src/index.js index 5dc97795..a7d2f126 100644 --- a/src/puter-js/src/index.js +++ b/src/puter-js/src/index.js @@ -12,6 +12,12 @@ import * as utils from './lib/utils.js'; import path from './lib/path.js'; import Util from './modules/Util.js'; import Drivers from './modules/Drivers.js'; +import putility from '@heyputer/putility'; +import { FSHostService } from './services/FSHost.js'; +import { FilesystemService } from './services/Filesystem.js'; +import { APIAccessService } from './services/APIAccess.js'; +import { XDIncomingService } from './services/XDIncoming.js'; +import { NoPuterYetService } from './services/NoPuterYet.js'; window.puter = (function() { 'use strict'; @@ -60,7 +66,12 @@ window.puter = (function() { constructor(options) { options = options ?? {}; + // "modules" in puter.js are external interfaces for the developer this.modules_ = []; + // "services" in puter.js are used by modules and may interact with each other + const context = new putility.libs.context.Context(); + this.services = new putility.system.ServiceManager({ context }); + context.services = this.services; // Holds the query parameters found in the current URL let URLParams = new URLSearchParams(window.location.search); @@ -137,6 +148,35 @@ window.puter = (function() { this.APIOrigin = 'https://api.' + URLParams.get('puter.domain'); } + if ( this.env !== 'app' ) { + this.services.register('no-puter-yet', NoPuterYetService); + this.services.register('filesystem', FilesystemService); + this.services.register('api-access', APIAccessService); + this.services.register('xd-incoming', XDIncomingService); + // this.services.register('fs-host', FSHostService); + } + + // When api-access is initialized, bind `.authToken` and + // `.APIOrigin` as a 1-1 mapping with the `puter` global + (async () => { + await this.services.wait_for_init(['api-access']); + const svc_apiAccess = this.services.get('api-access'); + + svc_apiAccess.authToken = this.authToken; + svc_apiAccess.APIOrigin = this.APIOrigin; + ['authToken', 'APIOrigin'].forEach(key => { + Object.defineProperty(this, key, { + get () { + return svc_apiAccess[key]; + }, + set (v) { + svc_apiAccess[key] = v; + return true; + } + }); + }); + })(); + // The SDK is running in the Puter GUI (i.e. 'gui') if(this.env === 'gui'){ this.authToken = window.auth_token; diff --git a/src/puter-js/src/services/APIAccess.js b/src/puter-js/src/services/APIAccess.js new file mode 100644 index 00000000..bceaf818 --- /dev/null +++ b/src/puter-js/src/services/APIAccess.js @@ -0,0 +1,24 @@ +import putility from "@heyputer/putility"; + +const { TTopics } = putility.traits; + +/** + * Manages the auth token and origin used to communicate with + * Puter's API + */ +export class APIAccessService extends putility.concepts.Service { + static TOPICS = ['update']; + + static PROPERTIES = { + auth_token: { + post_set () { + this.as(TTopics).pub('update'); + } + }, + api_origin: { + post_set () { + this.as(TTopics).pub('update'); + } + }, + }; +} diff --git a/src/puter-js/src/services/FSHost.js b/src/puter-js/src/services/FSHost.js new file mode 100644 index 00000000..a9aeccde --- /dev/null +++ b/src/puter-js/src/services/FSHost.js @@ -0,0 +1,7 @@ +import putility from '@heyputer/putility'; + +export class FSHostService extends putility.concepts.Service { + async _init () { + // + } +} diff --git a/src/puter-js/src/services/Filesystem.js b/src/puter-js/src/services/Filesystem.js new file mode 100644 index 00000000..0c30e805 --- /dev/null +++ b/src/puter-js/src/services/Filesystem.js @@ -0,0 +1,80 @@ +import putility from "@heyputer/putility"; + +export class FilesystemService extends putility.concepts.Service { + static PROPERTIES = { + // filesystem: + }; + + static DEPENDS = ['api-access']; + static HOOKS = [ + { + service: 'api-access', + event: 'update', + description: ` + re-initialize the socket connection whenever the + authentication token or API origin is changed. + `, + async do () { + console.log('do() was called'); + this.initializeSocket(); + } + } + ] + + _init () { + console.log('does this init get called'); + this.initializeSocket(); + } + + initializeSocket () { + console.log('THIS IS RUNNING'); + if (this.socket) { + this.socket.disconnect(); + } + + this.socket = io(this.APIOrigin, { + auth: { + auth_token: this.authToken, + } + }); + + this.bindSocketEvents(); + } + + bindSocketEvents() { + this.socket.on('connect', () => { + if(puter.debugMode) + console.log('FileSystem Socket: Connected', this.socket.id); + }); + + this.socket.on('disconnect', () => { + if(puter.debugMode) + console.log('FileSystem Socket: Disconnected'); + }); + + this.socket.on('reconnect', (attempt) => { + if(puter.debugMode) + console.log('FileSystem Socket: Reconnected', this.socket.id); + }); + + this.socket.on('reconnect_attempt', (attempt) => { + if(puter.debugMode) + console.log('FileSystem Socket: Reconnection Attemps', attempt); + }); + + this.socket.on('reconnect_error', (error) => { + if(puter.debugMode) + console.log('FileSystem Socket: Reconnection Error', error); + }); + + this.socket.on('reconnect_failed', () => { + if(puter.debugMode) + console.log('FileSystem Socket: Reconnection Failed'); + }); + + this.socket.on('error', (error) => { + if(puter.debugMode) + console.error('FileSystem Socket Error:', error); + }); + } +} diff --git a/src/puter-js/src/services/NoPuterYet.js b/src/puter-js/src/services/NoPuterYet.js new file mode 100644 index 00000000..004a4720 --- /dev/null +++ b/src/puter-js/src/services/NoPuterYet.js @@ -0,0 +1,20 @@ +import putility from "@heyputer/putility"; + +/** + * Runs commands on the special `window.when_puter_happens` global, for + * situations where the `puter` global doesn't exist soon enough. + */ +export class NoPuterYetService extends putility.concepts.Service { + _init () { + if ( ! window.when_puter_happens ) return; + if ( puter && puter.env !== 'gui' ) return; + + if ( ! Array.isArray(window.when_puter_happens) ) { + window.when_puter_happens = [window.when_puter_happens]; + } + + for ( const fn of window.when_puter_happens ) { + fn({ context: this._.context }); + } + } +} diff --git a/src/puter-js/src/services/XDIncoming.js b/src/puter-js/src/services/XDIncoming.js new file mode 100644 index 00000000..ce5833ea --- /dev/null +++ b/src/puter-js/src/services/XDIncoming.js @@ -0,0 +1,27 @@ +import putility from "@heyputer/putility"; + +const TeePromise = putility.libs.promise.TeePromise; + +/** + * Manages message events from the window object. + */ +export class XDIncomingService extends putility.concepts.Service { + _construct () { + this.filter_listeners_ = []; + this.tagged_listeners_ = {}; + } + + _init () { + window.addEventListener('message', async event => { + for ( const fn of this.filter_listeners_ ) { + const tp = new TeePromise(); + fn(event, tp); + if ( await tp ) return; + } + }); + } + + register_filter_listener (fn) { + this.filter_listeners_.push(fn); + } +}