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
This commit is contained in:
Nariman Jelveh
2025-12-12 19:44:15 -08:00
committed by GitHub
parent ef69865dd1
commit ae92233b95
26 changed files with 272 additions and 1108 deletions
+1 -2
View File
@@ -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",
+17 -3
View File
@@ -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);
});
});
+2 -3
View File
@@ -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"
}
}
+100 -80
View File
@@ -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;
};
}
});
});
-65
View File
@@ -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.');
}
}
}
}
-242
View File
@@ -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;
},
},
};
}
@@ -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;
},
},
};
}
@@ -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);
},
},
};
}
+23 -55
View File
@@ -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
};
+8 -204
View File
@@ -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', {
+6 -5
View File
@@ -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;
export default Apps;
+6 -5
View File
@@ -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;
export default Auth;
+4 -4
View File
@@ -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);
}
});
}
+12 -17
View File
@@ -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,
+10 -23
View File
@@ -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;
}
+5 -4
View File
@@ -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;
}
/**
+20 -10
View File
@@ -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;
export default KV;
+6 -5
View File
@@ -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;
export default OS;
+4 -3
View File
@@ -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;
+4 -3
View File
@@ -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;
+44 -28
View File
@@ -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 () {
-46
View File
@@ -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;
}
}
-20
View File
@@ -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]);
});
}
}
-137
View File
@@ -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);
}
});
}
}
-20
View File
@@ -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 });
}
}
}
-44
View File
@@ -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);
}
}