@heyputer/puter.js 2.1.15 → 2.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/puter.cjs +2 -2
  2. package/index.d.ts +0 -2
  3. package/package.json +2 -3
  4. package/src/index.js +100 -82
  5. package/src/lib/utils.js +42 -55
  6. package/src/modules/AI.js +8 -204
  7. package/src/modules/Apps.js +42 -11
  8. package/src/modules/Auth.js +6 -5
  9. package/src/modules/Debug.js +4 -4
  10. package/src/modules/Drivers.js +12 -17
  11. package/src/modules/FileSystem/index.js +9 -24
  12. package/src/modules/Hosting.js +5 -4
  13. package/src/modules/KV.js +67 -11
  14. package/src/modules/OS.js +6 -5
  15. package/src/modules/Perms.js +4 -3
  16. package/src/modules/PuterDialog.js +2 -2
  17. package/src/modules/UI.js +44 -28
  18. package/src/modules/UsageLimitDialog.js +208 -0
  19. package/types/modules/ai.d.ts +0 -10
  20. package/types/modules/apps.d.ts +24 -15
  21. package/types/modules/auth.d.ts +3 -1
  22. package/types/modules/filesystem.d.ts +0 -2
  23. package/types/modules/kv.d.ts +10 -0
  24. package/types/modules/networking.d.ts +1 -1
  25. package/types/modules/os.d.ts +2 -7
  26. package/types/modules/ui.d.ts +10 -7
  27. package/types/modules/util.d.ts +0 -1
  28. package/types/modules/workers.d.ts +9 -10
  29. package/types/puter.d.ts +1 -7
  30. package/src/lib/filesystem/APIFS.js +0 -65
  31. package/src/lib/filesystem/CacheFS.js +0 -243
  32. package/src/lib/filesystem/PostMessageFS.js +0 -40
  33. package/src/lib/filesystem/definitions.js +0 -40
  34. package/src/modules/Threads.js +0 -72
  35. package/src/services/APIAccess.js +0 -46
  36. package/src/services/FSRelay.js +0 -20
  37. package/src/services/Filesystem.js +0 -137
  38. package/src/services/NoPuterYet.js +0 -20
  39. package/src/services/XDIncoming.js +0 -44
  40. package/types/modules/threads.d.ts +0 -27
package/index.d.ts CHANGED
@@ -11,7 +11,6 @@ import type { KV, KVIncrementPath, KVPair } from './types/modules/kv.d.ts';
11
11
  import type { Networking, PSocket, PTLSSocket } from './types/modules/networking.d.ts';
12
12
  import type { OS } from './types/modules/os.d.ts';
13
13
  import type { Perms } from './types/modules/perms.d.ts';
14
- import type Threads from './types/modules/threads.d.ts';
15
14
  import type { AlertButton, AppConnection, AppConnectionCloseEvent, CancelAwarePromise, ContextMenuItem, ContextMenuOptions, DirectoryPickerOptions, FilePickerOptions, LaunchAppOptions, MenuItem, MenubarOptions, ThemeData, UI, WindowOptions } from './types/modules/ui.d.ts';
16
15
  import type Util, { UtilRPC } from './types/modules/util.d.ts';
17
16
  import type { WorkerDeployment, WorkerInfo, WorkersHandler } from './types/modules/workers.d.ts';
@@ -93,7 +92,6 @@ export type {
93
92
  Speech2TxtOptions,
94
93
  Subdomain,
95
94
  ThemeData,
96
- Threads,
97
95
  ToolSchema,
98
96
  Txt2ImgOptions,
99
97
  Txt2SpeechCallable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyputer/puter.js",
3
- "version": "2.1.15",
3
+ "version": "2.2.4",
4
4
  "description": "Puter.js - A JavaScript library for interacting with Puter services.",
5
5
  "homepage": "https://developer.puter.com",
6
6
  "main": "src/index.js",
@@ -42,7 +42,6 @@
42
42
  "webpack-cli": "^5.1.4"
43
43
  },
44
44
  "dependencies": {
45
- "@heyputer/kv.js": "^0.2.1",
46
- "@heyputer/putility": "^1.1.1"
45
+ "@heyputer/kv.js": "^0.2.1"
47
46
  }
48
47
  }
