@heyputer/puter.js 2.1.6 → 2.1.7

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 (57) hide show
  1. package/dist/puter.cjs +2 -2
  2. package/index.d.ts +14 -14
  3. package/package.json +1 -1
  4. package/src/index.js +91 -91
  5. package/src/lib/APICallLogger.js +20 -21
  6. package/src/lib/EventListener.js +10 -10
  7. package/src/lib/filesystem/APIFS.js +11 -19
  8. package/src/lib/filesystem/CacheFS.js +25 -25
  9. package/src/lib/filesystem/PostMessageFS.js +11 -11
  10. package/src/lib/filesystem/definitions.js +11 -10
  11. package/src/lib/path.js +505 -446
  12. package/src/lib/polyfills/fileReaderPoly.js +40 -0
  13. package/src/lib/polyfills/localStorage.js +30 -33
  14. package/src/lib/polyfills/xhrshim.js +206 -207
  15. package/src/lib/utils.js +160 -151
  16. package/src/lib/xdrpc.js +9 -9
  17. package/src/modules/AI.js +416 -290
  18. package/src/modules/Apps.js +56 -56
  19. package/src/modules/Auth.js +17 -17
  20. package/src/modules/Debug.js +1 -1
  21. package/src/modules/Drivers.js +41 -41
  22. package/src/modules/FSItem.js +64 -62
  23. package/src/modules/FileSystem/index.js +22 -23
  24. package/src/modules/FileSystem/operations/copy.js +7 -7
  25. package/src/modules/FileSystem/operations/deleteFSEntry.js +14 -12
  26. package/src/modules/FileSystem/operations/getReadUrl.js +16 -14
  27. package/src/modules/FileSystem/operations/mkdir.js +11 -11
  28. package/src/modules/FileSystem/operations/move.js +12 -12
  29. package/src/modules/FileSystem/operations/read.js +10 -10
  30. package/src/modules/FileSystem/operations/readdir.js +28 -28
  31. package/src/modules/FileSystem/operations/rename.js +11 -11
  32. package/src/modules/FileSystem/operations/sign.js +33 -30
  33. package/src/modules/FileSystem/operations/space.js +7 -7
  34. package/src/modules/FileSystem/operations/stat.js +25 -25
  35. package/src/modules/FileSystem/operations/symlink.js +15 -17
  36. package/src/modules/FileSystem/operations/upload.js +151 -122
  37. package/src/modules/FileSystem/operations/write.js +16 -12
  38. package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +10 -6
  39. package/src/modules/Hosting.js +29 -29
  40. package/src/modules/KV.js +23 -23
  41. package/src/modules/OS.js +15 -15
  42. package/src/modules/Perms.js +19 -21
  43. package/src/modules/PuterDialog.js +46 -48
  44. package/src/modules/Threads.js +17 -20
  45. package/src/modules/UI.js +156 -156
  46. package/src/modules/Util.js +3 -3
  47. package/src/modules/Workers.js +52 -49
  48. package/src/modules/networking/PSocket.js +38 -38
  49. package/src/modules/networking/PTLS.js +54 -47
  50. package/src/modules/networking/PWispHandler.js +49 -47
  51. package/src/modules/networking/parsers.js +110 -108
  52. package/src/modules/networking/requests.js +67 -78
  53. package/src/services/APIAccess.js +9 -9
  54. package/src/services/FSRelay.js +6 -6
  55. package/src/services/Filesystem.js +8 -8
  56. package/src/services/NoPuterYet.js +2 -2
  57. package/src/services/XDIncoming.js +1 -1
