From ae92233b95f6abc26d4fcc1486f7007fb4ff563b Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Fri, 12 Dec 2025 19:44:15 -0800 Subject: [PATCH] Remove fat from puterjs (#2156) * Refactor puter-js to remove putility and service layer * Add fallback for IPC listener registration * Remove TeePromise in favor of `createDeferred` pattern * Update utils.js * Bump version to 2.2.0 in package.json * Remove model name normalization and driver mapping logic Eliminated the code responsible for normalizing model names and mapping models to specific AI drivers. The default driver is now set to 'ai-chat', simplifying the model selection and driver assignment process. * Simplify AIService selection logic in image generation * Remove unused Together image model constants --- package-lock.json | 3 +- src/gui/src/IPC.js | 20 +- src/puter-js/package.json | 5 +- src/puter-js/src/index.js | 180 +++++++------ src/puter-js/src/lib/filesystem/APIFS.js | 65 ----- src/puter-js/src/lib/filesystem/CacheFS.js | 242 ------------------ .../src/lib/filesystem/PostMessageFS.js | 40 --- .../src/lib/filesystem/definitions.js | 40 --- src/puter-js/src/lib/utils.js | 78 ++---- src/puter-js/src/modules/AI.js | 212 +-------------- src/puter-js/src/modules/Apps.js | 11 +- src/puter-js/src/modules/Auth.js | 11 +- src/puter-js/src/modules/Debug.js | 8 +- src/puter-js/src/modules/Drivers.js | 29 +-- src/puter-js/src/modules/FileSystem/index.js | 33 +-- src/puter-js/src/modules/Hosting.js | 9 +- src/puter-js/src/modules/KV.js | 30 ++- src/puter-js/src/modules/OS.js | 11 +- src/puter-js/src/modules/Perms.js | 7 +- src/puter-js/src/modules/Threads.js | 7 +- src/puter-js/src/modules/UI.js | 72 ++++-- src/puter-js/src/services/APIAccess.js | 46 ---- src/puter-js/src/services/FSRelay.js | 20 -- src/puter-js/src/services/Filesystem.js | 137 ---------- src/puter-js/src/services/NoPuterYet.js | 20 -- src/puter-js/src/services/XDIncoming.js | 44 ---- 26 files changed, 272 insertions(+), 1108 deletions(-) delete mode 100644 src/puter-js/src/lib/filesystem/APIFS.js delete mode 100644 src/puter-js/src/lib/filesystem/CacheFS.js delete mode 100644 src/puter-js/src/lib/filesystem/PostMessageFS.js delete mode 100644 src/puter-js/src/lib/filesystem/definitions.js delete mode 100644 src/puter-js/src/services/APIAccess.js delete mode 100644 src/puter-js/src/services/FSRelay.js delete mode 100644 src/puter-js/src/services/Filesystem.js delete mode 100644 src/puter-js/src/services/NoPuterYet.js delete mode 100644 src/puter-js/src/services/XDIncoming.js diff --git a/package-lock.json b/package-lock.json index 4c3b0fc6b..a32b8c6ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17885,8 +17885,7 @@ "version": "2.1.15", "license": "Apache-2.0", "dependencies": { - "@heyputer/kv.js": "^0.2.1", - "@heyputer/putility": "^1.1.1" + "@heyputer/kv.js": "^0.2.1" }, "devDependencies": { "concurrently": "^8.2.2", diff --git a/src/gui/src/IPC.js b/src/gui/src/IPC.js index 929925971..545c17486 100644 --- a/src/gui/src/IPC.js +++ b/src/gui/src/IPC.js @@ -36,6 +36,7 @@ import UIWindowSaveAccount from './UI/UIWindowSaveAccount.js'; import UIWindowSignup from './UI/UIWindowSignup.js'; import { PROCESS_IPC_ATTACHED } from './definitions.js'; +import TeePromise from './util/TeePromise.js'; window.ipc_handlers = {}; /** @@ -1911,7 +1912,20 @@ const ipc_listener = async (event, handled) => { 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); + // puter.services was removed during the recent puter.js refactor. If the + // service layer exists (older builds), use it; otherwise, attach the IPC + // listener directly so apps can still communicate with the GUI. + const svc_mgr = puter.services; + const svc_xdIncoming = svc_mgr?.get?.('xd-incoming'); + if ( svc_mgr?.wait_for_init && svc_xdIncoming?.register_filter_listener ) { + await svc_mgr.wait_for_init(['xd-incoming']); + svc_xdIncoming.register_filter_listener(ipc_listener); + return; + } + + // Fallback: register message handler directly + window.addEventListener('message', (event) => { + const handled = new TeePromise(); + ipc_listener(event, handled); + }); }); diff --git a/src/puter-js/package.json b/src/puter-js/package.json index 0428965c1..06ad73334 100644 --- a/src/puter-js/package.json +++ b/src/puter-js/package.json @@ -1,6 +1,6 @@ { "name": "@heyputer/puter.js", - "version": "2.1.15", + "version": "2.2.0", "description": "Puter.js - A JavaScript library for interacting with Puter services.", "homepage": "https://developer.puter.com", "main": "src/index.js", @@ -42,7 +42,6 @@ "webpack-cli": "^5.1.4" }, "dependencies": { - "@heyputer/kv.js": "^0.2.1", - "@heyputer/putility": "^1.1.1" + "@heyputer/kv.js": "^0.2.1" } } diff --git a/src/puter-js/src/index.js b/src/puter-js/src/index.js index 51f734bdb..96e3fd407 100644 --- a/src/puter-js/src/index.js +++ b/src/puter-js/src/index.js @@ -1,5 +1,3 @@ -import putility from '@heyputer/putility'; - import kvjs from '@heyputer/kv.js'; import APICallLogger from './lib/APICallLogger.js'; import path from './lib/path.js'; @@ -24,12 +22,69 @@ import Threads from './modules/Threads.js'; import UI from './modules/UI.js'; import Util from './modules/Util.js'; import { WorkersHandler } from './modules/Workers.js'; -import { APIAccessService } from './services/APIAccess.js'; -import { FilesystemService } from './services/Filesystem.js'; -import { FSRelayService } from './services/FSRelay.js'; -import { NoPuterYetService } from './services/NoPuterYet.js'; -import { XDIncomingService } from './services/XDIncoming.js'; +class SimpleLogger { + constructor (fields = {}) { + this.fieldsObj = fields; + this.enabled = new Set(); + } + + on (category) { + this.enabled.add(category); + } + + fields (extra = {}) { + return new SimpleLogger({ ...this.fieldsObj, ...extra }); + } + + info (...args) { + console.log(...this._prefix(), ...args); + } + + warn (...args) { + console.warn(...this._prefix(), ...args); + } + + error (...args) { + console.error(...this._prefix(), ...args); + } + + debug (...args) { + console.debug(...this._prefix(), ...args); + } + + _prefix () { + const entries = Object.entries(this.fieldsObj); + if ( !entries.length ) return []; + return [`[${ entries.map(([k, v]) => `${k}=${v}`).join(' ')}]`]; + } +} + +class Lock { + constructor () { + this.locked = false; + this.queue = []; + } + + async acquire () { + if ( !this.locked ) { + this.locked = true; + return; + } + + await new Promise(resolve => this.queue.push(resolve)); + this.locked = true; + } + + release () { + const next = this.queue.shift(); + if ( next ) { + next(); + return; + } + this.locked = false; + } +} // TODO: This is for a safe-guard below; we should check if we can // generalize this behavior rather than hard-coding it. // (using defaultGUIOrigin breaks locally-hosted apps) @@ -135,15 +190,6 @@ const puterInit = (function () { // "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() - .follow(this, ['env', 'util', 'authToken', 'APIOrigin', 'appID']); - - context.puter = this; - - this.services = new putility.system.ServiceManager({ context }); - this.context = context; - context.services = this.services; // Holds the query parameters found in the current URL let URLParams = new URLSearchParams(globalThis.location?.search); @@ -261,57 +307,15 @@ const puterInit = (function () { // === START :: Logger === - // logger will log to console - let logger = new putility.libs.log.ConsoleLogger(); - - // logs can be toggled based on categories - logger = new putility.libs.log.CategorizedToggleLogger({ delegate: logger }); - const cat_logger = logger; - - // create facade for easy logging - this.logger = new putility.libs.log.LoggerFacade({ - impl: logger, - cat: cat_logger, - }); + // Basic logger replacement (console-based) + let logger = new SimpleLogger(); + this.logger = logger; // Initialize API call logger this.apiCallLogger = new APICallLogger({ enabled: false, // Disabled by default }); - // === START :: Services === // - - this.services.register('no-puter-yet', NoPuterYetService); - this.services.register('filesystem', FilesystemService); - this.services.register('api-access', APIAccessService); - this.services.register('xd-incoming', XDIncomingService); - if ( this.env !== 'app' ) { - this.services.register('fs-relay', FSRelayService); - } - - // 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.auth_token = this.authToken; - svc_apiAccess.api_origin = this.APIOrigin; - [ - ['authToken', 'auth_token'], - ['APIOrigin', 'api_origin'], - ].forEach(([k1, k2]) => { - Object.defineProperty(this, k1, { - get () { - return svc_apiAccess[k2]; - }, - set (v) { - svc_apiAccess[k2] = v; - }, - }); - }); - })(); - // === Start :: Modules === // // The SDK is running in the Puter GUI (i.e. 'gui') @@ -365,31 +369,27 @@ const puterInit = (function () { // Add prefix logger (needed to happen after modules are initialized) (async () => { - await this.services.wait_for_init(['api-access']); - const whoami = await this.auth.whoami(); - logger = new putility.libs.log.PrefixLogger({ - delegate: logger, - prefix: `[${ + try { + const whoami = await this.auth.whoami(); + const prefix = `[${ whoami?.app_name ?? this.appInstanceID ?? 'HOST' - }] `, - }); - - this.logger.impl = logger; + }]`; + logger = logger.fields({ prefix }); + this.logger = logger; + } catch (error) { + if ( this.debugMode ) { + console.error('Failed to initialize prefix logger', error); + } + } })(); // Lock to prevent multiple requests to `/rao` - this.lock_rao_ = new putility.libs.promise.Lock(); + this.lock_rao_ = new Lock(); // Promise that resolves when it's okay to request `/rao` - this.p_can_request_rao_ = new putility.libs.promise.TeePromise(); + this.p_can_request_rao_ = Promise.resolve(); // Flag that indicates if a request to `/rao` has been made this.rao_requested_ = false; - // In case we're already auth'd, request `/rao` - (async () => { - await this.services.wait_for_init(['api-access']); - this.p_can_request_rao_.resolve(); - })(); - this.net = { generateWispV1URL: async () => { const { token: wispToken, server: wispServer } = (await (await fetch(`${this.APIOrigin }/wisp/relay-token/create`, { @@ -457,7 +457,7 @@ const puterInit = (function () { } registerModule (name, cls, parameters = {}) { - const instance = new cls(this.context, parameters); + const instance = new cls(this, parameters); instance.puter = this; this.modules_.push(name); this[name] = instance; @@ -518,6 +518,25 @@ const puterInit = (function () { this.updateSubmodules(); }; + runWhenPuterHappensCallbacks = function () { + if ( this.env !== 'gui' ) return; + if ( ! globalThis.when_puter_happens ) return; + + const callbacks = Array.isArray(globalThis.when_puter_happens) + ? globalThis.when_puter_happens + : [globalThis.when_puter_happens]; + + for ( const fn of callbacks ) { + try { + fn({ puter: this }); + } catch ( error ) { + if ( this.debugMode ) { + console.error('when_puter_happens callback failed', error); + } + } + } + }; + resetAuthToken = function () { this.authToken = null; // If the SDK is running on a 3rd-party site or an app, then save the authToken in localStorage @@ -785,6 +804,7 @@ const puterInit = (function () { export const puter = puterInit(); export default puter; globalThis.puter = puter; +puter.runWhenPuterHappensCallbacks(); puter.tools = []; /** @@ -863,4 +883,4 @@ globalThis.addEventListener && globalThis.addEventListener('message', async (eve puter.puterAuthState.resolver = null; }; } -}); \ No newline at end of file +}); diff --git a/src/puter-js/src/lib/filesystem/APIFS.js b/src/puter-js/src/lib/filesystem/APIFS.js deleted file mode 100644 index 64ebfe773..000000000 --- a/src/puter-js/src/lib/filesystem/APIFS.js +++ /dev/null @@ -1,65 +0,0 @@ -import * as utils from '../utils.js'; -import putility from '@heyputer/putility'; -import { TeePromise } from '@heyputer/putility/src/libs/promise.js'; -import getAbsolutePathForApp from '../../modules/FileSystem/utils/getAbsolutePathForApp.js'; -import { TFilesystem } from './definitions.js'; - -export class PuterAPIFilesystem extends putility.AdvancedBase { - constructor ({ api_info }) { - super(); - this.api_info = api_info; - } - - static IMPLEMENTS = { - [TFilesystem]: { - stat: async function (options) { - this.ensure_auth_(); - const tp = new TeePromise(); - - const xhr = new utils.initXhr('/stat', this.api_info.APIOrigin, undefined, 'post', 'text/plain;actually=json'); - utils.setupXhrEventHandlers(xhr, undefined, undefined, tp.resolve.bind(tp), tp.reject.bind(tp)); - - let dataToSend = {}; - if ( options.uid !== undefined ) { - dataToSend.uid = options.uid; - } else if ( options.path !== undefined ) { - // If dirPath is not provided or it's not starting with a slash, it means it's a relative path - // in that case, we need to prepend the app's root directory to it - dataToSend.path = getAbsolutePathForApp(options.path); - } - - dataToSend.return_subdomains = options.returnSubdomains; - dataToSend.return_permissions = options.returnPermissions; - dataToSend.return_versions = options.returnVersions; - dataToSend.return_size = options.returnSize; - dataToSend.auth_token = this.api_info.authToken; - - xhr.send(JSON.stringify(dataToSend)); - - return await tp; - }, - readdir: async function (options) { - this.ensure_auth_(); - const tp = new TeePromise(); - - const xhr = new utils.initXhr('/readdir', this.api_info.APIOrigin, undefined, 'post', 'text/plain;actually=json'); - utils.setupXhrEventHandlers(xhr, undefined, undefined, tp.resolve.bind(tp), tp.reject.bind(tp)); - - xhr.send(JSON.stringify({ path: getAbsolutePathForApp(options.path), auth_token: this.api_info.authToken })); - - return await tp; - }, - }, - }; - - ensure_auth_ () { - // TODO: remove reference to global 'puter'; get 'env' via context - if ( !this.api_info.authToken && puter.env === 'web' ) { - try { - this.ui.authenticateWithPuter(); - } catch (e) { - throw new Error('Authentication failed.'); - } - } - } -} diff --git a/src/puter-js/src/lib/filesystem/CacheFS.js b/src/puter-js/src/lib/filesystem/CacheFS.js deleted file mode 100644 index fdb5bfd99..000000000 --- a/src/puter-js/src/lib/filesystem/CacheFS.js +++ /dev/null @@ -1,242 +0,0 @@ -import putility from '@heyputer/putility'; -import { RWLock } from '@heyputer/putility/src/libs/promise.js'; -import { ProxyFilesystem, TFilesystem } from './definitions.js'; -import { uuidv4 } from '../utils.js'; - -export const ROOT_UUID = '00000000-0000-0000-0000-000000000000'; -const TTL = 5 * 1000; - -export class CacheFS extends putility.AdvancedBase { - static PROPERTIES = { - assocs_path_: () => ({}), - assocs_uuid_: () => ({}), - entries: () => ({}), - }; - - get_entry_ei (external_identifier) { - if ( Array.isArray(external_identifier) ) { - for ( const ei of external_identifier ) { - const entry = this.get_entry_ei(ei); - if ( entry ) return entry; - } - return; - } - - console.log('GET ENTRY EI', external_identifier); - - const internal_identifier = - this.assocs_path_[external_identifier] || - this.assocs_uuid_[external_identifier] || - external_identifier; - - if ( ! internal_identifier ) { - return; - } - return this.entries[internal_identifier]; - } - - add_entry ({ id } = {}) { - const internal_identifier = id ?? uuidv4(); - const entry = { - id: internal_identifier, - stat_has: {}, - stat_exp: 0, - locks: { - stat: new RWLock(), - members: new RWLock(), - }, - }; - this.entries[internal_identifier] = entry; - return entry; - } - - assoc_path (path, internal_identifier) { - console.log('ASSOC PATH', path, internal_identifier); - this.assocs_path_[path] = internal_identifier; - } - - assoc_uuid (uuid, internal_identifier) { - if ( uuid === internal_identifier ) return; - this.assocs_uuid_[uuid] = internal_identifier; - } - -} - -export class CachedFilesystem extends ProxyFilesystem { - constructor (o) { - super(o); - // this.cacheFS = cacheFS; - this.cacheFS = new CacheFS(); - } - static IMPLEMENTS = { - [TFilesystem]: { - stat: async function (o) { - let cent = this.cacheFS.get_entry_ei(o.path ?? o.uid); - - const modifiers = [ - 'subdomains', - 'permissions', - 'versions', - 'size', - ]; - - let values_requested = {}; - for ( const mod of modifiers ) { - const optionsKey = `return${ - mod.charAt(0).toUpperCase() - }${mod.slice(1)}`; - if ( ! o[optionsKey] ) continue; - values_requested[mod] = true; - } - - const satisfactory_cache = cent => { - for ( const mod of modifiers ) { - if ( ! values_requested[mod] ) continue; - if ( ! cent.stat_has[mod] ) { - return false; - } - } - return true; - }; - - let cached_stat; - if ( cent && cent.stat && cent.stat_exp > Date.now() ) { - const l = await cent.locks.stat.rlock(); - if ( satisfactory_cache(cent) ) { - cached_stat = cent.stat; - } - l.unlock(); - } - - if ( cached_stat ) { - console.log('CACHE HIT'); - return cached_stat; - } - console.log('CACHE MISS'); - - let l; - if ( cent ) { - l = await cent.locks.stat.wlock(); - } - - console.log('DOING THE STAT', o); - const entry = await this.delegate.stat(o); - - // We might have new information to identify a relevant cache entry - let cent_replaced = !!cent; - cent = this.cacheFS.get_entry_ei([entry.uid, entry.path]); - if ( cent ) { - if ( cent_replaced ) l.unlock(); - l = await cent.locks.stat.wlock(); - } - - if ( ! cent ) { - cent = this.cacheFS.add_entry({ id: entry.uid }); - this.cacheFS.assoc_path(entry.path, cent.id); - this.cacheFS.assoc_uuid(entry.uid, cent.id); - - l = await cent.locks.stat.wlock(); - } - - cent.stat = entry; - cent.stat_has = { ...values_requested }; - // TODO: increase cache TTL once invalidation works - cent.stat_exp = Date.now() + TTL; - - l.unlock(); - - console.log('RETRUNING THE ENTRY', entry); - return entry; - }, - readdir: async function (o) { - let cent = this.cacheFS.get_entry_ei([o.path, o.uid]); - - console.log('CENT', cent, o); - let stats = null; - if ( cent && cent.members && cent.members_exp > Date.now() ) { - console.log('MEMBERS', cent.members); - stats = []; - const l = await cent.locks.stat.rlock(); - - for ( const id of cent.members ) { - const member = this.cacheFS.get_entry_ei(id); - if ( !member || !member.stat || member.stat_exp <= Date.now() ) { - console.log('NO MEMBER OR STAT', member); - stats = null; - break; - } - console.log('member', member); - if ( !o.no_assocs && !member.stat_has.subdomains ) { - stats = null; - break; - } - if ( !o.no_assocs && !member.stat_has.apps ) { - stats = null; - break; - } - if ( !o.no_thumbs && !member.stat_has.thumbnail ) { - stats = null; - break; - } - console.log('PUSHING', member.stat); - - stats.push(member.stat); - } - - l.unlock(); - } - - if ( stats ) { - return stats; - } - - let l; - if ( cent ) { - l = await cent.locks.members.wlock(); - } - - const entries = await this.delegate.readdir(o); - if ( ! cent ) { - cent = this.cacheFS.add_entry(o.uid ? { id: o.uid } : {}); - if ( o.path ) this.cacheFS.assoc_path(o.path, cent.id); - l = await cent.locks.members.wlock(); - } - - let cent_ids = []; - for ( const entry of entries ) { - let entry_cent = this.cacheFS.get_entry_ei([entry.path, entry.uid]); - if ( ! entry_cent ) { - entry_cent = this.cacheFS.add_entry({ id: entry.uid }); - this.cacheFS.assoc_path(entry.path, entry.uid); - } - cent_ids.push(entry_cent.id); - // TODO: update_stat_ is not implemented - // this.cacheFS.update_stat_(entry_cent, entry, { - // subdomains: ! o.no_assocs, - // apps: ! o.no_assocs, - // thumbnail: ! o.no_thumbs, - // }); - entry_cent.stat = entry; - entry_cent.stat_has = { - subdomains: !o.no_assocs, - apps: !o.no_assocs, - thumbnail: !o.no_thumbs, - }; - entry_cent.stat_exp = Date.now() + 1000 * 3; - } - - cent.members = []; - for ( const id of cent_ids ) { - cent.members.push(id); - } - cent.members_exp = Date.now() + TTL; - - l.unlock(); - - console.log('CACHE ENTRY?', cent); - - return entries; - }, - }, - }; -} diff --git a/src/puter-js/src/lib/filesystem/PostMessageFS.js b/src/puter-js/src/lib/filesystem/PostMessageFS.js deleted file mode 100644 index 0d0dca046..000000000 --- a/src/puter-js/src/lib/filesystem/PostMessageFS.js +++ /dev/null @@ -1,40 +0,0 @@ -import putility from '@heyputer/putility'; -import { TFilesystem } from './definitions.js'; - -const example = { - 'id': 'f485f1ba-de07-422c-8c4b-c2da057d4a44', - 'uid': 'f485f1ba-de07-422c-8c4b-c2da057d4a44', - 'is_dir': true, - 'immutable': true, - 'name': 'Test', -}; - -export class PostMessageFilesystem extends putility.AdvancedBase { - constructor ({ rpc, messageTarget }) { - super(); - this.rpc = rpc; - this.messageTarget = messageTarget; - } - static IMPLEMENTS = { - [TFilesystem]: { - stat: async function (o) { - return example; - }, - readdir: async function (o) { - const tp = new putility.libs.promise.TeePromise(); - const $callback = this.rpc.registerCallback((result) => { - tp.resolve(result); - }); - // return [example]; - this.messageTarget.postMessage({ - $: 'puter-fs', - $callback, - op: 'readdir', - args: o, - }, '*'); - - return await tp; - }, - }, - }; -} diff --git a/src/puter-js/src/lib/filesystem/definitions.js b/src/puter-js/src/lib/filesystem/definitions.js deleted file mode 100644 index f65bb2778..000000000 --- a/src/puter-js/src/lib/filesystem/definitions.js +++ /dev/null @@ -1,40 +0,0 @@ -import putility from '@heyputer/putility'; - -export const TFilesystem = 'TFilesystem'; - -// TODO: UNUSED (eventually putility will support these definitions) -// This is here so that the idea is not forgotten. -export const IFilesystem = { - methods: { - stat: { - parameters: { - path: { - alias: 'uid', - }, - }, - }, - }, - -}; - -export class ProxyFilesystem extends putility.AdvancedBase { - static PROPERTIES = { - delegate: () => { - }, - }; - // TODO: constructor implied by properties - constructor ({ delegate }) { - super(); - this.delegate = delegate; - } - static IMPLEMENTS = { - [TFilesystem]: { - stat: async function (o) { - return this.delegate.stat(o); - }, - readdir: async function (o) { - return this.delegate.readdir(o); - }, - }, - }; -} diff --git a/src/puter-js/src/lib/utils.js b/src/puter-js/src/lib/utils.js index 6c2dbe02e..6777ccb53 100644 --- a/src/puter-js/src/lib/utils.js +++ b/src/puter-js/src/lib/utils.js @@ -65,6 +65,16 @@ function uuidv4 () { (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); } +const createDeferred = () => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +}; + /** * Initializes and returns an XMLHttpRequest object configured for a specific API endpoint, method, and headers. * @@ -266,11 +276,11 @@ function make_driver_method (arg_defs, driverInterface, driverName, driverMethod } async function driverCall (options, driverInterface, driverName, driverMethod, driverArgs, settings) { - const tp = new TeePromise(); + const deferred = createDeferred(); driverCall_(options, - tp.resolve.bind(tp), - tp.reject.bind(tp), + deferred.resolve, + deferred.reject, driverInterface, driverName, driverMethod, @@ -279,7 +289,7 @@ async function driverCall (options, driverInterface, driverName, driverMethod, d undefined, settings); - return await tp; + return await deferred.promise; } // This function encapsulates the logic for sending a driver call request @@ -368,9 +378,9 @@ async function driverCall_ ( is_stream = true; const Stream = async function* Stream () { while ( !response_complete ) { - const tp = new TeePromise(); - signal_stream_update = tp.resolve.bind(tp); - await tp; + const signal = createDeferred(); + signal_stream_update = signal.resolve; + await signal.promise; if ( response_complete ) break; while ( lines_received.length > 0 ) { const line = lines_received.shift(); @@ -543,58 +553,16 @@ async function driverCall_ ( args: driverArgs, auth_token: puter.authToken, })); -} -class TeePromise { - static STATUS_PENDING = {}; - static STATUS_RUNNING = {}; - static STATUS_DONE = {}; - constructor () { - this.status_ = this.constructor.STATUS_PENDING; - this.donePromise = new Promise((resolve, reject) => { - this.doneResolve = resolve; - this.doneReject = reject; - }); - } - get status () { - return this.status_; - } - set status (status) { - this.status_ = status; - if ( status === this.constructor.STATUS_DONE ) { - this.doneResolve(); - } - } - resolve (value) { - this.status_ = this.constructor.STATUS_DONE; - this.doneResolve(value); - } - awaitDone () { - return this.donePromise; - } - then (fn, rfn) { - return this.donePromise.then(fn, rfn); - } - - reject (err) { - this.status_ = this.constructor.STATUS_DONE; - this.doneReject(err); - } - - /** - * @deprecated use then() instead - */ - onComplete (fn) { - return this.then(fn); - } } async function blob_to_url (blob) { - const tp = new TeePromise(); const reader = new (globalThis.FileReader || FileReaderPoly)(); - reader.onloadend = () => tp.resolve(reader.result); - reader.readAsDataURL(blob); - return await tp; + return await new Promise((resolve, reject) => { + reader.onloadend = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsDataURL(blob); + }); } function blobToDataUri (blob) { @@ -625,5 +593,5 @@ function arrayBufferToDataUri (arrayBuffer) { } export { - arrayBufferToDataUri, blob_to_url, blobToDataUri, driverCall, handle_error, handle_resp, initXhr, make_driver_method, parseResponse, setupXhrEventHandlers, TeePromise, uuidv4 + arrayBufferToDataUri, blob_to_url, blobToDataUri, driverCall, handle_error, handle_resp, initXhr, make_driver_method, parseResponse, setupXhrEventHandlers, uuidv4 }; diff --git a/src/puter-js/src/modules/AI.js b/src/puter-js/src/modules/AI.js index 2a18cadb1..7c62e17d7 100644 --- a/src/puter-js/src/modules/AI.js +++ b/src/puter-js/src/modules/AI.js @@ -11,33 +11,6 @@ const normalizeTTSProvider = (value) => { return value; }; -const TOGETHER_IMAGE_MODEL_PREFIXES = [ - 'black-forest-labs/', - 'stabilityai/', - 'togethercomputer/', - 'playgroundai/', - 'runwayml/', - 'lightricks/', - 'sg161222/', - 'wavymulder/', - 'prompthero/', - 'bytedance-seed/', - 'hidream-ai/', - 'lykon/', - 'qwen/', - 'rundiffusion/', - 'google/', - 'ideogram/', -]; - -const TOGETHER_IMAGE_MODEL_KEYWORDS = [ - 'flux', - 'kling', - 'sd3', - 'stable-diffusion', - 'kolors', -]; - const TOGETHER_VIDEO_MODEL_PREFIXES = [ 'minimax/', 'google/', @@ -57,10 +30,11 @@ class AI { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; } /** @@ -666,7 +640,7 @@ class AI { let testMode = false; // default driver is openai-completion - let driver = 'openai-completion'; + let driver = 'ai-chat'; // Check that the argument is not undefined or null if ( ! args ) { @@ -765,160 +739,6 @@ class AI { // convert undefined to empty string so that .startsWith works requestParams.model = requestParams.model ?? ''; - // If model starts with "anthropic/", remove it - // later on we should standardize the model names to [vendor]/[model] - // for example: "claude-3-5-sonnet" should become "anthropic/claude-3-5-sonnet" - // but for now, we want to keep the old behavior - // so we remove the "anthropic/" prefix if it exists - if ( requestParams.model && requestParams.model.startsWith('anthropic/') ) { - requestParams.model = requestParams.model.replace('anthropic/', ''); - } - - // convert to the correct model name if necessary - if ( requestParams.model === 'claude-3-5-sonnet' ) { - requestParams.model = 'claude-3-5-sonnet-latest'; - } - if ( requestParams.model === 'claude-3-7-sonnet' || requestParams.model === 'claude' ) { - requestParams.model = 'claude-3-7-sonnet-latest'; - } - if ( requestParams.model === 'claude-sonnet-4' || requestParams.model === 'claude-sonnet-4-latest' ) { - requestParams.model = 'claude-sonnet-4-20250514'; - } - if ( requestParams.model === 'claude-opus-4' || requestParams.model === 'claude-opus-4-latest' ) { - requestParams.model = 'claude-opus-4-20250514'; - } - if ( requestParams.model === 'mistral' ) { - requestParams.model = 'mistral-large-latest'; - } - if ( requestParams.model === 'groq' ) { - requestParams.model = 'llama3-8b-8192'; - } - if ( requestParams.model === 'deepseek' ) { - requestParams.model = 'deepseek-chat'; - } - - // o1-mini to openrouter:openai/o1-mini - if ( requestParams.model === 'o1-mini' ) { - requestParams.model = 'openrouter:openai/o1-mini'; - } - - // if a model is prepended with "openai/", remove it - if ( requestParams.model && requestParams.model.startsWith('openai/') ) { - requestParams.model = requestParams.model.replace('openai/', ''); - driver = 'openai-completion'; - } - // For the following providers, we need to prepend "openrouter:" to the model name so that the backend driver can handle it - if ( - requestParams.model.startsWith('agentica-org/') || - requestParams.model.startsWith('ai21/') || - requestParams.model.startsWith('aion-labs/') || - requestParams.model.startsWith('alfredpros/') || - requestParams.model.startsWith('allenai/') || - requestParams.model.startsWith('alpindale/') || - requestParams.model.startsWith('amazon/') || - requestParams.model.startsWith('anthracite-org/') || - requestParams.model.startsWith('arcee-ai/') || - requestParams.model.startsWith('arliai/') || - requestParams.model.startsWith('baidu/') || - requestParams.model.startsWith('bytedance/') || - requestParams.model.startsWith('cognitivecomputations/') || - requestParams.model.startsWith('cohere/') || - requestParams.model.startsWith('deepseek/') || - requestParams.model.startsWith('eleutherai/') || - requestParams.model.startsWith('google/') || - requestParams.model.startsWith('gryphe/') || - requestParams.model.startsWith('inception/') || - requestParams.model.startsWith('infermatic/') || - requestParams.model.startsWith('liquid/') || - requestParams.model.startsWith('mancer/') || - requestParams.model.startsWith('meta-llama/') || - requestParams.model.startsWith('microsoft/') || - requestParams.model.startsWith('minimax/') || - requestParams.model.startsWith('mistralai/') || - requestParams.model.startsWith('moonshotai/') || - requestParams.model.startsWith('morph/') || - requestParams.model.startsWith('neversleep/') || - requestParams.model.startsWith('nousresearch/') || - requestParams.model.startsWith('nvidia/') || - requestParams.model.startsWith('openrouter/') || - requestParams.model.startsWith('perplexity/') || - requestParams.model.startsWith('pygmalionai/') || - requestParams.model.startsWith('qwen/') || - requestParams.model.startsWith('raifle/') || - requestParams.model.startsWith('rekaai/') || - requestParams.model.startsWith('sao10k/') || - requestParams.model.startsWith('sarvamai/') || - requestParams.model.startsWith('scb10x/') || - requestParams.model.startsWith('shisa-ai/') || - requestParams.model.startsWith('sophosympatheia/') || - requestParams.model.startsWith('switchpoint/') || - requestParams.model.startsWith('tencent/') || - requestParams.model.startsWith('thedrummer/') || - requestParams.model.startsWith('thudm/') || - requestParams.model.startsWith('tngtech/') || - requestParams.model.startsWith('undi95/') || - requestParams.model.startsWith('x-ai/') || - requestParams.model.startsWith('z-ai/') - ) { - requestParams.model = `openrouter:${ requestParams.model}`; - } - - // map model to the appropriate driver - if ( !requestParams.model || requestParams.model.startsWith('gpt-') ) { - driver = 'openai-completion'; - } else if ( - requestParams.model.startsWith('claude-') - ) { - driver = 'claude'; - } else if ( requestParams.model === 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo' || requestParams.model === 'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo' || requestParams.model === 'google/gemma-2-27b-it' ) { - driver = 'together-ai'; - } else if ( requestParams.model.startsWith('mistral-') || requestParams.model.startsWith('codestral-') || requestParams.model.startsWith('pixtral-') || requestParams.model.startsWith('magistral-') || requestParams.model.startsWith('devstral-') || requestParams.model.startsWith('mistral-ocr-') || requestParams.model.startsWith('open-mistral-') ) { - driver = 'mistral'; - } else if ( [ - 'distil-whisper-large-v3-en', - 'gemma2-9b-it', - 'gemma-7b-it', - 'llama-3.1-70b-versatile', - 'llama-3.1-8b-instant', - 'llama3-70b-8192', - 'llama3-8b-8192', - 'llama3-groq-70b-8192-tool-use-preview', - 'llama3-groq-8b-8192-tool-use-preview', - 'llama-guard-3-8b', - 'mixtral-8x7b-32768', - 'whisper-large-v3', - ].includes(requestParams.model) ) { - driver = 'groq'; - } else if ( requestParams.model === 'grok-beta' ) { - driver = 'xai'; - } - else if ( requestParams.model.startsWith('grok-') ) { - driver = 'openrouter'; - } - else if ( - requestParams.model === 'deepseek-chat' || - requestParams.model === 'deepseek-reasoner' - ) { - driver = 'deepseek'; - } - else if ( - requestParams.model === 'gemini-1.5-flash' || - requestParams.model === 'gemini-2.0-flash' || - requestParams.model === 'gemini-2.5-flash' || - requestParams.model === 'gemini-2.5-flash-lite' || - requestParams.model === 'gemini-2.0-flash-lite' || - requestParams.model === 'gemini-3-pro-preview' || - requestParams.model === 'gemini-2.5-pro' - ) { - driver = 'gemini'; - } - else if ( requestParams.model.startsWith('openrouter:') ) { - driver = 'openrouter'; - } - else if ( requestParams.model.startsWith('ollama:') ) { - driver = 'ollama'; - } - // stream flag from userParams if ( userParams.stream !== undefined && typeof userParams.stream === 'boolean' ) { requestParams.stream = userParams.stream; @@ -1020,27 +840,11 @@ class AI { } const driverHint = typeof options.driver === 'string' ? options.driver : undefined; - const providerRaw = typeof options.provider === 'string' - ? options.provider - : (typeof options.service === 'string' ? options.service : undefined); - const providerHint = typeof providerRaw === 'string' ? providerRaw.toLowerCase() : undefined; - const modelLower = typeof options.model === 'string' ? options.model.toLowerCase() : ''; - - const looksLikeTogetherModel = - typeof options.model === 'string' && - (TOGETHER_IMAGE_MODEL_PREFIXES.some(prefix => modelLower.startsWith(prefix)) || - TOGETHER_IMAGE_MODEL_KEYWORDS.some(keyword => modelLower.includes(keyword))); if ( driverHint ) { AIService = driverHint; - } else if ( providerHint === 'gemini' ) { - AIService = 'gemini-image-generation'; - } else if ( providerHint === 'together' || providerHint === 'together-ai' ) { - AIService = 'together-image-generation'; - } else if (options.model === 'gemini-2.5-flash-image-preview' || options.model === "gemini-3-pro-image-preview" ) { - AIService = 'gemini-image-generation'; - } else if ( looksLikeTogetherModel ) { - AIService = 'together-image-generation'; + } else { + AIService = 'ai-image'; } // Call the original chat.complete method return await utils.make_driver_method(['prompt'], 'puter-image-generation', AIService, 'generate', { diff --git a/src/puter-js/src/modules/Apps.js b/src/puter-js/src/modules/Apps.js index 660a152b9..07922397f 100644 --- a/src/puter-js/src/modules/Apps.js +++ b/src/puter-js/src/modules/Apps.js @@ -9,10 +9,11 @@ class Apps { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; } /** @@ -212,4 +213,4 @@ class Apps { }; } -export default Apps; \ No newline at end of file +export default Apps; diff --git a/src/puter-js/src/modules/Auth.js b/src/puter-js/src/modules/Auth.js index de69a0c81..4f729f613 100644 --- a/src/puter-js/src/modules/Auth.js +++ b/src/puter-js/src/modules/Auth.js @@ -13,10 +13,11 @@ class Auth { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; } /** @@ -291,4 +292,4 @@ class Auth { } } -export default Auth; \ No newline at end of file +export default Auth; diff --git a/src/puter-js/src/modules/Debug.js b/src/puter-js/src/modules/Debug.js index 1ccb22c2c..085b48e01 100644 --- a/src/puter-js/src/modules/Debug.js +++ b/src/puter-js/src/modules/Debug.js @@ -1,6 +1,6 @@ export class Debug { - constructor (context, parameters) { - this.context = context; + constructor (puter, parameters) { + this.puter = puter; this.parameters = parameters; this._init(); @@ -14,7 +14,7 @@ export class Debug { enabled_logs = enabled_logs.split(';'); for ( const category of enabled_logs ) { if ( category === '' ) continue; - this.context.puter.logger.on(category); + this.puter.logger.on(category); } globalThis.addEventListener('message', async e => { @@ -32,7 +32,7 @@ export class Debug { if ( e.data.cmd === 'log.on' ) { console.log('Got instruction to turn logs on!'); - this.context.puter.logger.on(e.data.category); + this.puter.logger.on(e.data.category); } }); } diff --git a/src/puter-js/src/modules/Drivers.js b/src/puter-js/src/modules/Drivers.js index 8abbb1c71..6199ad6ec 100644 --- a/src/puter-js/src/modules/Drivers.js +++ b/src/puter-js/src/modules/Drivers.js @@ -1,6 +1,7 @@ class FetchDriverCallBackend { - constructor ({ context }) { - this.context = context; + constructor ({ getAPIOrigin, getAuthToken }) { + this.getAPIOrigin = getAPIOrigin; + this.getAuthToken = getAuthToken; this.response_handlers = this.constructor.response_handlers; } @@ -32,7 +33,7 @@ class FetchDriverCallBackend { async call ({ driver, method_name, parameters }) { try { - const resp = await fetch(`${this.context.APIOrigin}/drivers/call`, { + const resp = await fetch(`${this.getAPIOrigin()}/drivers/call`, { headers: { 'Content-Type': 'text/plain;actually=json', }, @@ -44,7 +45,7 @@ class FetchDriverCallBackend { : {}), method: method_name, args: parameters, - auth_token: this.context.authToken, + auth_token: this.getAuthToken(), }), }); @@ -131,22 +132,15 @@ class Drivers { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; // Driver-specific this.drivers_ = {}; - // TODO: replace with `context` from constructor and test site login - this.context = {}; - Object.defineProperty(this.context, 'authToken', { - get: () => this.authToken, - }); - Object.defineProperty(this.context, 'APIOrigin', { - get: () => this.APIOrigin, - }); } _init ({ puter }) { @@ -226,7 +220,8 @@ class Drivers { return this.drivers_[key] = new Driver ({ call_backend: new FetchDriverCallBackend({ - context: this.context, + getAPIOrigin: () => this.APIOrigin, + getAuthToken: () => this.authToken, }), // iface: interfaces[iface_name], iface_name, diff --git a/src/puter-js/src/modules/FileSystem/index.js b/src/puter-js/src/modules/FileSystem/index.js index 0ef4309a8..025aef5b5 100644 --- a/src/puter-js/src/modules/FileSystem/index.js +++ b/src/puter-js/src/modules/FileSystem/index.js @@ -20,14 +20,11 @@ import stat from './operations/stat.js'; import symlink from './operations/symlink.js'; import upload from './operations/upload.js'; import write from './operations/write.js'; -// Why is this called deleteFSEntry instead of just delete? because delete is -// a reserved keyword in javascript -import { AdvancedBase } from '../../../../putility/index.js'; import FSItem from '../FSItem.js'; import deleteFSEntry from './operations/deleteFSEntry.js'; import getReadURL from './operations/getReadUrl.js'; -export class PuterJSFileSystemModule extends AdvancedBase { +export class PuterJSFileSystemModule { space = space; mkdir = mkdir; @@ -48,16 +45,7 @@ export class PuterJSFileSystemModule extends AdvancedBase { FSItem = FSItem; - static NARI_METHODS = { - // stat: { - // positional: ['path'], - // firstarg_options: true, - // async fn (parameters) { - // const svc_fs = await this.context.services.aget('filesystem'); - // return svc_fs.filesystem.stat(parameters); - // } - // }, - }; + static NARI_METHODS = {}; /** * Creates a new instance with the given authentication token, API origin, and app ID, @@ -68,12 +56,11 @@ export class PuterJSFileSystemModule extends AdvancedBase { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - super(); - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; - this.context = context; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; this.cacheUpdateTimer = null; // Connect socket. this.initializeSocket(); @@ -107,7 +94,7 @@ export class PuterJSFileSystemModule extends AdvancedBase { auth: { auth_token: this.authToken, }, - autoUnref: this.context.env === 'nodejs', + autoUnref: this.puter.env === 'nodejs', }); this.bindSocketEvents(); @@ -212,7 +199,7 @@ export class PuterJSFileSystemModule extends AdvancedBase { this.authToken = authToken; // Check cache timestamp and purge if needed (only in GUI environment) - if ( this.context.env === 'gui' ) { + if ( this.puter.env === 'gui' ) { this.checkCacheAndPurge(); // Start background task to update LAST_VALID_TS every 1 second this.startCacheUpdateTimer(); @@ -305,7 +292,7 @@ export class PuterJSFileSystemModule extends AdvancedBase { * @returns {void} */ startCacheUpdateTimer () { - if ( this.context.env !== 'gui' ) { + if ( this.puter.env !== 'gui' ) { return; } diff --git a/src/puter-js/src/modules/Hosting.js b/src/puter-js/src/modules/Hosting.js index 3c10434d4..fba047435 100644 --- a/src/puter-js/src/modules/Hosting.js +++ b/src/puter-js/src/modules/Hosting.js @@ -10,10 +10,11 @@ class Hosting { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; } /** diff --git a/src/puter-js/src/modules/KV.js b/src/puter-js/src/modules/KV.js index 781a2bb33..4e753362d 100644 --- a/src/puter-js/src/modules/KV.js +++ b/src/puter-js/src/modules/KV.js @@ -1,6 +1,15 @@ -import { TeePromise } from '@heyputer/putility/src/libs/promise.js'; import * as utils from '../lib/utils.js'; +const createDeferred = () => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +}; + const gui_cache_keys = [ 'has_set_default_app_user_permissions', 'window_sidebar_width', @@ -29,15 +38,16 @@ class KV { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; - this.gui_cached = new TeePromise(); - this.gui_cache_init = new TeePromise(); + this.gui_cached = createDeferred(); + this.gui_cache_init = createDeferred(); (async () => { - await this.gui_cache_init; + await this.gui_cache_init.promise; this.gui_cache_init = null; const resp = await fetch(`${this.APIOrigin}/drivers/call`, { method: 'POST', @@ -142,7 +152,7 @@ class KV { this.gui_cached !== null ) { this.gui_cache_init && this.gui_cache_init.resolve(); - const cache = await this.gui_cached; + const cache = await this.gui_cached.promise; return cache[args[0]]; } @@ -323,4 +333,4 @@ function globMatch (pattern, str) { return re.test(str); } -export default KV; \ No newline at end of file +export default KV; diff --git a/src/puter-js/src/modules/OS.js b/src/puter-js/src/modules/OS.js index 97fb8c053..c51f02dca 100644 --- a/src/puter-js/src/modules/OS.js +++ b/src/puter-js/src/modules/OS.js @@ -9,10 +9,11 @@ class OS { * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs. * @param {string} appID - ID of the app to use. */ - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; - this.appID = context.appID; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; + this.appID = puter.appID; } /** @@ -92,4 +93,4 @@ class OS { }; } -export default OS; \ No newline at end of file +export default OS; diff --git a/src/puter-js/src/modules/Perms.js b/src/puter-js/src/modules/Perms.js index dcce2f6e9..c819a9c6b 100644 --- a/src/puter-js/src/modules/Perms.js +++ b/src/puter-js/src/modules/Perms.js @@ -1,7 +1,8 @@ export default class Perms { - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; } setAuthToken (authToken) { this.authToken = authToken; diff --git a/src/puter-js/src/modules/Threads.js b/src/puter-js/src/modules/Threads.js index ad6884dd4..a8af2e99a 100644 --- a/src/puter-js/src/modules/Threads.js +++ b/src/puter-js/src/modules/Threads.js @@ -1,9 +1,10 @@ import { RequestError } from '../lib/RequestError.js'; export default class Threads { - constructor (context) { - this.authToken = context.authToken; - this.APIOrigin = context.APIOrigin; + constructor (puter) { + this.puter = puter; + this.authToken = puter.authToken; + this.APIOrigin = puter.APIOrigin; } setAuthToken (authToken) { this.authToken = authToken; diff --git a/src/puter-js/src/modules/UI.js b/src/puter-js/src/modules/UI.js index 431badfc3..bf9baa4f2 100644 --- a/src/puter-js/src/modules/UI.js +++ b/src/puter-js/src/modules/UI.js @@ -1,8 +1,17 @@ -import putility from '@heyputer/putility'; import EventListener from '../lib/EventListener.js'; import FSItem from './FSItem.js'; import PuterDialog from './PuterDialog.js'; +const createDeferred = () => { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +}; + const FILE_SAVE_CANCELLED = Symbol('FILE_SAVE_CANCELLED'); const FILE_OPEN_CANCELLED = Symbol('FILE_OPEN_CANCELLED'); @@ -22,10 +31,12 @@ class AppConnection extends EventListener { // (Closing and close events will still function.) #usesSDK; - static from (values, context) { - const connection = new AppConnection(context, { + static from (values, puter, { messageTarget, appInstanceID }) { + const connection = new AppConnection(puter, { target: values.appInstanceID, usesSDK: values.usesSDK, + messageTarget, + appInstanceID, }); // When a connection is established the app is able to @@ -35,23 +46,23 @@ class AppConnection extends EventListener { return connection; } - constructor (context, { target, usesSDK }) { + constructor (puter, { target, usesSDK, messageTarget, appInstanceID }) { super([ 'message', // The target sent us something with postMessage() 'close', // The target app was closed ]); - this.messageTarget = context.messageTarget; - this.appInstanceID = context.appInstanceID; + this.messageTarget = messageTarget; + this.appInstanceID = appInstanceID; this.targetAppInstanceID = target; this.#isOpen = true; this.#usesSDK = usesSDK; - this.log = context.puter.logger.fields({ + this.log = puter.logger.fields({ category: 'ipc', }); this.log.fields({ - cons_source: context.appInstanceID, - source: context.puter.appInstanceID, + cons_source: appInstanceID, + source: puter.appInstanceID, target, }).info(`AppConnection created to ${target}`, this); @@ -232,7 +243,7 @@ class UI extends EventListener { return ret; }; - constructor (context, { appInstanceID, parentInstanceID }) { + constructor (puter, { appInstanceID, parentInstanceID }) { const eventNames = [ 'localeChanged', 'themeChanged', @@ -240,12 +251,12 @@ class UI extends EventListener { ]; super(eventNames); this.#eventNames = eventNames; - this.context = context; + this.puter = puter; this.appInstanceID = appInstanceID; this.parentInstanceID = parentInstanceID; - this.appID = context.appID; - this.env = context.env; - this.util = context.util; + this.appID = puter.appID; + this.env = puter.env; + this.util = puter.util; if ( this.env === 'app' ) { this.messageTarget = window.parent; @@ -254,16 +265,12 @@ class UI extends EventListener { return; } - // Context to pass to AppConnection instances - this.context = this.context.sub({ - appInstanceID: this.appInstanceID, - messageTarget: this.messageTarget, - }); - if ( this.parentInstanceID ) { - this.#parentAppConnection = new AppConnection(this.context, { + this.#parentAppConnection = new AppConnection(this.puter, { target: this.parentInstanceID, usesSDK: true, + messageTarget: this.messageTarget, + appInstanceID: this.appInstanceID, }); } @@ -535,7 +542,10 @@ class UI extends EventListener { } else if ( e.data.msg === 'connection' ) { e.data.usesSDK = true; // we can safely assume this - const conn = AppConnection.from(e.data, this.context); + const conn = AppConnection.from(e.data, this.puter, { + messageTarget: this.messageTarget, + appInstanceID: this.appInstanceID, + }); const accept = value => { this.messageTarget?.postMessage({ $: 'connection-resp', @@ -743,7 +753,7 @@ class UI extends EventListener { }; showOpenFilePicker (options, callback) { - const undefinedOnCancel = new putility.libs.promise.TeePromise(); + const undefinedOnCancel = createDeferred(); const resolveOnlyPromise = new Promise((resolve, reject) => { if ( ! globalThis.open ) { return reject('This API is not compatible in Web Workers.'); @@ -779,7 +789,7 @@ class UI extends EventListener { resolve(maybe_result); }; }); - resolveOnlyPromise.undefinedOnCancel = undefinedOnCancel; + resolveOnlyPromise.undefinedOnCancel = undefinedOnCancel.promise; return resolveOnlyPromise; }; @@ -802,7 +812,7 @@ class UI extends EventListener { }; showSaveFilePicker (content, suggestedName, type) { - const undefinedOnCancel = new putility.libs.promise.TeePromise(); + const undefinedOnCancel = createDeferred(); const resolveOnlyPromise = new Promise((resolve, reject) => { if ( ! globalThis.open ) { return reject('This API is not compatible in Web Workers.'); @@ -870,7 +880,7 @@ class UI extends EventListener { }; }); - resolveOnlyPromise.undefinedOnCancel = undefinedOnCancel; + resolveOnlyPromise.undefinedOnCancel = undefinedOnCancel.promise; return resolveOnlyPromise; }; @@ -1203,7 +1213,10 @@ class UI extends EventListener { }, }); - return AppConnection.from(app_info, this.context); + return AppConnection.from(app_info, this.puter, { + messageTarget: this.messageTarget, + appInstanceID: this.appInstanceID, + }); }; connectToInstance = async function connectToInstance (app_name) { @@ -1214,7 +1227,10 @@ class UI extends EventListener { }, }); - return AppConnection.from(app_info, this.context); + return AppConnection.from(app_info, this.puter, { + messageTarget: this.messageTarget, + appInstanceID: this.appInstanceID, + }); }; parentApp () { diff --git a/src/puter-js/src/services/APIAccess.js b/src/puter-js/src/services/APIAccess.js deleted file mode 100644 index 81958547f..000000000 --- a/src/puter-js/src/services/APIAccess.js +++ /dev/null @@ -1,46 +0,0 @@ -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 (v) { - this.as(TTopics).pub('update'); - }, - }, - api_origin: { - post_set () { - this.as(TTopics).pub('update'); - }, - }, - }; - - // TODO: inconsistent! Update all dependents. - get_api_info () { - const self = this; - const o = {}; - [ - ['auth_token', 'auth_token'], - ['authToken', 'auth_token'], - ['APIOrigin', 'api_origin'], - ['api_origin', 'api_origin'], - ].forEach(([k1, k2]) => { - Object.defineProperty(o, k1, { - get () { - return self[k2]; - }, - set (v) { - return self; - }, - }); - }); - return o; - } -} diff --git a/src/puter-js/src/services/FSRelay.js b/src/puter-js/src/services/FSRelay.js deleted file mode 100644 index 75bd3b21f..000000000 --- a/src/puter-js/src/services/FSRelay.js +++ /dev/null @@ -1,20 +0,0 @@ -import putility from '@heyputer/putility'; - -const example = { - 'id': 'f485f1ba-de07-422c-8c4b-c2da057d4a44', - 'uid': 'f485f1ba-de07-422c-8c4b-c2da057d4a44', - 'is_dir': true, - 'immutable': true, - 'name': 'FromParentWindow', -}; - -export class FSRelayService extends putility.concepts.Service { - async _init () { - const services = this._.context.services; - const util = this._.context.util; - const svc_xdIncoming = services.get('xd-incoming'); - svc_xdIncoming.register_tagged_listener('puter-fs', event => { - util.rpc.send(event.source, event.data.$callback, [example]); - }); - } -} diff --git a/src/puter-js/src/services/Filesystem.js b/src/puter-js/src/services/Filesystem.js deleted file mode 100644 index 096f05129..000000000 --- a/src/puter-js/src/services/Filesystem.js +++ /dev/null @@ -1,137 +0,0 @@ -import putility from '@heyputer/putility'; -import { PuterAPIFilesystem } from '../lib/filesystem/APIFS.js'; -import { CachedFilesystem } from '../lib/filesystem/CacheFS.js'; -import { ProxyFilesystem, TFilesystem } from '../lib/filesystem/definitions.js'; -import { PostMessageFilesystem } from '../lib/filesystem/PostMessageFS.js'; -import io from '../lib/socket.io/socket.io.esm.min.js'; - -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 () { - this.initializeSocket(); - }, - }, - ]; - - _init () { - const env = this._.context.env; - - if ( env === 'app' ) { - // TODO: uncomment when relay is ready - // this.init_app_fs_(); - - this.init_top_fs_(); - } else { - this.init_top_fs_(); - } - - this.initializeSocket(); - } - - init_app_fs_ () { - this.fs_nocache_ = new PostMessageFilesystem({ - messageTarget: globalThis.parent, - rpc: this._.context.util.rpc, - }).as(TFilesystem); - this.filesystem = this.fs_nocache_; - } - init_top_fs_ () { - const api_info = this._.context.services.get('api-access').get_api_info(); - this.fs_nocache_ = new PuterAPIFilesystem({ api_info }).as(TFilesystem); - this.fs_cache_ = new CachedFilesystem({ delegate: this.fs_nocache_ }).as(TFilesystem); - // this.filesystem = this.fs_nocache; - this.fs_proxy_ = new ProxyFilesystem({ delegate: this.fs_nocache_ }); - this.filesystem = this.fs_proxy_.as(TFilesystem); - } - - cache_on () { - this.fs_proxy_.delegate = this.fs_cache_; - } - cache_off () { - this.fs_proxy_.delegate = this.fs_nocache_; - } - - async initializeSocket () { - if ( this.socket ) { - this.socket.disconnect(); - } - - const svc_apiAccess = this._.context.services.get('api-access'); - const api_info = svc_apiAccess.get_api_info(); - - if ( api_info.api_origin === undefined ) { - // This will get called again later with updated information - return; - } - - this.socket = io(api_info.api_origin, { - auth: { auth_token: api_info.auth_token }, - autoUnref: this._.context.env === 'nodejs', - }); - - 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 deleted file mode 100644 index 6911e1f0a..000000000 --- a/src/puter-js/src/services/NoPuterYet.js +++ /dev/null @@ -1,20 +0,0 @@ -import putility from '@heyputer/putility'; - -/** - * Runs commands on the special `globalThis.when_puter_happens` global, for - * situations where the `puter` global doesn't exist soon enough. - */ -export class NoPuterYetService extends putility.concepts.Service { - _init () { - if ( ! globalThis.when_puter_happens ) return; - if ( puter && puter.env !== 'gui' ) return; - - if ( ! Array.isArray(globalThis.when_puter_happens) ) { - globalThis.when_puter_happens = [globalThis.when_puter_happens]; - } - - for ( const fn of globalThis.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 deleted file mode 100644 index f8f3caf3f..000000000 --- a/src/puter-js/src/services/XDIncoming.js +++ /dev/null @@ -1,44 +0,0 @@ -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 () { - globalThis.addEventListener('message', async event => { - for ( const fn of this.filter_listeners_ ) { - const tp = new TeePromise(); - fn(event, tp); - if ( await tp ) return; - } - - const data = event.data; - if ( ! data ) return; - const tag = data.$; - if ( ! tag ) return; - if ( ! this.tagged_listeners_[tag] ) return; - - for ( const fn of this.tagged_listeners_[tag] ) { - fn({ data, source: event.source }); - } - }); - } - - register_filter_listener (fn) { - this.filter_listeners_.push(fn); - } - - register_tagged_listener (tag, fn) { - if ( ! this.tagged_listeners_[tag] ) { - this.tagged_listeners_[tag] = []; - } - this.tagged_listeners_[tag].push(fn); - } -}