mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-25 23:46:51 +00:00
refactor(gui): remove the Services system, flatten into plain singleton modules
The GUI had a mini service framework in src/gui/src/services/: a `Service` base class (definitions.js) with `construct`/`init`/`__on`/`context` magic, a `globalThis.services` string-keyed registry with a two-phase construct→init lifecycle and an `emit`/`__on_*` event bus, plus a frontend↔backend `service_script` bridge for registering extra services from the backend. Nothing used the extension bridge, and the framework added indirection over what are really just nine long-lived singletons. This flattens all of it: - Move the nine services to src/gui/src/modules/ as plain classes exporting a singleton (process, exec, ipc, broadcast, theme, debug, locale, anti-csrf). `__launch-on-init` becomes a plain `launch_on_init(gui_params)` function since nothing else references it. Method bodies are unchanged. - Cross-service access is now direct ES-module imports instead of `globalThis.services.get(name)`. No base class, no registry, no lifecycle phases, no event bus. The single `gui:ready` event becomes a direct `process_service.on_gui_ready()` call from UIDesktop. - launch_services() now just calls init() on the four modules that need runtime setup (exec/debug/theme/process), then runs launch_on_init. All singletons are constructed at import, so construct-before-init ordering is preserved. - Remove the `Service` base class from definitions.js. - Remove the unused `service_script` bridge from PuterHomepageService (the `window.service_script`/`service_script_api_promise` shim, `registerScript`, `#serviceScripts`, and the `serviceScriptTags` injection point). Verified: webpack GUI build and backend tsc both clean; homepage controller tests pass; booted the app and exercised the desktop, launching an SDK app (process registration) and the Task Manager (process tree) with no console errors.
This commit is contained in:
@@ -68,8 +68,6 @@ interface PuterGuiAddonsEvent {
|
||||
* Serves the root HTML shell that bootstraps the Puter GUI.
|
||||
*
|
||||
* Extensions contribute by:
|
||||
* - `registerScript(url)` — adds a `<script type="module" src=...>` tag
|
||||
* after the GUI init script.
|
||||
* - `setGuiParam(key, value)` — injects a value into the client-side
|
||||
* `gui()` call.
|
||||
* - Listening for `puter.gui.addons` — mutate the event object's
|
||||
@@ -78,7 +76,6 @@ interface PuterGuiAddonsEvent {
|
||||
*/
|
||||
export class PuterHomepageService extends PuterService {
|
||||
#manifest: Manifest | null = null;
|
||||
#serviceScripts: string[] = [];
|
||||
#guiParams: Record<string, unknown> = {};
|
||||
|
||||
override async onServerStart(): Promise<void> {
|
||||
@@ -102,10 +99,6 @@ export class PuterHomepageService extends PuterService {
|
||||
}
|
||||
}
|
||||
|
||||
registerScript(url: string): void {
|
||||
this.#serviceScripts.push(url);
|
||||
}
|
||||
|
||||
setGuiParam(key: string, val: unknown): void {
|
||||
this.#guiParams[key] = val;
|
||||
}
|
||||
@@ -253,10 +246,6 @@ export class PuterHomepageService extends PuterService {
|
||||
.join('\n')
|
||||
: '';
|
||||
|
||||
const serviceScriptTags = this.#serviceScripts
|
||||
.map((url) => `<script type="module" src="${url}"></script>`)
|
||||
.join('\n');
|
||||
|
||||
const guiParamsJson = JSON.stringify(guiParams).replace(
|
||||
/</g,
|
||||
'\\u003c',
|
||||
@@ -310,22 +299,6 @@ export class PuterHomepageService extends PuterService {
|
||||
|
||||
<link rel="preload" as="image" href="https://puter-assets.b-cdn.net/wallpaper.webp">
|
||||
|
||||
<script>
|
||||
if ( ! window.service_script ) {
|
||||
window.service_script_api_promise = (() => {
|
||||
let resolve, reject;
|
||||
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
||||
promise.resolve = resolve;
|
||||
promise.reject = reject;
|
||||
return promise;
|
||||
})();
|
||||
window.service_script = async fn => {
|
||||
try { await fn(await window.service_script_api_promise); }
|
||||
catch (e) { console.error('service_script(ERROR)', e); }
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
${manifestCss}
|
||||
|
||||
${event.headContent}
|
||||
@@ -341,7 +314,6 @@ export class PuterHomepageService extends PuterService {
|
||||
gui(${guiParamsJson});
|
||||
});
|
||||
</script>
|
||||
${serviceScriptTags}
|
||||
<div id="templates" style="display: none;"></div>
|
||||
|
||||
${event.bodyContent}
|
||||
|
||||
+5
-11
@@ -37,6 +37,8 @@ import UIWindowSignup from './UI/UIWindowSignup.js';
|
||||
import UINotification from './UI/UINotification.js';
|
||||
|
||||
import { PROCESS_IPC_ATTACHED } from './definitions.js';
|
||||
import { process_service } from './modules/process.js';
|
||||
import { ipc_service } from './modules/ipc.js';
|
||||
import TeePromise from './util/TeePromise.js';
|
||||
|
||||
window.ipc_handlers = {};
|
||||
@@ -113,9 +115,6 @@ const ipc_listener = async (event, handled) => {
|
||||
// New IPC handlers should be registered here.
|
||||
// Do this by calling `register_ipc_handler` of IPCService.
|
||||
if ( window.ipc_handlers.hasOwnProperty(event.data.msg) ) {
|
||||
const services = globalThis.services;
|
||||
const svc_process = services.get('process');
|
||||
|
||||
// Add version info to old puter.js messages
|
||||
// (and coerce them into the format of new ones)
|
||||
if ( event.data.$ === undefined ) {
|
||||
@@ -130,7 +129,7 @@ const ipc_listener = async (event, handled) => {
|
||||
|
||||
// The IPC context contains information about the call
|
||||
const iframe = window.iframe_for_app_instance(event.data.appInstanceID);
|
||||
const process = svc_process.get_by_uuid(event.data.appInstanceID);
|
||||
const process = process_service.get_by_uuid(event.data.appInstanceID);
|
||||
const ipc_context = {
|
||||
caller: {
|
||||
process: process,
|
||||
@@ -162,9 +161,7 @@ const ipc_listener = async (event, handled) => {
|
||||
// READY
|
||||
//-------------------------------------------------
|
||||
if ( event.data.msg === 'READY' ) {
|
||||
const services = globalThis.services;
|
||||
const svc_process = services.get('process');
|
||||
const process = svc_process.get_by_uuid(event.data.appInstanceID);
|
||||
const process = process_service.get_by_uuid(event.data.appInstanceID);
|
||||
|
||||
process.ipc_status = PROCESS_IPC_ATTACHED;
|
||||
}
|
||||
@@ -1888,10 +1885,7 @@ const ipc_listener = async (event, handled) => {
|
||||
const { appInstanceID, targetAppInstanceID, targetAppOrigin, contents } = event.data;
|
||||
// TODO: Determine if we should allow the message
|
||||
// TODO: Track message traffic between apps
|
||||
const svc_ipc = globalThis.services.get('ipc');
|
||||
// const svc_exec = globalThis.services()
|
||||
|
||||
const conn = svc_ipc.get_connection(targetAppInstanceID);
|
||||
const conn = ipc_service.get_connection(targetAppInstanceID);
|
||||
if ( conn ) {
|
||||
conn.send(contents);
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
import path from '../lib/path.js';
|
||||
import { process_service } from '../modules/process.js';
|
||||
|
||||
import UIContextMenu from './UIContextMenu.js';
|
||||
import UIItem from './UIItem.js';
|
||||
@@ -1262,7 +1263,7 @@ async function UIDesktop (options) {
|
||||
// GUI is ready to launch apps!
|
||||
//-----------------------------
|
||||
window.dispatchEvent(new CustomEvent('desktop:ready'));
|
||||
globalThis.services.emit('gui:ready');
|
||||
process_service.on_gui_ready();
|
||||
|
||||
//--------------------------------------------------------
|
||||
// Open the AI app (best-effort — the `ai` app isn't seeded
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { anti_csrf_service } from '../modules/anti-csrf.js';
|
||||
|
||||
// Renders the Session Manager as a self-contained, responsive modal (a plain
|
||||
// DOM overlay) rather than a draggable UIWindow. Confirmations are shown as
|
||||
// in-modal sheets instead of UIAlert windows, so nothing here depends on the
|
||||
@@ -94,8 +96,6 @@ const kindBadgeLabel = (kind) => {
|
||||
const UIWindowManageSessions = async function UIWindowManageSessions (options) {
|
||||
options = options ?? {};
|
||||
|
||||
const services = globalThis.services;
|
||||
|
||||
// =====================================================================
|
||||
// Responsive modal shell
|
||||
// =====================================================================
|
||||
@@ -450,7 +450,7 @@ const UIWindowManageSessions = async function UIWindowManageSessions (options) {
|
||||
session.label = next || null;
|
||||
el_title.textContent = sessionTitle(session);
|
||||
try {
|
||||
const anti_csrf = await services.get('anti-csrf').token();
|
||||
const anti_csrf = await anti_csrf_service.token();
|
||||
const resp = await fetch(
|
||||
`${window.api_origin}/auth/sessions/${encodeURIComponent(session.uuid)}/label`,
|
||||
{
|
||||
@@ -558,7 +558,7 @@ const UIWindowManageSessions = async function UIWindowManageSessions (options) {
|
||||
});
|
||||
if ( ! ok ) return;
|
||||
|
||||
const anti_csrf = await services.get('anti-csrf').token();
|
||||
const anti_csrf = await anti_csrf_service.token();
|
||||
|
||||
// Route access-token rows to the dedicated endpoint
|
||||
// so `access_token_permissions` is cleared in addition
|
||||
@@ -732,7 +732,7 @@ const UIWindowManageSessions = async function UIWindowManageSessions (options) {
|
||||
});
|
||||
if ( ! ok ) return;
|
||||
|
||||
const anti_csrf = await services.get('anti-csrf').token();
|
||||
const anti_csrf = await anti_csrf_service.token();
|
||||
const resp = await fetch(
|
||||
`${window.api_origin}/auth/revoke-all-sessions`,
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import UIWindow from './UIWindow.js';
|
||||
import UIAlert from './UIAlert.js';
|
||||
import { locale_service } from '../modules/locale.js';
|
||||
import UIWindowLogin from './UIWindowLogin.js';
|
||||
import check_password_strength from '../helpers/check_password_strength.js';
|
||||
|
||||
@@ -122,8 +123,7 @@ async function UIWindowNewPassword (options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const svc_locale = globalThis.services.get('locale');
|
||||
const countdown = svc_locale.format_duration(time_remaining);
|
||||
const countdown = locale_service.format_duration(time_remaining);
|
||||
|
||||
$(el_window).find('.change-password-btn').html(`Set New Password (${countdown})`);
|
||||
}, 1000);
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { END_HARD, END_SOFT } from '../definitions.js';
|
||||
import { process_service } from '../modules/process.js';
|
||||
import UIAlert from './UIAlert.js';
|
||||
import UIContextMenu from './UIContextMenu.js';
|
||||
import UIWindow from './UIWindow.js';
|
||||
|
||||
const end_process = async (uuid, force) => {
|
||||
const svc_process = globalThis.services.get('process');
|
||||
const svc_process = process_service;
|
||||
const process = svc_process.get_by_uuid(uuid);
|
||||
if ( ! process ) {
|
||||
console.warn(`Can't end process with uuid='${uuid}': does not exist`);
|
||||
@@ -86,7 +87,7 @@ const calculate_indent_string = (indent_level, is_last_item_stack, is_last_item)
|
||||
};
|
||||
|
||||
const generate_task_rows = (items, { indent_level, is_last_item_stack }) => {
|
||||
const svc_process = globalThis.services.get('process');
|
||||
const svc_process = process_service;
|
||||
let rows_html = '';
|
||||
|
||||
for ( let i = 0; i < items.length; i++ ) {
|
||||
@@ -141,7 +142,7 @@ const generate_task_rows = (items, { indent_level, is_last_item_stack }) => {
|
||||
};
|
||||
|
||||
const UIWindowTaskManager = async function UIWindowTaskManager () {
|
||||
const svc_process = globalThis.services.get('process');
|
||||
const svc_process = process_service;
|
||||
|
||||
let h = '';
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
*/
|
||||
import { UIColorPickerWidget, hslaToHex8 } from './UIColorPickerWidget.js';
|
||||
import UIWindow from './UIWindow.js';
|
||||
import { theme_service } from '../modules/theme.js';
|
||||
|
||||
const UIWindowThemeDialog = async function UIWindowThemeDialog (options) {
|
||||
options = options ?? {};
|
||||
const services = globalThis.services;
|
||||
const svc_theme = services.get('theme');
|
||||
const svc_theme = theme_service;
|
||||
|
||||
// Get current theme values and convert to hex8 for the color picker
|
||||
const currentHue = svc_theme.get('hue');
|
||||
|
||||
@@ -19,40 +19,7 @@
|
||||
|
||||
import TeePromise from './util/TeePromise.js';
|
||||
import AdvancedBase from './util/AdvancedBase.js';
|
||||
|
||||
const NOOP = async () => {
|
||||
};
|
||||
|
||||
export class Service extends AdvancedBase {
|
||||
// TODO: Service todo items
|
||||
static TODO = [
|
||||
'consolidate with BaseService from backend',
|
||||
];
|
||||
async __on (id, args) {
|
||||
const handler = this.__get_event_handler(id);
|
||||
|
||||
return await handler(id, ...args);
|
||||
}
|
||||
__get_event_handler (id) {
|
||||
return this[`__on_${id}`]?.bind?.(this)
|
||||
|| this.constructor[`__on_${id}`]?.bind?.(this.constructor)
|
||||
|| NOOP;
|
||||
}
|
||||
construct (o) {
|
||||
this.$puter = {};
|
||||
for ( const k in o ) this.$puter[k] = o[k];
|
||||
if ( ! this._construct ) return;
|
||||
return this._construct();
|
||||
}
|
||||
init (...a) {
|
||||
if ( ! this._init ) return;
|
||||
this.services = a[0].services;
|
||||
return this._init(...a);
|
||||
}
|
||||
get context () {
|
||||
return { services: this.services };
|
||||
}
|
||||
};
|
||||
import { process_service } from './modules/process.js';
|
||||
|
||||
export const PROCESS_INITIALIZING = { i18n_key: 'initializing' };
|
||||
export const PROCESS_RUNNING = { i18n_key: 'running' };
|
||||
@@ -137,8 +104,7 @@ export class InitProcess extends Process {
|
||||
}
|
||||
|
||||
_signal (sig) {
|
||||
const svc_process = globalThis.services.get('process');
|
||||
for ( const process of svc_process.processes ) {
|
||||
for ( const process of process_service.processes ) {
|
||||
if ( process === this ) continue;
|
||||
process.signal(sig);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import update_title_based_on_uploads from './helpers/update_title_based_on_uploa
|
||||
import update_username_in_gui from './helpers/update_username_in_gui.js';
|
||||
import mime from './lib/mime.js';
|
||||
import path from './lib/path.js';
|
||||
import { broadcast_service } from './modules/broadcast.js';
|
||||
import UIAlert from './UI/UIAlert.js';
|
||||
import UIItem from './UI/UIItem.js';
|
||||
import UIWindowLogin from './UI/UIWindowLogin.js';
|
||||
@@ -719,8 +720,7 @@ window.update_user_preferences = function (user_preferences) {
|
||||
window.locale = language;
|
||||
|
||||
// Broadcast locale change to apps
|
||||
const broadcastService = globalThis.services.get('broadcast');
|
||||
broadcastService.sendBroadcast('localeChanged', {
|
||||
broadcast_service.sendBroadcast('localeChanged', {
|
||||
language: language,
|
||||
}, { sendToNewAppInstances: true });
|
||||
};
|
||||
@@ -3271,7 +3271,7 @@ window.iframe_for_app_instance = (instance_id) => {
|
||||
};
|
||||
|
||||
// Run any callbacks to say that the app has closed
|
||||
// ref(./services/ExecService.js): this is called from ExecService.js on
|
||||
// ref(./modules/exec.js): this is called from the exec module on
|
||||
// close if the app does not use puter.js
|
||||
window.report_app_closed = (instance_id, status_code) => {
|
||||
const el_window = window.window_for_app_instance(instance_id);
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
import path from '../lib/path.js';
|
||||
import { PROCESS_IPC_ATTACHED, PROCESS_RUNNING, PortalProcess, PseudoProcess } from '../definitions.js';
|
||||
import { process_service } from '../modules/process.js';
|
||||
import { broadcast_service } from '../modules/broadcast.js';
|
||||
import UIWindow from '../UI/UIWindow.js';
|
||||
|
||||
const normalizePrivateAccessDecision = (privateAccess) => {
|
||||
@@ -295,7 +297,7 @@ const launch_app = async (options) => {
|
||||
app_info: app_info,
|
||||
},
|
||||
});
|
||||
const svc_process = globalThis.services.get('process');
|
||||
const svc_process = process_service;
|
||||
svc_process.register(process);
|
||||
if ( options.path === window.home_path ) {
|
||||
title = i18n('home');
|
||||
@@ -367,7 +369,7 @@ const launch_app = async (options) => {
|
||||
app_info: app_info,
|
||||
},
|
||||
});
|
||||
const svc_process = globalThis.services.get('process');
|
||||
const svc_process = process_service;
|
||||
svc_process.register(process);
|
||||
|
||||
//-----------------------------------
|
||||
@@ -691,7 +693,7 @@ const launch_app = async (options) => {
|
||||
$(process.references.iframe).attr('data-appUsesSDK', 'true');
|
||||
|
||||
// Send any saved broadcasts to the new app
|
||||
globalThis.services.get('broadcast').sendSavedBroadcastsTo(uuid);
|
||||
broadcast_service.sendSavedBroadcastsTo(uuid);
|
||||
|
||||
// If `window-active` is set (meaning the window is focused), focus the window one more time
|
||||
// this is to ensure that the iframe is `definitely` focused and can receive keyboard events (e.g. keydown)
|
||||
@@ -704,7 +706,7 @@ const launch_app = async (options) => {
|
||||
process.chstatus(PROCESS_RUNNING);
|
||||
|
||||
$(el).on('remove', () => {
|
||||
const svc_process = globalThis.services.get('process');
|
||||
const svc_process = process_service;
|
||||
svc_process.unregister(process.uuid);
|
||||
});
|
||||
|
||||
|
||||
+15
-73
@@ -44,15 +44,11 @@ import update_last_touch_coordinates from './helpers/update_last_touch_coordinat
|
||||
import update_mouse_position from './helpers/update_mouse_position.js';
|
||||
import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js';
|
||||
import path from './lib/path.js';
|
||||
import { AntiCSRFService } from './services/AntiCSRFService.js';
|
||||
import { BroadcastService } from './services/BroadcastService.js';
|
||||
import { DebugService } from './services/DebugService.js';
|
||||
import { ExecService } from './services/ExecService.js';
|
||||
import { IPCService } from './services/IPCService.js';
|
||||
import { LaunchOnInitService } from './services/LaunchOnInitService.js';
|
||||
import { LocaleService } from './services/LocaleService.js';
|
||||
import { ProcessService } from './services/ProcessService.js';
|
||||
import { ThemeService } from './services/ThemeService.js';
|
||||
import { exec_service } from './modules/exec.js';
|
||||
import { debug_service } from './modules/debug.js';
|
||||
import { theme_service } from './modules/theme.js';
|
||||
import { process_service } from './modules/process.js';
|
||||
import { launch_on_init } from './modules/launch-on-init.js';
|
||||
import { privacy_aware_path } from './util/desktop.js';
|
||||
|
||||
const postAuthActions = async (action) => {
|
||||
@@ -397,75 +393,21 @@ const postAuthActions = async (action) => {
|
||||
};
|
||||
|
||||
const launch_services = async function (options) {
|
||||
// === Services Data Structures ===
|
||||
const services_l_ = [];
|
||||
const services_m_ = {};
|
||||
globalThis.services = {
|
||||
get: (name) => services_m_[name],
|
||||
emit: (id, args) => {
|
||||
for (const [_, instance] of services_l_) {
|
||||
instance.__on(id, args ?? []);
|
||||
}
|
||||
},
|
||||
};
|
||||
const register = (name, instance) => {
|
||||
services_l_.push([name, instance]);
|
||||
services_m_[name] = instance;
|
||||
};
|
||||
|
||||
globalThis.def(UIComponentWindow, 'ui.UIComponentWindow');
|
||||
|
||||
// === Hooks for Service Scripts from Backend ===
|
||||
const service_script_deferred = { services: [], on_ready: [] };
|
||||
const service_script_api = {
|
||||
register: (...a) => service_script_deferred.services.push(a),
|
||||
on_ready: (fn) => service_script_deferred.on_ready.push(fn),
|
||||
// Some files can't be imported by service scripts,
|
||||
// so this hack makes that possible.
|
||||
def: globalThis.def,
|
||||
use: globalThis.use,
|
||||
// use: name => ({ UIWindow, UIComponentWindow })[name],
|
||||
};
|
||||
globalThis.service_script_api_promise.resolve(service_script_api);
|
||||
// These modules are plain singletons wired by direct import; their
|
||||
// constructors have already run, so init() just performs the one-time
|
||||
// setup that needs the runtime (DOM, puter, window.ipc_handlers).
|
||||
exec_service.init();
|
||||
debug_service.init();
|
||||
theme_service.init();
|
||||
process_service.init();
|
||||
|
||||
// === Builtin Services ===
|
||||
register('ipc', new IPCService());
|
||||
register('exec', new ExecService());
|
||||
register('debug', new DebugService());
|
||||
register('broadcast', new BroadcastService());
|
||||
register('theme', new ThemeService());
|
||||
register('process', new ProcessService());
|
||||
register('locale', new LocaleService());
|
||||
register('anti-csrf', new AntiCSRFService());
|
||||
register('__launch-on-init', new LaunchOnInitService());
|
||||
|
||||
// === Service-Script Services ===
|
||||
for (const [name, script] of service_script_deferred.services) {
|
||||
register(name, script);
|
||||
}
|
||||
|
||||
for (const [_, instance] of services_l_) {
|
||||
await instance.construct({
|
||||
gui_params: options,
|
||||
});
|
||||
}
|
||||
|
||||
for (const [_, instance] of services_l_) {
|
||||
await instance.init({
|
||||
services: globalThis.services,
|
||||
});
|
||||
}
|
||||
|
||||
// === Service-Script Ready ===
|
||||
for (const fn of service_script_deferred.on_ready) {
|
||||
await fn();
|
||||
}
|
||||
// Run any commands the GUI was launched with.
|
||||
launch_on_init(options);
|
||||
|
||||
// Set init process status
|
||||
{
|
||||
const svc_process = globalThis.services.get('process');
|
||||
svc_process.get_init().chstatus(PROCESS_RUNNING);
|
||||
}
|
||||
process_service.get_init().chstatus(PROCESS_RUNNING);
|
||||
};
|
||||
|
||||
// This code snippet addresses the issue flagged by Lighthouse regarding the use of
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Service } from '../definitions.js';
|
||||
|
||||
export class AntiCSRFService extends Service {
|
||||
class AntiCSRFService {
|
||||
/**
|
||||
* Request an anti-csrf token from the server
|
||||
* @return anti_csrf: string
|
||||
@@ -39,3 +37,5 @@ export class AntiCSRFService extends Service {
|
||||
return anti_csrf;
|
||||
}
|
||||
}
|
||||
|
||||
export const anti_csrf_service = new AntiCSRFService();
|
||||
@@ -17,16 +17,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Service } from '../definitions.js';
|
||||
|
||||
export class BroadcastService extends Service {
|
||||
class BroadcastService {
|
||||
// After a new app is launched, it will receive these broadcasts
|
||||
#broadcastsToSendToNewAppInstances = new Map(); // name -> data
|
||||
|
||||
async _init () {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
// Send a 'broadcast' message to all open apps, with the given name and data.
|
||||
// If sendToNewAppInstances is true, the message will be saved, and sent to any apps that are launched later.
|
||||
// A new saved broadcast will replace an earlier one with the same name.
|
||||
@@ -60,3 +54,5 @@ export class BroadcastService extends Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const broadcast_service = new BroadcastService();
|
||||
@@ -17,16 +17,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Service } from '../definitions.js';
|
||||
import { exec_service } from './exec.js';
|
||||
|
||||
export class DebugService extends Service {
|
||||
async _init () {
|
||||
class DebugService {
|
||||
constructor () {
|
||||
// Track enabled log categories
|
||||
this.enabled_logs = [];
|
||||
}
|
||||
|
||||
init () {
|
||||
// Provide enabled logs as a query param
|
||||
const svc_exec = this.services.get('exec');
|
||||
svc_exec.register_param_provider(() => {
|
||||
exec_service.register_param_provider(() => {
|
||||
return {
|
||||
...(this.enabled_logs.length > 0
|
||||
? { enabled_logs: this.enabled_logs.join(';') }
|
||||
@@ -48,3 +49,5 @@ export class DebugService extends Service {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const debug_service = new DebugService();
|
||||
@@ -17,15 +17,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PROCESS_IPC_ATTACHED, Service } from '../definitions.js';
|
||||
import { PROCESS_IPC_ATTACHED } from '../definitions.js';
|
||||
import launch_app from '../helpers/launch_app.js';
|
||||
import { broadcast_service } from './broadcast.js';
|
||||
import { ipc_service } from './ipc.js';
|
||||
import { process_service } from './process.js';
|
||||
|
||||
export class ExecService extends Service {
|
||||
static description = `
|
||||
Manages instances of apps on the Puter desktop.
|
||||
`;
|
||||
|
||||
_construct () {
|
||||
class ExecService {
|
||||
constructor () {
|
||||
this.param_providers = [];
|
||||
}
|
||||
|
||||
@@ -33,12 +32,11 @@ export class ExecService extends Service {
|
||||
this.param_providers.push(param_provider);
|
||||
}
|
||||
|
||||
async _init ({ services }) {
|
||||
const svc_ipc = services.get('ipc');
|
||||
svc_ipc.register_ipc_handler('launchApp', {
|
||||
init () {
|
||||
ipc_service.register_ipc_handler('launchApp', {
|
||||
handler: this.launchApp.bind(this),
|
||||
});
|
||||
svc_ipc.register_ipc_handler('connectToInstance', {
|
||||
ipc_service.register_ipc_handler('connectToInstance', {
|
||||
handler: this.connectToInstance.bind(this),
|
||||
});
|
||||
|
||||
@@ -55,8 +53,7 @@ export class ExecService extends Service {
|
||||
// This mechanism will be replated with xdrpc soon
|
||||
const child_instance_id = window.uuidv4();
|
||||
|
||||
const svc_ipc = this.services.get('ipc');
|
||||
const connection = ipc_context ? svc_ipc.add_connection({
|
||||
const connection = ipc_context ? ipc_service.add_connection({
|
||||
source: process.uuid,
|
||||
target: child_instance_id,
|
||||
}) : undefined;
|
||||
@@ -199,7 +196,7 @@ export class ExecService extends Service {
|
||||
send_child_launched_msg({ uses_sdk: true });
|
||||
|
||||
// Send any saved broadcasts to the new app
|
||||
globalThis.services.get('broadcast').sendSavedBroadcastsTo(child_instance_id);
|
||||
broadcast_service.sendSavedBroadcastsTo(child_instance_id);
|
||||
|
||||
// If `window-active` is set (meanign the window is focused), focus the window one more time
|
||||
// this is to ensure that the iframe is `definitely` focused and can receive keyboard events (e.g. keydown)
|
||||
@@ -245,16 +242,14 @@ export class ExecService extends Service {
|
||||
throw new Error('Connection not allowed.');
|
||||
}
|
||||
|
||||
const svc_process = this.services.get('process');
|
||||
const options = svc_process.select_by_name(app_name);
|
||||
const options = process_service.select_by_name(app_name);
|
||||
const process = options[0];
|
||||
|
||||
if ( ! process ) {
|
||||
throw new Error(`No process found: ${app_name}`);
|
||||
}
|
||||
|
||||
const svc_ipc = this.services.get('ipc');
|
||||
const connection = svc_ipc.add_connection({
|
||||
const connection = ipc_service.add_connection({
|
||||
source: caller_process.uuid,
|
||||
target: process.uuid,
|
||||
});
|
||||
@@ -268,3 +263,5 @@ export class ExecService extends Service {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const exec_service = new ExecService();
|
||||
@@ -17,11 +17,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Service } from '../definitions.js';
|
||||
import { process_service } from './process.js';
|
||||
|
||||
class InternalConnection {
|
||||
constructor ({ source, target, uuid, reverse }, { services }) {
|
||||
this.services = services;
|
||||
constructor ({ source, target, uuid, reverse }) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.uuid = uuid;
|
||||
@@ -29,8 +28,7 @@ class InternalConnection {
|
||||
}
|
||||
|
||||
send (data) {
|
||||
const svc_process = this.services.get('process');
|
||||
const process = svc_process.get_by_uuid(this.target);
|
||||
const process = process_service.get_by_uuid(this.target);
|
||||
const channel = {
|
||||
returnAddress: this.reverse,
|
||||
};
|
||||
@@ -38,12 +36,8 @@ class InternalConnection {
|
||||
}
|
||||
}
|
||||
|
||||
export class IPCService extends Service {
|
||||
static description = `
|
||||
Allows other services to expose methods to apps.
|
||||
`;
|
||||
|
||||
async _init () {
|
||||
class IPCService {
|
||||
constructor () {
|
||||
this.connections_ = {};
|
||||
}
|
||||
|
||||
@@ -69,10 +63,12 @@ export class IPCService extends Service {
|
||||
const entry = this.connections_[uuid];
|
||||
if ( ! entry ) return;
|
||||
if ( entry.object ) return entry.object;
|
||||
return entry.object = new InternalConnection(entry, this.context);
|
||||
return entry.object = new InternalConnection(entry);
|
||||
}
|
||||
|
||||
register_ipc_handler (name, spec) {
|
||||
window.ipc_handlers[name] = spec;
|
||||
}
|
||||
}
|
||||
|
||||
export const ipc_service = new IPCService();
|
||||
+19
-24
@@ -16,32 +16,27 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import UIAlert from '../UI/UIAlert.js';
|
||||
|
||||
import { Service } from '../definitions.js';
|
||||
const commands = {
|
||||
'window-call': ({ fn_name, args }) => {
|
||||
window[fn_name](...args);
|
||||
},
|
||||
};
|
||||
|
||||
export class LaunchOnInitService extends Service {
|
||||
_construct () {
|
||||
this.commands = {
|
||||
'window-call': ({ fn_name, args }) => {
|
||||
window[fn_name](...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
async _init () {
|
||||
const launch_options = this.$puter.gui_params.launch_options;
|
||||
if ( ! launch_options ) return;
|
||||
const run_command = (command) => {
|
||||
const args = { ...command };
|
||||
delete args.$;
|
||||
commands[command.$](args);
|
||||
};
|
||||
|
||||
if ( launch_options.on_initialized ) {
|
||||
for ( const command of launch_options.on_initialized ) {
|
||||
this.run_(command);
|
||||
}
|
||||
// Run any commands the GUI was launched with (gui_params.launch_options.on_initialized).
|
||||
export const launch_on_init = (gui_params) => {
|
||||
const launch_options = gui_params?.launch_options;
|
||||
if ( ! launch_options ) return;
|
||||
|
||||
if ( launch_options.on_initialized ) {
|
||||
for ( const command of launch_options.on_initialized ) {
|
||||
run_command(command);
|
||||
}
|
||||
}
|
||||
|
||||
run_ (command) {
|
||||
const args = { ...command };
|
||||
delete args.$;
|
||||
this.commands[command.$](args);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -16,10 +16,9 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Service } from '../definitions.js';
|
||||
import i18n from '../i18n/i18n.js';
|
||||
|
||||
export class LocaleService extends Service {
|
||||
class LocaleService {
|
||||
format_duration (seconds) {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
@@ -41,3 +40,5 @@ export class LocaleService extends Service {
|
||||
return `${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const locale_service = new LocaleService();
|
||||
@@ -16,35 +16,37 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { InitProcess, Service } from '../definitions.js';
|
||||
import { InitProcess } from '../definitions.js';
|
||||
import { exec_service } from './exec.js';
|
||||
|
||||
// The NULL UUID is also the UUID for the init process.
|
||||
const NULL_UUID = '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
export class ProcessService extends Service {
|
||||
class ProcessService {
|
||||
static INITRC = [
|
||||
// 'puter-linux'
|
||||
];
|
||||
|
||||
async _init () {
|
||||
constructor () {
|
||||
this.processes = [];
|
||||
this.processes_map = new Map();
|
||||
this.uuid_to_treelist = new Map();
|
||||
}
|
||||
|
||||
init () {
|
||||
const root = new InitProcess({
|
||||
uuid: NULL_UUID,
|
||||
});
|
||||
this.register_(root);
|
||||
}
|
||||
|
||||
['__on_gui:ready'] () {
|
||||
const svc_exec = this.services.get('exec');
|
||||
on_gui_ready () {
|
||||
for ( let spec of ProcessService.INITRC ) {
|
||||
if ( typeof spec === 'string' ) {
|
||||
spec = { name: spec };
|
||||
}
|
||||
|
||||
svc_exec.launchApp({
|
||||
exec_service.launchApp({
|
||||
app_name: spec.name,
|
||||
});
|
||||
}
|
||||
@@ -120,3 +122,5 @@ export class ProcessService extends Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const process_service = new ProcessService();
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import UIAlert from '../UI/UIAlert.js';
|
||||
import { Service } from '../definitions.js';
|
||||
import { broadcast_service } from './broadcast.js';
|
||||
|
||||
const PUTER_THEME_DATA_FILENAME = '~/.__puter_gui.json';
|
||||
|
||||
@@ -32,12 +32,8 @@ const default_values = {
|
||||
light_text: false,
|
||||
};
|
||||
|
||||
export class ThemeService extends Service {
|
||||
#broadcastService;
|
||||
|
||||
async _init () {
|
||||
this.#broadcastService = globalThis.services.get('broadcast');
|
||||
|
||||
class ThemeService {
|
||||
init () {
|
||||
this.state = {
|
||||
sat: 41.18,
|
||||
hue: 210,
|
||||
@@ -129,7 +125,7 @@ export class ThemeService extends Service {
|
||||
this.root.style.setProperty('--primary-color-sidebar-item', s.light_text ? '#5a5d61aa' : '#fefeff');
|
||||
|
||||
// TODO: Should we debounce this to reduce traffic?
|
||||
this.#broadcastService.sendBroadcast('themeChanged', {
|
||||
broadcast_service.sendBroadcast('themeChanged', {
|
||||
palette: {
|
||||
primaryHue: s.hue,
|
||||
primarySaturation: `${s.sat }%`,
|
||||
@@ -154,3 +150,5 @@ export class ThemeService extends Service {
|
||||
5));
|
||||
}
|
||||
}
|
||||
|
||||
export const theme_service = new ThemeService();
|
||||
Reference in New Issue
Block a user