@heyputer/puter.js 1.0.0

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 (74) hide show
  1. package/APACHE_LICENSE.txt +201 -0
  2. package/README.md +88 -0
  3. package/doc/devlog.md +49 -0
  4. package/package.json +31 -0
  5. package/src/bg.png +0 -0
  6. package/src/bg.webp +0 -0
  7. package/src/index.js +745 -0
  8. package/src/lib/APICallLogger.js +110 -0
  9. package/src/lib/EventListener.js +51 -0
  10. package/src/lib/RequestError.js +6 -0
  11. package/src/lib/filesystem/APIFS.js +73 -0
  12. package/src/lib/filesystem/CacheFS.js +243 -0
  13. package/src/lib/filesystem/PostMessageFS.js +40 -0
  14. package/src/lib/filesystem/definitions.js +39 -0
  15. package/src/lib/path.js +509 -0
  16. package/src/lib/polyfills/localStorage.js +92 -0
  17. package/src/lib/polyfills/xhrshim.js +233 -0
  18. package/src/lib/socket.io/socket.io.esm.min.js +7 -0
  19. package/src/lib/socket.io/socket.io.esm.min.js.map +1 -0
  20. package/src/lib/socket.io/socket.io.js +4385 -0
  21. package/src/lib/socket.io/socket.io.js.map +1 -0
  22. package/src/lib/socket.io/socket.io.min.js +7 -0
  23. package/src/lib/socket.io/socket.io.min.js.map +1 -0
  24. package/src/lib/socket.io/socket.io.msgpack.min.js +7 -0
  25. package/src/lib/socket.io/socket.io.msgpack.min.js.map +1 -0
  26. package/src/lib/utils.js +620 -0
  27. package/src/lib/xdrpc.js +104 -0
  28. package/src/modules/AI.js +680 -0
  29. package/src/modules/Apps.js +215 -0
  30. package/src/modules/Auth.js +171 -0
  31. package/src/modules/Debug.js +39 -0
  32. package/src/modules/Drivers.js +278 -0
  33. package/src/modules/FSItem.js +139 -0
  34. package/src/modules/FileSystem/index.js +187 -0
  35. package/src/modules/FileSystem/operations/copy.js +64 -0
  36. package/src/modules/FileSystem/operations/deleteFSEntry.js +59 -0
  37. package/src/modules/FileSystem/operations/getReadUrl.js +42 -0
  38. package/src/modules/FileSystem/operations/mkdir.js +62 -0
  39. package/src/modules/FileSystem/operations/move.js +75 -0
  40. package/src/modules/FileSystem/operations/read.js +46 -0
  41. package/src/modules/FileSystem/operations/readdir.js +102 -0
  42. package/src/modules/FileSystem/operations/rename.js +58 -0
  43. package/src/modules/FileSystem/operations/sign.js +103 -0
  44. package/src/modules/FileSystem/operations/space.js +40 -0
  45. package/src/modules/FileSystem/operations/stat.js +95 -0
  46. package/src/modules/FileSystem/operations/symlink.js +55 -0
  47. package/src/modules/FileSystem/operations/upload.js +440 -0
  48. package/src/modules/FileSystem/operations/write.js +65 -0
  49. package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +21 -0
  50. package/src/modules/Hosting.js +138 -0
  51. package/src/modules/KV.js +301 -0
  52. package/src/modules/OS.js +95 -0
  53. package/src/modules/Perms.js +109 -0
  54. package/src/modules/PuterDialog.js +481 -0
  55. package/src/modules/Threads.js +75 -0
  56. package/src/modules/UI.js +1555 -0
  57. package/src/modules/Util.js +38 -0
  58. package/src/modules/Workers.js +120 -0
  59. package/src/modules/networking/PSocket.js +87 -0
  60. package/src/modules/networking/PTLS.js +100 -0
  61. package/src/modules/networking/PWispHandler.js +89 -0
  62. package/src/modules/networking/parsers.js +157 -0
  63. package/src/modules/networking/requests.js +282 -0
  64. package/src/services/APIAccess.js +46 -0
  65. package/src/services/FSRelay.js +20 -0
  66. package/src/services/Filesystem.js +122 -0
  67. package/src/services/NoPuterYet.js +20 -0
  68. package/src/services/XDIncoming.js +44 -0
  69. package/test/ai.test.js +214 -0
  70. package/test/fs.test.js +798 -0
  71. package/test/index.html +1183 -0
  72. package/test/kv.test.js +548 -0
  73. package/test/txt2speech.test.js +178 -0
  74. package/webpack.config.js +25 -0
