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); - } -}