package/src/index.js CHANGED
@@ -1,5 +1,3 @@
1
- import putility from '@heyputer/putility';
2
-
3
1
  import kvjs from '@heyputer/kv.js';
4
2
  import APICallLogger from './lib/APICallLogger.js';
5
3
  import path from './lib/path.js';
@@ -20,16 +18,72 @@ import { PTLSSocket } from './modules/networking/PTLS.js';
20
18
  import { pFetch } from './modules/networking/requests.js';
21
19
  import OS from './modules/OS.js';
22
20
  import Perms from './modules/Perms.js';
23
- import Threads from './modules/Threads.js';
24
21
  import UI from './modules/UI.js';
25
22
  import Util from './modules/Util.js';
26
23
  import { WorkersHandler } from './modules/Workers.js';
27
- import { APIAccessService } from './services/APIAccess.js';
28
- import { FilesystemService } from './services/Filesystem.js';
29
- import { FSRelayService } from './services/FSRelay.js';
30
- import { NoPuterYetService } from './services/NoPuterYet.js';
31
- import { XDIncomingService } from './services/XDIncoming.js';
32
24
 
25
+ class SimpleLogger {
26
+ constructor (fields = {}) {
27
+ this.fieldsObj = fields;
28
+ this.enabled = new Set();
29
+ }
30
+
31
+ on (category) {
32
+ this.enabled.add(category);
33
+ }
34
+
35
+ fields (extra = {}) {
36
+ return new SimpleLogger({ ...this.fieldsObj, ...extra });
37
+ }
38
+
39
+ info (...args) {
40
+ console.log(...this._prefix(), ...args);
41
+ }
42
+
43
+ warn (...args) {
44
+ console.warn(...this._prefix(), ...args);
45
+ }
46
+
47
+ error (...args) {
48
+ console.error(...this._prefix(), ...args);
49
+ }
50
+
51
+ debug (...args) {
52
+ console.debug(...this._prefix(), ...args);
53
+ }
54
+
55
+ _prefix () {
56
+ const entries = Object.entries(this.fieldsObj);
57
+ if ( !entries.length ) return [];
58
+ return [`[${ entries.map(([k, v]) => `${k}=${v}`).join(' ')}]`];
59
+ }
60
+ }
61
+
62
+ class Lock {
63
+ constructor () {
64
+ this.locked = false;
65
+ this.queue = [];
66
+ }
67
+
68
+ async acquire () {
69
+ if ( !this.locked ) {
70
+ this.locked = true;
71
+ return;
72
+ }
73
+
74
+ await new Promise(resolve => this.queue.push(resolve));
75
+ this.locked = true;
76
+ }
77
+
78
+ release () {
79
+ const next = this.queue.shift();
80
+ if ( next ) {
81
+ next();
82
+ return;
83
+ }
84
+ this.locked = false;
85
+ }
86
+ }
33
87
  // TODO: This is for a safe-guard below; we should check if we can
34
88
  // generalize this behavior rather than hard-coding it.
35
89
  // (using defaultGUIOrigin breaks locally-hosted apps)