package/src/modules/UI.js CHANGED
@@ -22,7 +22,7 @@ class AppConnection extends EventListener {
22
22
  // (Closing and close events will still function.)
23
23
  #usesSDK;
24
24
 
25
- static from(values, context) {
25
+ static from (values, context) {
26
26
  const connection = new AppConnection(context, {
27
27
  target: values.appInstanceID,
28
28
  usesSDK: values.usesSDK,
@@ -35,10 +35,10 @@ class AppConnection extends EventListener {
35
35
  return connection;
36
36
  }
37
37
 
38
- constructor(context, { target, usesSDK }) {
38
+ constructor (context, { target, usesSDK }) {
39
39
  super([
40
40
  'message', // The target sent us something with postMessage()
41
- 'close', // The target app was closed
41
+ 'close', // The target app was closed
42
42
  ]);
43
43
  this.messageTarget = context.messageTarget;
44
44
  this.appInstanceID = context.appInstanceID;
@@ -88,18 +88,18 @@ class AppConnection extends EventListener {
88
88
  }
89
89
 
90
90
  // Does the target app use the Puter SDK? If not, certain features will be unavailable.
91
- get usesSDK() {
91
+ get usesSDK () {
92
92
  return this.#usesSDK;
93
93
  }
94
94
 
95
95
  // Send a message to the target app. Requires the target to use the Puter SDK.
96
- postMessage(message) {
97
- if ( !this.#isOpen ) {
96
+ postMessage (message) {
97
+ if ( ! this.#isOpen ) {
98
98
  console.warn('Trying to post message on a closed AppConnection');
99
99
  return;
100
100
  }
101
101
 
102
- if ( !this.#usesSDK ) {
102
+ if ( ! this.#usesSDK ) {
103
103
  console.warn('Trying to post message to a non-SDK app');
104
104
  return;
105
105
  }
@@ -117,8 +117,8 @@ class AppConnection extends EventListener {
117
117
  }
118
118
 
119
119
  // Attempt to close the target application
120
- close() {
121
- if ( !this.#isOpen ) {
120
+ close () {
121
+ if ( ! this.#isOpen ) {
122
122
  console.warn('Trying to close an app on a closed AppConnection');
123
123
  return;
124
124
  }
@@ -172,7 +172,7 @@ class UI extends EventListener {
172
172
  #overlayTimer = null;
173
173
 
174
174
  // Replaces boilerplate for most methods: posts a message to the GUI with a unique ID, and sets a callback for it.
175
- #postMessageWithCallback(name, resolve, args = {}) {
175
+ #postMessageWithCallback (name, resolve, args = {}) {
176
176
  const msg_id = this.#messageID++;
177
177
  this.messageTarget?.postMessage({
178
178
  msg: name,
@@ -185,7 +185,7 @@ class UI extends EventListener {
185
185
  this.#callbackFunctions[msg_id] = resolve;
186
186
  }
187
187
 
188
- #postMessageWithObject(name, value) {
188
+ #postMessageWithObject (name, value) {
189
189
  const dehydrator = this.util.rpc.getDehydrator({
190
190
  target: this.messageTarget,
191
191
  });
@@ -197,7 +197,7 @@ class UI extends EventListener {
197
197
  }, '*');
198
198
  };
199
199
 
200
- async #ipc_stub({
200
+ async #ipc_stub ({
201
201
  callback,
202
202
  method,
203
203
  parameters,
@@ -224,7 +224,7 @@ class UI extends EventListener {
224
224
  return ret;
225
225
  };
226
226
 
227
- constructor(context, { appInstanceID, parentInstanceID }) {
227
+ constructor (context, { appInstanceID, parentInstanceID }) {
228
228
  const eventNames = [
229
229
  'localeChanged',
230
230
  'themeChanged',
@@ -239,10 +239,10 @@ class UI extends EventListener {
239
239
  this.env = context.env;
240
240
  this.util = context.util;
241
241
 
242
- if ( this.env === 'app' ){
242
+ if ( this.env === 'app' ) {
243
243
  this.messageTarget = window.parent;
244
244
  }
245
- else if ( this.env === 'gui' ){
245
+ else if ( this.env === 'gui' ) {
246
246
  return;
247
247
  }
248
248
 
@@ -277,17 +277,17 @@ class UI extends EventListener {
277
277
  // Bind the message event listener to the window
278
278
  let lastDraggedOverElement = null;
279
279
  (globalThis.document) && window.addEventListener('message', async (e) => {
280
- if ( !e.data ) return;
280
+ if ( ! e.data ) return;
281
281
  // `error`
282
- if ( e.data.error ){
282
+ if ( e.data.error ) {
283
283
  throw e.data.error;
284
284
  }
285
285
  // `focus` event
286
- else if ( e.data.msg && e.data.msg === 'focus' ){
286
+ else if ( e.data.msg && e.data.msg === 'focus' ) {
287
287
  window.focus();
288
288
  }
289
289
  // `click` event
290
- else if ( e.data.msg && e.data.msg === 'click' ){
290
+ else if ( e.data.msg && e.data.msg === 'click' ) {
291
291
  // Get the element that was clicked on and click it
292
292
  const clicked_el = document.elementFromPoint(e.data.x, e.data.y);
293
293
  if ( clicked_el !== null )
@@ -296,12 +296,12 @@ class UI extends EventListener {
296
296
  }
297
297
  }
298
298
  // `dragover` event based on the `drag` event from the host environment
299
- else if ( e.data.msg && e.data.msg === 'drag' ){
299
+ else if ( e.data.msg && e.data.msg === 'drag' ) {
300
300
  // Get the element being dragged over
301
301
  const draggedOverElement = document.elementFromPoint(e.data.x, e.data.y);
302
- if ( draggedOverElement !== lastDraggedOverElement ){
302
+ if ( draggedOverElement !== lastDraggedOverElement ) {
303
303
  // If the last element exists and is different from the current, dispatch a dragleave on it
304
- if ( lastDraggedOverElement ){
304
+ if ( lastDraggedOverElement ) {
305
305
  const dragLeaveEvent = new Event('dragleave', {
306
306
  bubbles: true,
307
307
  cancelable: true,
@@ -311,7 +311,7 @@ class UI extends EventListener {
311
311
  lastDraggedOverElement.dispatchEvent(dragLeaveEvent);
312
312
  }
313
313
  // If the current element exists and is different from the last, dispatch dragenter on it
314
- if ( draggedOverElement ){
314
+ if ( draggedOverElement ) {
315
315
  const dragEnterEvent = new Event('dragenter', {
316
316
  bubbles: true,
317
317
  cancelable: true,
@@ -326,8 +326,8 @@ class UI extends EventListener {
326
326
  }
327
327
  }
328
328
  // `drop` event
329
- else if ( e.data.msg && e.data.msg === 'drop' ){
330
- if ( lastDraggedOverElement ){
329
+ else if ( e.data.msg && e.data.msg === 'drop' ) {
330
+ if ( lastDraggedOverElement ) {
331
331
  const dropEvent = new CustomEvent('drop', {
332
332
  bubbles: true,
333
333
  cancelable: true,
@@ -344,10 +344,10 @@ class UI extends EventListener {
344
344
  }
345
345
  }
346
346
  // windowWillClose
347
- else if ( e.data.msg === 'windowWillClose' ){
347
+ else if ( e.data.msg === 'windowWillClose' ) {
348
348
  // If the user has not overridden onWindowClose() then send a message back to the host environment
349
349
  // to let it know that it is ok to close the window.
350
- if ( this.#onWindowClose === undefined ){
350
+ if ( this.#onWindowClose === undefined ) {
351
351
  this.messageTarget?.postMessage({
352
352
  msg: true,
353
353
  appInstanceID: this.appInstanceID,
@@ -367,9 +367,9 @@ class UI extends EventListener {
367
367
  }
368
368
  }
369
369
  // itemsOpened
370
- else if ( e.data.msg === 'itemsOpened' ){
370
+ else if ( e.data.msg === 'itemsOpened' ) {
371
371
  // If the user has not overridden onItemsOpened() then only send a message back to the host environment
372
- if ( this.#onItemsOpened === undefined ){
372
+ if ( this.#onItemsOpened === undefined ) {
373
373
  this.messageTarget?.postMessage({
374
374
  msg: true,
375
375
  appInstanceID: this.appInstanceID,
@@ -386,7 +386,7 @@ class UI extends EventListener {
386
386
  }, '*');
387
387
 
388
388
  let items = [];
389
- if ( e.data.items.length > 0 ){
389
+ if ( e.data.items.length > 0 ) {
390
390
  for ( let index = 0; index < e.data.items.length; index++ )
391
391
  {
392
392
  items.push(new FSItem(e.data.items[index]));
@@ -396,41 +396,41 @@ class UI extends EventListener {
396
396
  }
397
397
  }
398
398
  // getAppDataSucceeded
399
- else if ( e.data.msg === 'getAppDataSucceeded' ){
399
+ else if ( e.data.msg === 'getAppDataSucceeded' ) {
400
400
  let appDataItem = new FSItem(e.data.item);
401
- if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
401
+ if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ) {
402
402
  this.#callbackFunctions[e.data.original_msg_id](appDataItem);
403
403
  }
404
404
  }
405
405
  // instancesOpenSucceeded
406
- else if ( e.data.msg === 'instancesOpenSucceeded' ){
407
- if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
406
+ else if ( e.data.msg === 'instancesOpenSucceeded' ) {
407
+ if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ) {
408
408
  this.#callbackFunctions[e.data.original_msg_id](e.data.instancesOpen);
409
409
  }
410
410
  }
411
411
  // readAppDataFileSucceeded
412
- else if ( e.data.msg === 'readAppDataFileSucceeded' ){
412
+ else if ( e.data.msg === 'readAppDataFileSucceeded' ) {
413
413
  let appDataItem = new FSItem(e.data.item);
414
- if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
414
+ if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ) {
415
415
  this.#callbackFunctions[e.data.original_msg_id](appDataItem);
416
416
  }
417
417
  }
418
418
  // readAppDataFileFailed
419
- else if ( e.data.msg === 'readAppDataFileFailed' ){
420
- if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ){
419
+ else if ( e.data.msg === 'readAppDataFileFailed' ) {
420
+ if ( e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id] ) {
421
421
  this.#callbackFunctions[e.data.original_msg_id](null);
422
422
  }
423
423
  }
424
424
  // Determine if this is a response to a previous message and if so, is there
425
425
  // a callback function for this message? if answer is yes to both then execute the callback
426
- else if ( e.data.original_msg_id !== undefined && this.#callbackFunctions[e.data.original_msg_id] ){
427
- if ( e.data.msg === 'fileOpenPicked' ){
426
+ else if ( e.data.original_msg_id !== undefined && this.#callbackFunctions[e.data.original_msg_id] ) {
427
+ if ( e.data.msg === 'fileOpenPicked' ) {
428
428
  // 1 item returned
429
- if ( e.data.items.length === 1 ){
429
+ if ( e.data.items.length === 1 ) {
430
430
  this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.items[0]));
431
431
  }
432
432
  // multiple items returned
433
- else if ( e.data.items.length > 1 ){
433
+ else if ( e.data.items.length > 1 ) {
434
434
  // multiple items returned
435
435
  let items = [];
436
436
  for ( let index = 0; index < e.data.items.length; index++ )
@@ -440,9 +440,9 @@ class UI extends EventListener {
440
440
  this.#callbackFunctions[e.data.original_msg_id](items);
441
441
  }
442
442
  }
443
- else if ( e.data.msg === 'directoryPicked' ){
443
+ else if ( e.data.msg === 'directoryPicked' ) {
444
444
  // 1 item returned
445
- if ( e.data.items.length === 1 ){
445
+ if ( e.data.items.length === 1 ) {
446
446
  this.#callbackFunctions[e.data.original_msg_id](new FSItem({
447
447
  uid: e.data.items[0].uid,
448
448
  name: e.data.items[0].fsentry_name,
@@ -458,7 +458,7 @@ class UI extends EventListener {
458
458
  }));
459
459
  }
460
460
  // multiple items returned
461
- else if ( e.data.items.length > 1 ){
461
+ else if ( e.data.items.length > 1 ) {
462
462
  // multiple items returned
463
463
  let items = [];
464
464
  for ( let index = 0; index < e.data.items.length; index++ )
@@ -468,35 +468,35 @@ class UI extends EventListener {
468
468
  this.#callbackFunctions[e.data.original_msg_id](items);
469
469
  }
470
470
  }
471
- else if ( e.data.msg === 'colorPicked' ){
471
+ else if ( e.data.msg === 'colorPicked' ) {
472
472
  // execute callback
473
473
  this.#callbackFunctions[e.data.original_msg_id](e.data.color);
474
474
  }
475
- else if ( e.data.msg === 'fontPicked' ){
475
+ else if ( e.data.msg === 'fontPicked' ) {
476
476
  // execute callback
477
477
  this.#callbackFunctions[e.data.original_msg_id](e.data.font);
478
478
  }
479
- else if ( e.data.msg === 'alertResponded' ){
479
+ else if ( e.data.msg === 'alertResponded' ) {
480
480
  // execute callback
481
481
  this.#callbackFunctions[e.data.original_msg_id](e.data.response);
482
482
  }
483
- else if ( e.data.msg === 'promptResponded' ){
483
+ else if ( e.data.msg === 'promptResponded' ) {
484
484
  // execute callback
485
485
  this.#callbackFunctions[e.data.original_msg_id](e.data.response);
486
486
  }
487
- else if ( e.data.msg === 'languageReceived' ){
487
+ else if ( e.data.msg === 'languageReceived' ) {
488
488
  // execute callback
489
489
  this.#callbackFunctions[e.data.original_msg_id](e.data.language);
490
490
  }
491
- else if ( e.data.msg === 'fileSaved' ){
491
+ else if ( e.data.msg === 'fileSaved' ) {
492
492
  // execute callback
493
493
  this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.saved_file));
494
494
  }
495
- else if ( e.data.msg === 'fileSaveCancelled' ){
495
+ else if ( e.data.msg === 'fileSaveCancelled' ) {
496
496
  // execute callback
497
497
  this.#callbackFunctions[e.data.original_msg_id](FILE_SAVE_CANCELLED);
498
498
  }
499
- else if ( e.data.msg === 'fileOpenCancelled' ){
499
+ else if ( e.data.msg === 'fileOpenCancelled' ) {
500
500
  // execute callback
501
501
  this.#callbackFunctions[e.data.original_msg_id](FILE_OPEN_CANCELLED);
502
502
  }
@@ -509,7 +509,7 @@ class UI extends EventListener {
509
509
  delete this.#callbackFunctions[e.data.original_msg_id];
510
510
  }
511
511
  // Item Watch response
512
- else if ( e.data.msg === 'itemChanged' && e.data.data && e.data.data.uid ){
512
+ else if ( e.data.msg === 'itemChanged' && e.data.data && e.data.data.uid ) {
513
513
  //excute callback
514
514
  if ( this.itemWatchCallbackFunctions[e.data.data.uid] && typeof this.itemWatchCallbackFunctions[e.data.data.uid] === 'function' )
515
515
  {
@@ -519,7 +519,7 @@ class UI extends EventListener {
519
519
  // Broadcasts
520
520
  else if ( e.data.msg === 'broadcast' ) {
521
521
  const { name, data } = e.data;
522
- if ( !this.#eventNames.includes(name) ) {
522
+ if ( ! this.#eventNames.includes(name) ) {
523
523
  return;
524
524
  }
525
525
  this.emit(name, data);
@@ -585,11 +585,11 @@ class UI extends EventListener {
585
585
  });
586
586
  }
587
587
 
588
- onWindowClose(callback) {
588
+ onWindowClose (callback) {
589
589
  this.#onWindowClose = callback;
590
590
  };
591
591
 
592
- onItemsOpened(callback) {
592
+ onItemsOpened (callback) {
593
593
  // DEPRECATED - this is also called when items are dropped on the app, which in new versions should be handled
594
594
  // with the 'drop' event.
595
595
  // Check if a file was opened with this app, i.e. check URL parameters of window/iframe
@@ -597,9 +597,9 @@ class UI extends EventListener {
597
597
  // before we can call it. This is why we need to check the URL parameters here.
598
598
  // This should also be done only the very first time the callback is set (hence the if(!this.#onItemsOpened) check) since
599
599
  // the URL parameters will be checked every time the callback is set which can cause problems if the callback is set multiple times.
600
- if ( !this.#onItemsOpened ){
600
+ if ( ! this.#onItemsOpened ) {
601
601
  let URLParams = new URLSearchParams(globalThis.location.search);
602
- if ( URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url') ){
602
+ if ( URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url') ) {
603
603
  let fpath = URLParams.get('puter.item.path');
604
604
 
605
605
  if ( !fpath.startsWith('~/') && !fpath.startsWith('/') )
@@ -627,22 +627,22 @@ class UI extends EventListener {
627
627
 
628
628
  // Check if the app was launched with items
629
629
  // This is useful for apps that are launched with items (e.g. when a file is opened with the app)
630
- wasLaunchedWithItems() {
630
+ wasLaunchedWithItems () {
631
631
  const URLParams = new URLSearchParams(globalThis.location.search);
632
632
  return URLParams.has('puter.item.name') &&
633
633
  URLParams.has('puter.item.uid') &&
634
634
  URLParams.has('puter.item.read_url');
635
635
  };
636
636
 
637
- onLaunchedWithItems(callback) {
637
+ onLaunchedWithItems (callback) {
638
638
  // Check if a file was opened with this app, i.e. check URL parameters of window/iframe
639
639
  // Even though the file has been opened when the app is launched, we need to wait for the onLaunchedWithItems callback to be set
640
640
  // before we can call it. This is why we need to check the URL parameters here.
641
641
  // This should also be done only the very first time the callback is set (hence the if(!this.#onLaunchedWithItems) check) since
642
642
  // the URL parameters will be checked every time the callback is set which can cause problems if the callback is set multiple times.
643
- if ( !this.#onLaunchedWithItems ){
643
+ if ( ! this.#onLaunchedWithItems ) {
644
644
  let URLParams = new URLSearchParams(globalThis.location.search);
645
- if ( URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url') ){
645
+ if ( URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url') ) {
646
646
  let fpath = URLParams.get('puter.item.path');
647
647
 
648
648
  if ( !fpath.startsWith('~/') && !fpath.startsWith('/') )
@@ -668,49 +668,49 @@ class UI extends EventListener {
668
668
  this.#onLaunchedWithItems = callback;
669
669
  };
670
670
 
671
- requestEmailConfirmation() {
671
+ requestEmailConfirmation () {
672
672
  return new Promise((resolve, reject) => {
673
- this.#postMessageWithCallback('requestEmailConfirmation', resolve, { });
673
+ this.#postMessageWithCallback('requestEmailConfirmation', resolve, { });
674
674
  });
675
675
  };
676
676
 
677
- alert(message, buttons, options, callback) {
677
+ alert (message, buttons, options, callback) {
678
678
  return new Promise((resolve) => {
679
679
  this.#postMessageWithCallback('ALERT', resolve, { message, buttons, options });
680
680
  });
681
681
  };
682
682
 
683
- openDevPaymentsAccount() {
683
+ openDevPaymentsAccount () {
684
684
  return new Promise((resolve) => {
685
- this.#postMessageWithCallback('openDevPaymentsAccount', resolve, { });
685
+ this.#postMessageWithCallback('openDevPaymentsAccount', resolve, { });
686
686
  });
687
687
  }
688
688
 
689
- instancesOpen(callback) {
689
+ instancesOpen (callback) {
690
690
  return new Promise((resolve) => {
691
- this.#postMessageWithCallback('getInstancesOpen', resolve, { });
691
+ this.#postMessageWithCallback('getInstancesOpen', resolve, { });
692
692
  });
693
693
  };
694
694
 
695
- socialShare(url, message, options, callback) {
695
+ socialShare (url, message, options, callback) {
696
696
  return new Promise((resolve) => {
697
697
  this.#postMessageWithCallback('socialShare', resolve, { url, message, options });
698
698
  });
699
699
  };
700
700
 
701
- prompt(message, placeholder, options, callback) {
701
+ prompt (message, placeholder, options, callback) {
702
702
  return new Promise((resolve) => {
703
703
  this.#postMessageWithCallback('PROMPT', resolve, { message, placeholder, options });
704
704
  });
705
705
  };
706
706
 
707
- showDirectoryPicker(options, callback){
707
+ showDirectoryPicker (options, callback) {
708
708
  return new Promise((resolve, reject) => {
709
- if ( !globalThis.open ) {
709
+ if ( ! globalThis.open ) {
710
710
  return reject('This API is not compatible in Web Workers.');
711
711
  }
712
712
  const msg_id = this.#messageID++;
713
- if ( this.env === 'app' ){
713
+ if ( this.env === 'app' ) {
714
714
  this.messageTarget?.postMessage({
715
715
  msg: 'showDirectoryPicker',
716
716
  appInstanceID: this.appInstanceID,
@@ -734,15 +734,15 @@ class UI extends EventListener {
734
734
  });
735
735
  };
736
736
 
737
- showOpenFilePicker(options, callback){
737
+ showOpenFilePicker (options, callback) {
738
738
  const undefinedOnCancel = new putility.libs.promise.TeePromise();
739
739
  const resolveOnlyPromise = new Promise((resolve, reject) => {
740
- if ( !globalThis.open ) {
740
+ if ( ! globalThis.open ) {
741
741
  return reject('This API is not compatible in Web Workers.');
742
742
  }
743
743
  const msg_id = this.#messageID++;
744
744
 
745
- if ( this.env === 'app' ){
745
+ if ( this.env === 'app' ) {
746
746
  this.messageTarget?.postMessage({
747
747
  msg: 'showOpenFilePicker',
748
748
  appInstanceID: this.appInstanceID,
@@ -775,38 +775,38 @@ class UI extends EventListener {
775
775
  return resolveOnlyPromise;
776
776
  };
777
777
 
778
- showFontPicker(options){
778
+ showFontPicker (options) {
779
779
  return new Promise((resolve) => {
780
780
  this.#postMessageWithCallback('showFontPicker', resolve, { options: options ?? {} });
781
781
  });
782
782
  };
783
783
 
784
- showColorPicker(options){
784
+ showColorPicker (options) {
785
785
  return new Promise((resolve) => {
786
786
  this.#postMessageWithCallback('showColorPicker', resolve, { options: options ?? {} });
787
787
  });
788
788
  };
789
789
 
790
- requestUpgrade() {
790
+ requestUpgrade () {
791
791
  return new Promise((resolve) => {
792
- this.#postMessageWithCallback('requestUpgrade', resolve, { });
792
+ this.#postMessageWithCallback('requestUpgrade', resolve, { });
793
793
  });
794
794
  };
795
795
 
796
- showSaveFilePicker(content, suggestedName, type){
796
+ showSaveFilePicker (content, suggestedName, type) {
797
797
  const undefinedOnCancel = new putility.libs.promise.TeePromise();
798
798
  const resolveOnlyPromise = new Promise((resolve, reject) => {
799
- if ( !globalThis.open ) {
799
+ if ( ! globalThis.open ) {
800
800
  return reject('This API is not compatible in Web Workers.');
801
801
  }
802
802
  const msg_id = this.#messageID++;
803
- if ( ! type && Object.prototype.toString.call(content) === '[object URL]' ) {
803
+ if ( !type && Object.prototype.toString.call(content) === '[object URL]' ) {
804
804
  type = 'url';
805
805
  }
806
806
  const url = type === 'url' ? content.toString() : undefined;
807
807
  const source_path = ['move', 'copy'].includes(type) ? content : undefined;
808
808
 
809
- if ( this.env === 'app' ){
809
+ if ( this.env === 'app' ) {
810
810
  this.messageTarget?.postMessage({
811
811
  msg: 'showSaveFilePicker',
812
812
  appInstanceID: this.appInstanceID,
@@ -820,7 +820,7 @@ class UI extends EventListener {
820
820
  }, '*');
821
821
  } else {
822
822
  window.addEventListener('message', async (e) => {
823
- if ( e.data?.msg === 'sendMeFileData' ){
823
+ if ( e.data?.msg === 'sendMeFileData' ) {
824
824
  // Send the blob URL to the host environment
825
825
  e.source.postMessage({
826
826
  msg: 'showSaveFilePickerPopup',
@@ -867,11 +867,11 @@ class UI extends EventListener {
867
867
  return resolveOnlyPromise;
868
868
  };
869
869
 
870
- setWindowTitle(title, window_id, callback) {
871
- if ( typeof window_id === 'function' ){
870
+ setWindowTitle (title, window_id, callback) {
871
+ if ( typeof window_id === 'function' ) {
872
872
  callback = window_id;
873
873
  window_id = undefined;
874
- } else if ( typeof window_id === 'object' && window_id !== null ){
874
+ } else if ( typeof window_id === 'object' && window_id !== null ) {
875
875
  window_id = window_id.id;
876
876
  }
877
877
 
@@ -880,11 +880,11 @@ class UI extends EventListener {
880
880
  });
881
881
  };
882
882
 
883
- setWindowWidth(width, window_id, callback) {
884
- if ( typeof window_id === 'function' ){
883
+ setWindowWidth (width, window_id, callback) {
884
+ if ( typeof window_id === 'function' ) {
885
885
  callback = window_id;
886
886
  window_id = undefined;
887
- } else if ( typeof window_id === 'object' && window_id !== null ){
887
+ } else if ( typeof window_id === 'object' && window_id !== null ) {
888
888
  window_id = window_id.id;
889
889
  }
890
890
 
@@ -893,11 +893,11 @@ class UI extends EventListener {
893
893
  });
894
894
  };
895
895
 
896
- setWindowHeight(height, window_id, callback) {
897
- if ( typeof window_id === 'function' ){
896
+ setWindowHeight (height, window_id, callback) {
897
+ if ( typeof window_id === 'function' ) {
898
898
  callback = window_id;
899
899
  window_id = undefined;
900
- } else if ( typeof window_id === 'object' && window_id !== null ){
900
+ } else if ( typeof window_id === 'object' && window_id !== null ) {
901
901
  window_id = window_id.id;
902
902
  }
903
903
 
@@ -906,11 +906,11 @@ class UI extends EventListener {
906
906
  });
907
907
  };
908
908
 
909
- setWindowSize(width, height, window_id, callback) {
910
- if ( typeof window_id === 'function' ){
909
+ setWindowSize (width, height, window_id, callback) {
910
+ if ( typeof window_id === 'function' ) {
911
911
  callback = window_id;
912
912
  window_id = undefined;
913
- } else if ( typeof window_id === 'object' && window_id !== null ){
913
+ } else if ( typeof window_id === 'object' && window_id !== null ) {
914
914
  window_id = window_id.id;
915
915
  }
916
916
 
@@ -919,11 +919,11 @@ class UI extends EventListener {
919
919
  });
920
920
  };
921
921
 
922
- setWindowPosition(x, y, window_id, callback) {
923
- if ( typeof window_id === 'function' ){
922
+ setWindowPosition (x, y, window_id, callback) {
923
+ if ( typeof window_id === 'function' ) {
924
924
  callback = window_id;
925
925
  window_id = undefined;
926
- } else if ( typeof window_id === 'object' && window_id !== null ){
926
+ } else if ( typeof window_id === 'object' && window_id !== null ) {
927
927
  window_id = window_id.id;
928
928
  }
929
929
 
@@ -932,11 +932,11 @@ class UI extends EventListener {
932
932
  });
933
933
  };
934
934
 
935
- setWindowY(y, window_id, callback) {
936
- if ( typeof window_id === 'function' ){
935
+ setWindowY (y, window_id, callback) {
936
+ if ( typeof window_id === 'function' ) {
937
937
  callback = window_id;
938
938
  window_id = undefined;
939
- } else if ( typeof window_id === 'object' && window_id !== null ){
939
+ } else if ( typeof window_id === 'object' && window_id !== null ) {
940
940
  window_id = window_id.id;
941
941
  }
942
942
 
@@ -945,11 +945,11 @@ class UI extends EventListener {
945
945
  });
946
946
  };
947
947
 
948
- setWindowX(x, window_id, callback) {
949
- if ( typeof window_id === 'function' ){
948
+ setWindowX (x, window_id, callback) {
949
+ if ( typeof window_id === 'function' ) {
950
950
  callback = window_id;
951
951
  window_id = undefined;
952
- } else if ( typeof window_id === 'object' && window_id !== null ){
952
+ } else if ( typeof window_id === 'object' && window_id !== null ) {
953
953
  window_id = window_id.id;
954
954
  }
955
955
 
@@ -958,23 +958,23 @@ class UI extends EventListener {
958
958
  });
959
959
  };
960
960
 
961
- showWindow() {
961
+ showWindow () {
962
962
  this.#postMessageWithObject('showWindow');
963
963
  };
964
964
 
965
- hideWindow() {
965
+ hideWindow () {
966
966
  this.#postMessageWithObject('hideWindow');
967
967
  };
968
968
 
969
- toggleWindow() {
969
+ toggleWindow () {
970
970
  this.#postMessageWithObject('toggleWindow');
971
971
  };
972
972
 
973
- setMenubar(spec) {
973
+ setMenubar (spec) {
974
974
  this.#postMessageWithObject('setMenubar', spec);
975
975
  };
976
976
 
977
- requestPermission(options) {
977
+ requestPermission (options) {
978
978
  return new Promise((resolve) => {
979
979
  if ( this.env === 'app' ) {
980
980
  return new Promise((resolve) => {
@@ -987,27 +987,27 @@ class UI extends EventListener {
987
987
  });
988
988
  };
989
989
 
990
- disableMenuItem(item_id) {
990
+ disableMenuItem (item_id) {
991
991
  this.#postMessageWithObject('disableMenuItem', { id: item_id });
992
992
  };
993
993
 
994
- enableMenuItem(item_id) {
994
+ enableMenuItem (item_id) {
995
995
  this.#postMessageWithObject('enableMenuItem', { id: item_id });
996
996
  };
997
997
 
998
- setMenuItemIcon(item_id, icon) {
998
+ setMenuItemIcon (item_id, icon) {
999
999
  this.#postMessageWithObject('setMenuItemIcon', { id: item_id, icon: icon });
1000
1000
  };
1001
1001
 
1002
- setMenuItemIconActive(item_id, icon) {
1002
+ setMenuItemIconActive (item_id, icon) {
1003
1003
  this.#postMessageWithObject('setMenuItemIconActive', { id: item_id, icon: icon });
1004
1004
  };
1005
1005
 
1006
- setMenuItemChecked(item_id, checked) {
1006
+ setMenuItemChecked (item_id, checked) {
1007
1007
  this.#postMessageWithObject('setMenuItemChecked', { id: item_id, checked: checked });
1008
1008
  };
1009
1009
 
1010
- contextMenu(spec) {
1010
+ contextMenu (spec) {
1011
1011
  this.#postMessageWithObject('contextMenu', spec);
1012
1012
  };
1013
1013
 
@@ -1027,7 +1027,7 @@ class UI extends EventListener {
1027
1027
  * const items = event.dataTransfer.items;
1028
1028
  * const entries = await getEntriesFromDataTransferItems(items, { raw: false });
1029
1029
  */
1030
- getEntriesFromDataTransferItems = async function(dataTransferItems, options = { raw: false }) {
1030
+ getEntriesFromDataTransferItems = async function (dataTransferItems, options = { raw: false }) {
1031
1031
  const checkErr = (err) => {
1032
1032
  if ( this.getEntriesFromDataTransferItems.didShowInfo ) return;
1033
1033
  if ( err.name !== 'EncodingError' ) return;
@@ -1041,7 +1041,7 @@ class UI extends EventListener {
1041
1041
  const readFile = (entry, path = '') => {
1042
1042
  return new Promise((resolve, reject) => {
1043
1043
  entry.file(file => {
1044
- if ( !options.raw ) file.filepath = path + file.name; // save full path
1044
+ if ( ! options.raw ) file.filepath = path + file.name; // save full path
1045
1045
  resolve(file);
1046
1046
  }, (err) => {
1047
1047
  checkErr(err);
@@ -1111,13 +1111,13 @@ class UI extends EventListener {
1111
1111
  return files;
1112
1112
  };
1113
1113
 
1114
- authenticateWithPuter() {
1115
- if ( this.env !== 'web' ){
1114
+ authenticateWithPuter () {
1115
+ if ( this.env !== 'web' ) {
1116
1116
  return;
1117
1117
  }
1118
1118
 
1119
1119
  // if authToken is already present, resolve immediately
1120
- if ( this.authToken ){
1120
+ if ( this.authToken ) {
1121
1121
  return new Promise((resolve) => {
1122
1122
  resolve();
1123
1123
  });
@@ -1135,7 +1135,7 @@ class UI extends EventListener {
1135
1135
  puter.puterAuthState.authGranted = null;
1136
1136
 
1137
1137
  return new Promise((resolve, reject) => {
1138
- if ( !puter.authToken ) {
1138
+ if ( ! puter.authToken ) {
1139
1139
  const puterDialog = new PuterDialog(resolve, reject);
1140
1140
  document.body.appendChild(puterDialog);
1141
1141
  puterDialog.open();
@@ -1154,7 +1154,7 @@ class UI extends EventListener {
1154
1154
  * @param {*} callback - in case you don't want to use `await` or `.then()`
1155
1155
  * @returns
1156
1156
  */
1157
- launchApp = async function launchApp(nameOrOptions, args, callback) {
1157
+ launchApp = async function launchApp (nameOrOptions, args, callback) {
1158
1158
  let pseudonym = undefined;
1159
1159
  let file_paths = undefined;
1160
1160
  let items = undefined;
@@ -1201,7 +1201,7 @@ class UI extends EventListener {
1201
1201
  return AppConnection.from(app_info, this.context);
1202
1202
  };
1203
1203
 
1204
- connectToInstance = async function connectToInstance(app_name) {
1204
+ connectToInstance = async function connectToInstance (app_name) {
1205
1205
  const app_info = await this.#ipc_stub({
1206
1206
  method: 'connectToInstance',
1207
1207
  parameters: {
@@ -1212,11 +1212,11 @@ class UI extends EventListener {
1212
1212
  return AppConnection.from(app_info, this.context);
1213
1213
  };
1214
1214
 
1215
- parentApp() {
1215
+ parentApp () {
1216
1216
  return this.#parentAppConnection;
1217
1217
  }
1218
1218
 
1219
- createWindow(options, callback) {
1219
+ createWindow (options, callback) {
1220
1220
  return new Promise((resolve) => {
1221
1221
  this.#postMessageWithCallback('createWindow', (res) => {
1222
1222
  resolve(res.window);
@@ -1225,9 +1225,9 @@ class UI extends EventListener {
1225
1225
  };
1226
1226
 
1227
1227
  // Menubar
1228
- menubar(){
1228
+ menubar () {
1229
1229
  // Remove previous style tag
1230
- document.querySelectorAll('style.puter-stylesheet').forEach(function(el) {
1230
+ document.querySelectorAll('style.puter-stylesheet').forEach(function (el) {
1231
1231
  el.remove();
1232
1232
  });
1233
1233
 
@@ -1363,15 +1363,15 @@ class UI extends EventListener {
1363
1363
  let head = document.head || document.getElementsByTagName('head')[0];
1364
1364
  head.appendChild(style);
1365
1365
 
1366
- document.addEventListener('click', function(e){
1366
+ document.addEventListener('click', function (e) {
1367
1367
  // Don't hide if clicking on disabled item
1368
1368
  if ( e.target.classList.contains('dropdown-item-disabled') )
1369
1369
  {
1370
1370
  return false;
1371
1371
  }
1372
1372
  // Hide open menus
1373
- if ( !(e.target).classList.contains('menubar-item') ){
1374
- document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
1373
+ if ( ! (e.target).classList.contains('menubar-item') ) {
1374
+ document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function (el) {
1375
1375
  el.classList.remove('menubar-item-open');
1376
1376
  });
1377
1377
 
@@ -1380,19 +1380,19 @@ class UI extends EventListener {
1380
1380
  });
1381
1381
 
1382
1382
  // When focus is gone from this window, hide open menus
1383
- window.addEventListener('blur', function(e){
1384
- document.querySelectorAll('.dropdown').forEach(function(el) {
1383
+ window.addEventListener('blur', function (e) {
1384
+ document.querySelectorAll('.dropdown').forEach(function (el) {
1385
1385
  el.style.display = 'none';
1386
1386
  });
1387
1387
  document.querySelectorAll('.menubar-item.menubar-item-open').forEach(el => el.classList.remove('menubar-item-open'));
1388
1388
  });
1389
1389
 
1390
1390
  // Returns the siblings of the element
1391
- const siblings = function(e) {
1391
+ const siblings = function (e) {
1392
1392
  const siblings = [];
1393
1393
 
1394
1394
  // if no parent, return empty list
1395
- if ( !e.parentNode ) {
1395
+ if ( ! e.parentNode ) {
1396
1396
  return siblings;
1397
1397
  }
1398
1398
 
@@ -1410,14 +1410,14 @@ class UI extends EventListener {
1410
1410
  };
1411
1411
 
1412
1412
  // Open dropdown
1413
- document.querySelectorAll('.menubar-item').forEach(el => el.addEventListener('mousedown', function(e){
1413
+ document.querySelectorAll('.menubar-item').forEach(el => el.addEventListener('mousedown', function (e) {
1414
1414
  // Hide all other menus
1415
- document.querySelectorAll('.dropdown').forEach(function(el) {
1415
+ document.querySelectorAll('.dropdown').forEach(function (el) {
1416
1416
  el.style.display = 'none';
1417
1417
  });
1418
1418
 
1419
1419
  // Remove open class from all menus, except this menu that was just clicked
1420
- document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
1420
+ document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function (el) {
1421
1421
  if ( el != e.target )
1422
1422
  {
1423
1423
  el.classList.remove('menubar-item-open');
@@ -1425,18 +1425,18 @@ class UI extends EventListener {
1425
1425
  });
1426
1426
 
1427
1427
  // If menu is already open, close it
1428
- if ( this.classList.contains('menubar-item-open') ){
1429
- document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
1428
+ if ( this.classList.contains('menubar-item-open') ) {
1429
+ document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function (el) {
1430
1430
  el.classList.remove('menubar-item-open');
1431
1431
  });
1432
1432
  }
1433
1433
 
1434
1434
  // If menu is not open, open it
1435
- else if ( !e.target.classList.contains('dropdown-item') ){
1435
+ else if ( ! e.target.classList.contains('dropdown-item') ) {
1436
1436
  this.classList.add('menubar-item-open');
1437
1437
 
1438
1438
  // show all sibling
1439
- siblings(this).forEach(function(el) {
1439
+ siblings(this).forEach(function (el) {
1440
1440
  el.style.display = 'block';
1441
1441
  });
1442
1442
  }
@@ -1444,15 +1444,15 @@ class UI extends EventListener {
1444
1444
  }));
1445
1445
 
1446
1446
  // If a menu is open, and you hover over another menu, open that menu
1447
- document.querySelectorAll('.--puter-menubar .menubar-item').forEach(el => el.addEventListener('mouseover', function(e){
1447
+ document.querySelectorAll('.--puter-menubar .menubar-item').forEach(el => el.addEventListener('mouseover', function (e) {
1448
1448
  const open_menus = document.querySelectorAll('.menubar-item.menubar-item-open');
1449
- if ( open_menus.length > 0 && open_menus[0] !== e.target ){
1449
+ if ( open_menus.length > 0 && open_menus[0] !== e.target ) {
1450
1450
  e.target.dispatchEvent(new Event('mousedown'));
1451
1451
  }
1452
1452
  }));
1453
1453
  };
1454
1454
 
1455
- on(eventName, callback) {
1455
+ on (eventName, callback) {
1456
1456
  super.on(eventName, callback);
1457
1457
  // If we already received a broadcast for this event, run the callback immediately
1458
1458
  if ( this.#eventNames.includes(eventName) && this.#lastBroadcastValue.has(eventName) ) {
@@ -1463,11 +1463,11 @@ class UI extends EventListener {
1463
1463
  #showTime = null;
1464
1464
  #hideTimeout = null;
1465
1465
 
1466
- showSpinner(html) {
1466
+ showSpinner (html) {
1467
1467
  if ( this.#overlayActive ) return;
1468
1468
 
1469
1469
  // Create and add stylesheet for spinner if it doesn't exist
1470
- if ( !document.getElementById('puter-spinner-styles') ) {
1470
+ if ( ! document.getElementById('puter-spinner-styles') ) {
1471
1471
  const styleSheet = document.createElement('style');
1472
1472
  styleSheet.id = 'puter-spinner-styles';
1473
1473
  styleSheet.textContent = `
@@ -1548,8 +1548,8 @@ class UI extends EventListener {
1548
1548
  }, 1000);
1549
1549
  }
1550
1550
 
1551
- hideSpinner() {
1552
- if ( !this.#overlayActive ) return;
1551
+ hideSpinner () {
1552
+ if ( ! this.#overlayActive ) return;
1553
1553
 
1554
1554
  if ( this.#overlayTimer ) {
1555
1555
  clearTimeout(this.#overlayTimer);
@@ -1575,7 +1575,7 @@ class UI extends EventListener {
1575
1575
  }
1576
1576
 
1577
1577
  // Add private method to handle spinner removal
1578
- #removeSpinner() {
1578
+ #removeSpinner () {
1579
1579
  const overlay = document.querySelector('.puter-loading-overlay');
1580
1580
  if ( overlay ) {
1581
1581
  overlay.parentNode?.removeChild(overlay);
@@ -1586,7 +1586,7 @@ class UI extends EventListener {
1586
1586
  this.#hideTimeout = null;
1587
1587
  }
1588
1588
 
1589
- isWorkingActive() {
1589
+ isWorkingActive () {
1590
1590
  return this.#overlayActive;
1591
1591
  }
1592
1592
 
@@ -1599,9 +1599,9 @@ class UI extends EventListener {
1599
1599
  * const currentLang = await puter.ui.getLanguage();
1600
1600
  * console.log(`Current language: ${currentLang}`); // e.g., "Current language: fr"
1601
1601
  */
1602
- getLanguage() {
1602
+ getLanguage () {
1603
1603
  // resolve with the current language code if in GUI environment
1604
- if ( this.env === 'gui' ){
1604
+ if ( this.env === 'gui' ) {
1605
1605
  // resolve with the current language code
1606
1606
  return new Promise((resolve) => {
1607
1607
  resolve(window.locale);