@@ -0,0 +1,620 @@
1
+ /**
2
+ * Parses a given response text into a JSON object. If the parsing fails due to invalid JSON format,
3
+ * the original response text is returned.
4
+ *
5
+ * @param {string} responseText - The response text to be parsed into JSON. It is expected to be a valid JSON string.
6
+ * @returns {Object|string} The parsed JSON object if the responseText is valid JSON, otherwise returns the original responseText.
7
+ * @example
8
+ * // returns { key: "value" }
9
+ * parseResponse('{"key": "value"}');
10
+ *
11
+ * @example
12
+ * // returns "Invalid JSON"
13
+ * parseResponse('Invalid JSON');
14
+ */
15
+ async function parseResponse(target) {
16
+ if ( target.responseType === 'blob' ) {
17
+ // Get content type of the blob
18
+ const contentType = target.getResponseHeader('content-type');
19
+ if ( contentType.startsWith('application/json') ) {
20
+ // If the blob is JSON, parse it
21
+ const text = await target.response.text();
22
+ try {
23
+ return JSON.parse(text);
24
+ } catch (error) {
25
+ return text;
26
+ }
27
+ }else if ( contentType.startsWith('application/octet-stream') ) {
28
+ // If the blob is an octet stream, return the blob
29
+ return target.response;
30
+ }
31
+
32
+ // Otherwise return an ojbect
33
+ return {
34
+ success: true,
35
+ result: target.response,
36
+ };
37
+ }
38
+ const responseText = target.responseText;
39
+ try {
40
+ return JSON.parse(responseText);
41
+ } catch (error) {
42
+ return responseText;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * A function that generates a UUID (Universally Unique Identifier) using the version 4 format,
48
+ * which are random UUIDs. It uses the cryptographic number generator available in modern browsers.
49
+ *
50
+ * The generated UUID is a 36 character string (32 alphanumeric characters separated by 4 hyphens).
51
+ * It follows the pattern: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where x is any hexadecimal digit
52
+ * and y is one of 8, 9, A, or B.
53
+ *
54
+ * @returns {string} Returns a new UUID v4 string.
55
+ *
56
+ * @example
57
+ *
58
+ * let id = this.#uuidv4(); // Generate a new UUID
59
+ *
60
+ */
61
+ function uuidv4(){
62
+ return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
63
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
64
+ );
65
+ }
66
+
67
+
68
+ /**
69
+ * Initializes and returns an XMLHttpRequest object configured for a specific API endpoint, method, and headers.
70
+ *
71
+ * @param {string} endpoint - The API endpoint to which the request will be sent. This is appended to the API origin URL.
72
+ * @param {string} APIOrigin - The origin URL of the API. This is prepended to the endpoint.
73
+ * @param {string} authToken - The authorization token used for accessing the API. This is included in the request headers.
74
+ * @param {string} [method='post'] - The HTTP method to be used for the request. Defaults to 'post' if not specified.
75
+ * @param {string} [contentType='application/json;charset=UTF-8'] - The content type of the request. Defaults to
76
+ * 'application/json;charset=UTF-8' if not specified.
77
+ *
78
+ * @returns {XMLHttpRequest} The initialized XMLHttpRequest object.
79
+ */
80
+ function initXhr(endpoint, APIOrigin, authToken, method= "post", contentType = "text/plain;actually=json", responseType = undefined) {
81
+ const xhr = new XMLHttpRequest();
82
+ xhr.open(method, APIOrigin + endpoint, true);
83
+ if (authToken)
84
+ xhr.setRequestHeader("Authorization", "Bearer " + authToken);
85
+ xhr.setRequestHeader("Content-Type", contentType);
86
+ xhr.responseType = responseType ?? '';
87
+
88
+ // Add API call logging if available
89
+ if (globalThis.puter?.apiCallLogger?.isEnabled()) {
90
+ xhr._puterRequestId = {
91
+ method,
92
+ service: 'xhr',
93
+ operation: endpoint.replace(/^\//, ''),
94
+ params: { endpoint, contentType, responseType }
95
+ };
96
+ }
97
+
98
+ return xhr;
99
+ }
100
+
101
+ /**
102
+ * Handles an HTTP response by invoking appropriate callback functions and resolving or rejecting a promise.
103
+ *
104
+ * @param {Function} success_cb - An optional callback function for successful responses. It should take a response object
105
+ * as its only argument.
106
+ * @param {Function} error_cb - An optional callback function for error handling. It should take an error object
107
+ * as its only argument.
108
+ * @param {Function} resolve_func - A function used to resolve a promise. It should take a response object
109
+ * as its only argument.
110
+ * @param {Function} reject_func - A function used to reject a promise. It should take an error object
111
+ * as its only argument.
112
+ * @param {Object} response - The HTTP response object from the request. Expected to have 'status' and 'responseText'
113
+ * properties.
114
+ *
115
+ * @returns {void} The function does not return a value but will either resolve or reject a promise based on the
116
+ * response status.
117
+ */
118
+ async function handle_resp(success_cb, error_cb, resolve_func, reject_func, response){
119
+ const resp = await parseResponse(response);
120
+ // error - unauthorized
121
+ if(response.status === 401){
122
+ // if error callback is provided, call it
123
+ if(error_cb && typeof error_cb === 'function')
124
+ error_cb({status: 401, message: 'Unauthorized'})
125
+ // reject promise
126
+ return reject_func({status: 401, message: 'Unauthorized'})
127
+ }
128
+ // error - other
129
+ else if(response.status !== 200){
130
+ // if error callback is provided, call it
131
+ if(error_cb && typeof error_cb === 'function')
132
+ error_cb(resp)
133
+ // reject promise
134
+ return reject_func(resp)
135
+ }
136
+ // success
137
+ else{
138
+ // This is a driver error
139
+ if(resp.success === false && resp.error?.code === 'permission_denied'){
140
+ let perm = await puter.ui.requestPermission({permission: 'driver:puter-image-generation:generate'});
141
+ // try sending again if permission was granted
142
+ if(perm.granted){
143
+ // todo repeat request
144
+ }
145
+ }
146
+ // if success callback is provided, call it
147
+ if(success_cb && typeof success_cb === 'function')
148
+ success_cb(resp);
149
+ // resolve with success
150
+ return resolve_func(resp);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Handles an error by invoking a specified error callback and then rejecting a promise.
156
+ *
157
+ * @param {Function} error_cb - An optional callback function that is called if it's provided.
158
+ * This function should take an error object as its only argument.
159
+ * @param {Function} reject_func - A function used to reject a promise. It should take an error object
160
+ * as its only argument.
161
+ * @param {Object} error - The error object that is passed to both the error callback and the reject function.
162
+ *
163
+ * @returns {void} The function does not return a value but will call the reject function with the error.
164
+ */
165
+ function handle_error(error_cb, reject_func, error){
166
+ // if error callback is provided, call it
167
+ if(error_cb && typeof error_cb === 'function')
168
+ error_cb(error)
169
+ // reject promise
170
+ return reject_func(error)
171
+ }
172
+
173
+ function setupXhrEventHandlers(xhr, success_cb, error_cb, resolve_func, reject_func) {
174
+ // load: success or error
175
+ xhr.addEventListener('load', async function(e){
176
+ // Log the response if API logging is enabled
177
+ if (globalThis.puter?.apiCallLogger?.isEnabled() && this._puterRequestId) {
178
+ const response = await parseResponse(this).catch(() => null);
179
+ globalThis.puter.apiCallLogger.logRequest({
180
+ service: this._puterRequestId.service,
181
+ operation: this._puterRequestId.operation,
182
+ params: this._puterRequestId.params,
183
+ result: this.status >= 400 ? null : response,
184
+ error: this.status >= 400 ? { message: this.statusText, status: this.status } : null
185
+ });
186
+ }
187
+ return handle_resp(success_cb, error_cb, resolve_func, reject_func, this, xhr);
188
+ });
189
+
190
+ // error
191
+ xhr.addEventListener('error', function(e){
192
+ // Log the error if API logging is enabled
193
+ if (globalThis.puter?.apiCallLogger?.isEnabled() && this._puterRequestId) {
194
+ globalThis.puter.apiCallLogger.logRequest({
195
+ service: this._puterRequestId.service,
196
+ operation: this._puterRequestId.operation,
197
+ params: this._puterRequestId.params,
198
+ error: {
199
+ message: 'Network error occurred',
200
+ event: e.type
201
+ }
202
+ });
203
+ }
204
+ return handle_error(error_cb, reject_func, this);
205
+ })
206
+ }
207
+
208
+ const NOOP = () => {};
209
+ class Valid {
210
+ static callback (cb) {
211
+ return (cb && typeof cb === 'function') ? cb : undefined;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Makes the hybrid promise/callback function for a particular driver method
217
+ * @param {string[]} arg_defs - argument names (for now; definitions eventually)
218
+ * @param {string} driverInterface - name of the interface
219
+ * @param {string} driverName - name of the driver
220
+ * @param {string} driverMethod - name of the method
221
+ */
222
+ function make_driver_method(arg_defs, driverInterface, driverName, driverMethod, settings = {}) {
223
+ return async function (...args) {
224
+ let driverArgs = {};
225
+ let options = {};
226
+
227
+ // Check if the first argument is an object and use it as named parameters
228
+ if (args.length === 1 && typeof args[0] === 'object' && !Array.isArray(args[0])) {
229
+ driverArgs = { ...args[0] };
230
+ options = {
231
+ success: driverArgs.success,
232
+ error: driverArgs.error,
233
+ };
234
+ // Remove callback functions from driverArgs if they exist
235
+ delete driverArgs.success;
236
+ delete driverArgs.error;
237
+ } else {
238
+ // Handle as individual arguments
239
+ arg_defs.forEach((argName, index) => {
240
+ driverArgs[argName] = args[index];
241
+ });
242
+ options = {
243
+ success: args[arg_defs.length],
244
+ error: args[arg_defs.length + 1],
245
+ };
246
+ }
247
+
248
+ // preprocess
249
+ if(settings.preprocess && typeof settings.preprocess === 'function'){
250
+ driverArgs = settings.preprocess(driverArgs);
251
+ }
252
+
253
+ return await driverCall(options, driverInterface, driverName, driverMethod, driverArgs, settings);
254
+ };
255
+ }
256
+
257
+ async function driverCall (options, driverInterface, driverName, driverMethod, driverArgs, settings) {
258
+ const tp = new TeePromise();
259
+
260
+ driverCall_(
261
+ options,
262
+ tp.resolve.bind(tp),
263
+ tp.reject.bind(tp),
264
+ driverInterface,
265
+ driverName,
266
+ driverMethod,
267
+ driverArgs,
268
+ undefined,
269
+ undefined,
270
+ settings,
271
+ );
272
+
273
+ return await tp;
274
+ }
275
+
276
+ // This function encapsulates the logic for sending a driver call request
277
+ async function driverCall_(
278
+ options = {},
279
+ resolve_func, reject_func,
280
+ driverInterface, driverName, driverMethod, driverArgs,
281
+ method,
282
+ contentType = 'text/plain;actually=json',
283
+ settings = {},
284
+ ) {
285
+ // Generate request ID for logging
286
+ // Store request info for logging
287
+ let requestInfo = null;
288
+ if (globalThis.puter?.apiCallLogger?.isEnabled()) {
289
+ requestInfo = {
290
+ interface: driverInterface,
291
+ driver: driverName,
292
+ method: driverMethod,
293
+ args: driverArgs
294
+ };
295
+ }
296
+
297
+ // If there is no authToken and the environment is web, try authenticating with Puter
298
+ if(!puter.authToken && puter.env === 'web'){
299
+ try{
300
+ await puter.ui.authenticateWithPuter();
301
+ }catch(e){
302
+ // Log authentication error
303
+ if (requestInfo && globalThis.puter?.apiCallLogger?.isEnabled()) {
304
+ globalThis.puter.apiCallLogger.logRequest({
305
+ service: 'drivers',
306
+ operation: `${driverInterface}::${driverMethod}`,
307
+ params: { interface: driverInterface, driver: driverName, method: driverMethod, args: driverArgs },
308
+ error: { code: 'auth_canceled', message: 'Authentication canceled' }
309
+ });
310
+ }
311
+ return reject_func({
312
+ error: {
313
+ code: 'auth_canceled', message: 'Authentication canceled'
314
+ }
315
+ })
316
+ }
317
+ }
318
+
319
+ const success_cb = Valid.callback(options.success) ?? NOOP;
320
+ const error_cb = Valid.callback(options.error) ?? NOOP;
321
+ // create xhr object
322
+ const xhr = initXhr('/drivers/call', puter.APIOrigin, undefined, 'POST', contentType);
323
+
324
+ // Store request info for later logging
325
+ if (requestInfo) {
326
+ xhr._puterDriverRequestInfo = requestInfo;
327
+ }
328
+
329
+ if ( settings.responseType ) {
330
+ xhr.responseType = settings.responseType;
331
+ }
332
+
333
+ // ===============================================
334
+ // TO UNDERSTAND THIS CODE, YOU MUST FIRST
335
+ // UNDERSTAND THE FOLLOWING TEXT:
336
+ //
337
+ // Everything between here and the comment reading
338
+ // "=== END OF STREAMING ===" is ONLY for handling
339
+ // requests with content type "application/x-ndjson"
340
+ // ===============================================
341
+
342
+ let is_stream = false;
343
+ let signal_stream_update = null;
344
+ let lastLength = 0;
345
+ let response_complete = false;
346
+
347
+ let buffer = '';
348
+
349
+ // NOTE: linked-list technically would perform better,
350
+ // but in practice there are at most 2-3 lines
351
+ // buffered so this does not matter.
352
+ const lines_received = [];
353
+
354
+ xhr.onreadystatechange = () => {
355
+ if ( xhr.readyState === 2 ) {
356
+ if ( xhr.getResponseHeader("Content-Type") !==
357
+ 'application/x-ndjson'
358
+ ) return;
359
+ is_stream = true;
360
+ const Stream = async function* Stream () {
361
+ while ( ! response_complete ) {
362
+ const tp = new TeePromise();
363
+ signal_stream_update = tp.resolve.bind(tp);
364
+ await tp;
365
+ if ( response_complete ) break;
366
+ while ( lines_received.length > 0 ) {
367
+ const line = lines_received.shift();
368
+ if ( line.trim() === '' ) continue;
369
+ const lineObject = (JSON.parse(line));
370
+ if (typeof (lineObject.text) === 'string') {
371
+ Object.defineProperty(lineObject, 'toString', {
372
+ enumerable: false,
373
+ value: () => lineObject.text,
374
+ });
375
+ }
376
+ yield lineObject;
377
+ }
378
+ }
379
+ }
380
+
381
+ const startedStream = Stream();
382
+ Object.defineProperty(startedStream, 'start', {
383
+ enumerable: false,
384
+ value: async(controller) => {
385
+ const texten = new TextEncoder();
386
+ for await (const part of startedStream) {
387
+ controller.enqueue(texten.encode(part))
388
+ }
389
+ controller.close();
390
+ }
391
+ })
392
+
393
+ return resolve_func(startedStream);
394
+ }
395
+ if ( xhr.readyState === 4 ) {
396
+ response_complete = true;
397
+ if ( is_stream ) {
398
+ signal_stream_update?.();
399
+ }
400
+ }
401
+ };
402
+
403
+ xhr.onprogress = function() {
404
+ if ( ! signal_stream_update ) return;
405
+
406
+ const newText = xhr.responseText.slice(lastLength);
407
+ lastLength = xhr.responseText.length; // Update lastLength to the current length
408
+
409
+ let hasUpdates = false;
410
+ for ( let i = 0; i < newText.length; i++ ) {
411
+ buffer += newText[i];
412
+ if ( newText[i] === '\n' ) {
413
+ hasUpdates = true;
414
+ lines_received.push(buffer);
415
+ buffer = '';
416
+ }
417
+ }
418
+
419
+ if ( hasUpdates ) {
420
+ signal_stream_update();
421
+ }
422
+ };
423
+
424
+ // ========================
425
+ // === END OF STREAMING ===
426
+ // ========================
427
+
428
+ // load: success or error
429
+ xhr.addEventListener('load', async function(response){
430
+ if ( is_stream ) {
431
+ return;
432
+ }
433
+ const resp = await parseResponse(response.target);
434
+
435
+ // Log driver call response
436
+ if (this._puterDriverRequestInfo && globalThis.puter?.apiCallLogger?.isEnabled()) {
437
+ globalThis.puter.apiCallLogger.logRequest({
438
+ service: 'drivers',
439
+ operation: `${this._puterDriverRequestInfo.interface}::${this._puterDriverRequestInfo.method}`,
440
+ params: { interface: this._puterDriverRequestInfo.interface, driver: this._puterDriverRequestInfo.driver, method: this._puterDriverRequestInfo.method, args: this._puterDriverRequestInfo.args },
441
+ result: response.status >= 400 || resp?.success === false ? null : resp,
442
+ error: response.status >= 400 || resp?.success === false ? resp : null
443
+ });
444
+ }
445
+
446
+ // HTTP Error - unauthorized
447
+ if(response.status === 401 || resp?.code === "token_auth_failed"){
448
+ if(resp?.code === "token_auth_failed" && puter.env === 'web'){
449
+ try{
450
+ puter.resetAuthToken();
451
+ await puter.ui.authenticateWithPuter();
452
+ }catch(e){
453
+ return reject_func({
454
+ error: {
455
+ code: 'auth_canceled', message: 'Authentication canceled'
456
+ }
457
+ })
458
+ }
459
+ }
460
+ // if error callback is provided, call it
461
+ if(error_cb && typeof error_cb === 'function')
462
+ error_cb({status: 401, message: 'Unauthorized'})
463
+ // reject promise
464
+ return reject_func({status: 401, message: 'Unauthorized'})
465
+ }
466
+ // HTTP Error - other
467
+ else if(response.status && response.status !== 200){
468
+ // if error callback is provided, call it
469
+ error_cb(resp)
470
+ // reject promise
471
+ return reject_func(resp)
472
+ }
473
+ // HTTP Success
474
+ else{
475
+ // Driver Error: permission denied
476
+ if(resp.success === false && resp.error?.code === 'permission_denied'){
477
+ let perm = await puter.ui.requestPermission({permission: 'driver:' + driverInterface + ':' + driverMethod});
478
+ // try sending again if permission was granted
479
+ if(perm.granted){
480
+ // repeat request with permission granted
481
+ return driverCall_(options, resolve_func, reject_func, driverInterface, driverMethod, driverArgs, method, contentType, settings);
482
+ }else{
483
+ // if error callback is provided, call it
484
+ error_cb(resp)
485
+ // reject promise
486
+ return reject_func(resp)
487
+ }
488
+ }
489
+ // Driver Error: other
490
+ else if(resp.success === false){
491
+ // if error callback is provided, call it
492
+ error_cb(resp)
493
+ // reject promise
494
+ return reject_func(resp)
495
+ }
496
+
497
+ let result = resp.result !== undefined ? resp.result : resp;
498
+ if ( settings.transform ) {
499
+ result = await settings.transform(result);
500
+ }
501
+
502
+ // Success: if callback is provided, call it
503
+ if(resolve_func.success)
504
+ success_cb(result);
505
+ // Success: resolve with the result
506
+ return resolve_func(result);
507
+ }
508
+ });
509
+
510
+ // error
511
+ xhr.addEventListener('error', function(e){
512
+ // Log driver call error
513
+ if (this._puterDriverRequestInfo && globalThis.puter?.apiCallLogger?.isEnabled()) {
514
+ globalThis.puter.apiCallLogger.logRequest({
515
+ service: 'drivers',
516
+ operation: `${this._puterDriverRequestInfo.interface}::${this._puterDriverRequestInfo.method}`,
517
+ params: { interface: this._puterDriverRequestInfo.interface, driver: this._puterDriverRequestInfo.driver, method: this._puterDriverRequestInfo.method, args: this._puterDriverRequestInfo.args },
518
+ error: { message: 'Network error occurred', event: e.type }
519
+ });
520
+ }
521
+ return handle_error(error_cb, reject_func, this);
522
+ })
523
+
524
+ // send request
525
+ xhr.send(JSON.stringify({
526
+ interface: driverInterface,
527
+ driver: driverName,
528
+ test_mode: settings?.test_mode,
529
+ method: driverMethod,
530
+ args: driverArgs,
531
+ auth_token: puter.authToken
532
+ }));
533
+ }
534
+
535
+ class TeePromise {
536
+ static STATUS_PENDING = {};
537
+ static STATUS_RUNNING = {};
538
+ static STATUS_DONE = {};
539
+ constructor () {
540
+ this.status_ = this.constructor.STATUS_PENDING;
541
+ this.donePromise = new Promise((resolve, reject) => {
542
+ this.doneResolve = resolve;
543
+ this.doneReject = reject;
544
+ });
545
+ }
546
+ get status () {
547
+ return this.status_;
548
+ }
549
+ set status (status) {
550
+ this.status_ = status;
551
+ if ( status === this.constructor.STATUS_DONE ) {
552
+ this.doneResolve();
553
+ }
554
+ }
555
+ resolve (value) {
556
+ this.status_ = this.constructor.STATUS_DONE;
557
+ this.doneResolve(value);
558
+ }
559
+ awaitDone () {
560
+ return this.donePromise;
561
+ }
562
+ then (fn, rfn) {
563
+ return this.donePromise.then(fn, rfn);
564
+ }
565
+
566
+ reject (err) {
567
+ this.status_ = this.constructor.STATUS_DONE;
568
+ this.doneReject(err);
569
+ }
570
+
571
+ /**
572
+ * @deprecated use then() instead
573
+ */
574
+ onComplete(fn) {
575
+ return this.then(fn);
576
+ }
577
+ }
578
+
579
+ async function blob_to_url (blob) {
580
+ const tp = new TeePromise();
581
+ const reader = new FileReader();
582
+ reader.onloadend = () => tp.resolve(reader.result);
583
+ reader.readAsDataURL(blob);
584
+ return await tp;
585
+ }
586
+
587
+ function blobToDataUri(blob) {
588
+ return new Promise((resolve, reject) => {
589
+ const reader = new FileReader();
590
+ reader.onload = function(event) {
591
+ resolve(event.target.result);
592
+ };
593
+ reader.onerror = function(error) {
594
+ reject(error);
595
+ };
596
+ reader.readAsDataURL(blob);
597
+ });
598
+ }
599
+
600
+ function arrayBufferToDataUri(arrayBuffer) {
601
+ return new Promise((resolve, reject) => {
602
+ const blob = new Blob([arrayBuffer]);
603
+ const reader = new FileReader();
604
+ reader.onload = function(event) {
605
+ resolve(event.target.result);
606
+ };
607
+ reader.onerror = function(error) {
608
+ reject(error);
609
+ };
610
+ reader.readAsDataURL(blob);
611
+ });
612
+ }
613
+
614
+ export {parseResponse, uuidv4, handle_resp, handle_error, initXhr, setupXhrEventHandlers, driverCall,
615
+ TeePromise,
616
+ make_driver_method,
617
+ blob_to_url,
618
+ arrayBufferToDataUri,
619
+ blobToDataUri,
620
+ };