@@ -115,7 +169,6 @@ const puterInit = (function () {
115
169
  this.registerModule('apps', Apps);
116
170
  this.registerModule('ai', AI);
117
171
  this.registerModule('kv', KV);
118
- this.registerModule('threads', Threads);
119
172
  this.registerModule('perms', Perms);
120
173
  this.registerModule('drivers', Drivers);
121
174
  this.registerModule('debug', Debug);
@@ -135,15 +188,6 @@ const puterInit = (function () {
135
188
 
136
189
  // "modules" in puter.js are external interfaces for the developer
137
190
  this.modules_ = [];
138
- // "services" in puter.js are used by modules and may interact with each other
139
- const context = new putility.libs.context.Context()
140
- .follow(this, ['env', 'util', 'authToken', 'APIOrigin', 'appID']);
141
-
142
- context.puter = this;
143
-
144
- this.services = new putility.system.ServiceManager({ context });
145
- this.context = context;
146
- context.services = this.services;
147
191
 
148
192
  // Holds the query parameters found in the current URL
149
193
  let URLParams = new URLSearchParams(globalThis.location?.search);
@@ -261,57 +305,15 @@ const puterInit = (function () {
261
305
 
262
306
  // === START :: Logger ===
263
307
 
264
- // logger will log to console
265
- let logger = new putility.libs.log.ConsoleLogger();
266
-
267
- // logs can be toggled based on categories
268
- logger = new putility.libs.log.CategorizedToggleLogger({ delegate: logger });
269
- const cat_logger = logger;
270
-
271
- // create facade for easy logging
272
- this.logger = new putility.libs.log.LoggerFacade({
273
- impl: logger,
274
- cat: cat_logger,
275
- });
308
+ // Basic logger replacement (console-based)
309
+ let logger = new SimpleLogger();
310
+ this.logger = logger;
276
311
 
277
312
  // Initialize API call logger
278
313
  this.apiCallLogger = new APICallLogger({
279
314
  enabled: false, // Disabled by default
280
315
  });
281
316
 
282
- // === START :: Services === //
283
-
284
- this.services.register('no-puter-yet', NoPuterYetService);
285
- this.services.register('filesystem', FilesystemService);
286
- this.services.register('api-access', APIAccessService);
287
- this.services.register('xd-incoming', XDIncomingService);
288
- if ( this.env !== 'app' ) {
289
- this.services.register('fs-relay', FSRelayService);
290
- }
291
-
292
- // When api-access is initialized, bind `.authToken` and
293
- // `.APIOrigin` as a 1-1 mapping with the `puter` global
294
- (async () => {
295
- await this.services.wait_for_init(['api-access']);
296
- const svc_apiAccess = this.services.get('api-access');
297
-
298
- svc_apiAccess.auth_token = this.authToken;
299
- svc_apiAccess.api_origin = this.APIOrigin;
300
- [
301
- ['authToken', 'auth_token'],
302
- ['APIOrigin', 'api_origin'],
303
- ].forEach(([k1, k2]) => {
304
- Object.defineProperty(this, k1, {
305
- get () {
306
- return svc_apiAccess[k2];
307
- },
308
- set (v) {
309
- svc_apiAccess[k2] = v;
310
- },
311
- });
312
- });
313
- })();
314
-
315
317
  // === Start :: Modules === //
316
318
 
317
319
  // The SDK is running in the Puter GUI (i.e. 'gui')
@@ -365,31 +367,27 @@ const puterInit = (function () {
365
367
 
366
368
  // Add prefix logger (needed to happen after modules are initialized)
367
369
  (async () => {
368
- await this.services.wait_for_init(['api-access']);
369
- const whoami = await this.auth.whoami();
370
- logger = new putility.libs.log.PrefixLogger({
371
- delegate: logger,
372
- prefix: `[${
370
+ try {
371
+ const whoami = await this.auth.whoami();
372
+ const prefix = `[${
373
373
  whoami?.app_name ?? this.appInstanceID ?? 'HOST'
374
- }] `,
375
- });
376
-
377
- this.logger.impl = logger;
374
+ }]`;
375
+ logger = logger.fields({ prefix });
376
+ this.logger = logger;
377
+ } catch (error) {
378
+ if ( this.debugMode ) {
379
+ console.error('Failed to initialize prefix logger', error);
380
+ }
381
+ }
378
382
  })();
379
383
 
380
384
  // Lock to prevent multiple requests to `/rao`
381
- this.lock_rao_ = new putility.libs.promise.Lock();
385
+ this.lock_rao_ = new Lock();
382
386
  // Promise that resolves when it's okay to request `/rao`
383
- this.p_can_request_rao_ = new putility.libs.promise.TeePromise();
387
+ this.p_can_request_rao_ = Promise.resolve();
384
388
  // Flag that indicates if a request to `/rao` has been made
385
389
  this.rao_requested_ = false;
386
390
 
387
- // In case we're already auth'd, request `/rao`
388
- (async () => {
389
- await this.services.wait_for_init(['api-access']);
390
- this.p_can_request_rao_.resolve();
391
- })();
392
-
393
391
  this.net = {
394
392
  generateWispV1URL: async () => {
395
393
  const { token: wispToken, server: wispServer } = (await (await fetch(`${this.APIOrigin }/wisp/relay-token/create`, {
@@ -457,7 +455,7 @@ const puterInit = (function () {
457
455
  }
458
456
 
459
457
  registerModule (name, cls, parameters = {}) {
460
- const instance = new cls(this.context, parameters);
458
+ const instance = new cls(this, parameters);
461
459
  instance.puter = this;
462
460
  this.modules_.push(name);
463
461
  this[name] = instance;
@@ -518,6 +516,25 @@ const puterInit = (function () {
518
516
  this.updateSubmodules();
519
517
  };
520
518
 
519
+ runWhenPuterHappensCallbacks = function () {
520
+ if ( this.env !== 'gui' ) return;
521
+ if ( ! globalThis.when_puter_happens ) return;
522
+
523
+ const callbacks = Array.isArray(globalThis.when_puter_happens)
524
+ ? globalThis.when_puter_happens
525
+ : [globalThis.when_puter_happens];
526
+
527
+ for ( const fn of callbacks ) {
528
+ try {
529
+ fn({ puter: this });
530
+ } catch ( error ) {
531
+ if ( this.debugMode ) {
532
+ console.error('when_puter_happens callback failed', error);
533
+ }
534
+ }
535
+ }
536
+ };
537
+
521
538
  resetAuthToken = function () {
522
539
  this.authToken = null;
523
540
  // If the SDK is running on a 3rd-party site or an app, then save the authToken in localStorage
@@ -785,6 +802,7 @@ const puterInit = (function () {
785
802
  export const puter = puterInit();
786
803
  export default puter;
787
804
  globalThis.puter = puter;
805
+ puter.runWhenPuterHappensCallbacks();
788
806
 
789
807
  puter.tools = [];
790
808
  /**
@@ -863,4 +881,4 @@ globalThis.addEventListener && globalThis.addEventListener('message', async (eve
863
881
  puter.puterAuthState.resolver = null;
864
882
  };
865
883
  }
866
- });
884
+ });
package/src/lib/utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { FileReaderPoly } from "./polyfills/fileReaderPoly.js";
2
+ import { showUsageLimitDialog } from "../modules/UsageLimitDialog.js";
2
3
 
3
4
  /**
4
5
  * Parses a given response text into a JSON object. If the parsing fails due to invalid JSON format,
@@ -65,6 +66,16 @@ function uuidv4 () {
65
66
  (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));
66
67
  }
67
68
 
69
+ const createDeferred = () => {
70
+ let resolve;
71
+ let reject;
72
+ const promise = new Promise((res, rej) => {
73
+ resolve = res;
74
+ reject = rej;
75
+ });
76
+ return { promise, resolve, reject };
77
+ };
78
+
68
79
  /**
69
80
  * Initializes and returns an XMLHttpRequest object configured for a specific API endpoint, method, and headers.
70
81
  *
@@ -266,11 +277,11 @@ function make_driver_method (arg_defs, driverInterface, driverName, driverMethod
266
277
  }
267
278
 
268
279
  async function driverCall (options, driverInterface, driverName, driverMethod, driverArgs, settings) {
269
- const tp = new TeePromise();
280
+ const deferred = createDeferred();
270
281
 
271
282
  driverCall_(options,
272
- tp.resolve.bind(tp),
273
- tp.reject.bind(tp),
283
+ deferred.resolve,
284
+ deferred.reject,
274
285
  driverInterface,
275
286
  driverName,
276
287
  driverMethod,
@@ -279,7 +290,7 @@ async function driverCall (options, driverInterface, driverName, driverMethod, d
279
290
  undefined,
280
291
  settings);
281
292
 
282
- return await tp;
293
+ return await deferred.promise;
283
294
  }
284
295
 
285
296
  // This function encapsulates the logic for sending a driver call request
@@ -368,14 +379,22 @@ async function driverCall_ (
368
379
  is_stream = true;
369
380
  const Stream = async function* Stream () {
370
381
  while ( !response_complete ) {
371
- const tp = new TeePromise();
372
- signal_stream_update = tp.resolve.bind(tp);
373
- await tp;
382
+ const signal = createDeferred();
383
+ signal_stream_update = signal.resolve;
384
+ await signal.promise;
374
385
  if ( response_complete ) break;
375
386
  while ( lines_received.length > 0 ) {
376
387
  const line = lines_received.shift();
377
388
  if ( line.trim() === '' ) continue;
378
389
  const lineObject = (JSON.parse(line));
390
+
391
+ // Check for usage limit errors in streaming responses
392
+ if ( lineObject?.error?.code === 'insufficient_funds' || lineObject?.metadata?.usage_limited === true ) {
393
+ if ( puter.env === 'web' ) {
394
+ showUsageLimitDialog('You have reached your usage limit for this account.<br>Please upgrade to continue.');
395
+ }
396
+ }
397
+
379
398
  if ( typeof (lineObject.text) === 'string' ) {
380
399
  Object.defineProperty(lineObject, 'toString', {
381
400
  enumerable: false,
@@ -452,6 +471,16 @@ async function driverCall_ (
452
471
  });
453
472
  }
454
473
 
474
+ // Check for usage limit errors and show upgrade dialog
475
+ const isInsufficientFunds = (response.target?.status === 402) ||
476
+ (resp?.error?.code === 'insufficient_funds') ||
477
+ (resp?.error?.status === 402);
478
+ const isUsageLimited = resp?.metadata?.usage_limited === true;
479
+
480
+ if ( (isInsufficientFunds || isUsageLimited) && puter.env === 'web' ) {
481
+ showUsageLimitDialog('Your account has not enough funding to complete this request.<br>Please upgrade to continue.');
482
+ }
483
+
455
484
  // HTTP Error - unauthorized
456
485
  if ( response.status === 401 || resp?.code === 'token_auth_failed' ) {
457
486
  if ( resp?.code === 'token_auth_failed' && puter.env === 'web' ) {
@@ -543,58 +572,16 @@ async function driverCall_ (
543
572
  args: driverArgs,
544
573
  auth_token: puter.authToken,
545
574
  }));
546
- }
547
575
 
548
- class TeePromise {
549
- static STATUS_PENDING = {};
550
- static STATUS_RUNNING = {};
551
- static STATUS_DONE = {};
552
- constructor () {
553
- this.status_ = this.constructor.STATUS_PENDING;
554
- this.donePromise = new Promise((resolve, reject) => {
555
- this.doneResolve = resolve;
556
- this.doneReject = reject;
557
- });
558
- }
559
- get status () {
560
- return this.status_;
561
- }
562
- set status (status) {
563
- this.status_ = status;
564
- if ( status === this.constructor.STATUS_DONE ) {
565
- this.doneResolve();
566
- }
567
- }
568
- resolve (value) {
569
- this.status_ = this.constructor.STATUS_DONE;
570
- this.doneResolve(value);
571
- }
572
- awaitDone () {
573
- return this.donePromise;
574
- }
575
- then (fn, rfn) {
576
- return this.donePromise.then(fn, rfn);
577
- }
578
-
579
- reject (err) {
580
- this.status_ = this.constructor.STATUS_DONE;
581
- this.doneReject(err);
582
- }
583
-
584
- /**
585
- * @deprecated use then() instead
586
- */
587
- onComplete (fn) {
588
- return this.then(fn);
589
- }
590
576
  }
591
577
 
592
578
  async function blob_to_url (blob) {
593
- const tp = new TeePromise();
594
579
  const reader = new (globalThis.FileReader || FileReaderPoly)();
595
- reader.onloadend = () => tp.resolve(reader.result);
596
- reader.readAsDataURL(blob);
597
- return await tp;
580
+ return await new Promise((resolve, reject) => {
581
+ reader.onloadend = () => resolve(reader.result);
582
+ reader.onerror = reject;
583
+ reader.readAsDataURL(blob);
584
+ });
598
585
  }
599
586
 
600
587
  function blobToDataUri (blob) {
@@ -625,5 +612,5 @@ function arrayBufferToDataUri (arrayBuffer) {
625
612
  }
626
613
 
627
614
  export {
628
- arrayBufferToDataUri, blob_to_url, blobToDataUri, driverCall, handle_error, handle_resp, initXhr, make_driver_method, parseResponse, setupXhrEventHandlers, TeePromise, uuidv4
615
+ arrayBufferToDataUri, blob_to_url, blobToDataUri, driverCall, handle_error, handle_resp, initXhr, make_driver_method, parseResponse, setupXhrEventHandlers, uuidv4
629
616
  };