@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,440 @@
1
+ import * as utils from '../../../lib/utils.js';
2
+ import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
3
+ import path from "../../../lib/path.js"
4
+
5
+ const upload = async function(items, dirPath, options = {}){
6
+ return new Promise(async (resolve, reject) => {
7
+ const DataTransferItem = globalThis.DataTransfer || (class DataTransferItem {});
8
+ const FileList = globalThis.FileList || (class FileList {});
9
+ const DataTransferItemList = globalThis.DataTransferItemList || (class DataTransferItemList {});
10
+
11
+ // If auth token is not provided and we are in the web environment,
12
+ // try to authenticate with Puter
13
+ if(!puter.authToken && puter.env === 'web'){
14
+ try{
15
+ await puter.ui.authenticateWithPuter();
16
+ }catch(e){
17
+ // if authentication fails, throw an error
18
+ reject(e);
19
+ }
20
+ }
21
+
22
+ const error = (e) => {
23
+ // if error callback is provided, call it
24
+ if(options.error && typeof options.error === 'function')
25
+ options.error(e);
26
+ return reject(e);
27
+ };
28
+
29
+ // xhr object to be used for the upload
30
+ let xhr = new XMLHttpRequest();
31
+
32
+ // Can not write to root
33
+ if(dirPath === '/')
34
+ return error('Can not upload to root directory.');
35
+
36
+ // If dirPath is not provided or it's not starting with a slash, it means it's a relative path
37
+ // in that case, we need to prepend the app's root directory to it
38
+ dirPath = getAbsolutePathForApp(dirPath);
39
+
40
+ // Generate a unique ID for this upload operation
41
+ // This will be used to uniquely identify this operation and its progress
42
+ // across servers and clients
43
+ const operation_id = utils.uuidv4();
44
+
45
+ // Call 'init' callback if provided
46
+ // init is basically a hook that allows the user to get the operation ID and the XMLHttpRequest object
47
+ if(options.init && typeof options.init === 'function'){
48
+ options.init(operation_id, xhr);
49
+ }
50
+
51
+ // keeps track of the amount of data uploaded to the server
52
+ let bytes_uploaded_to_server = 0;
53
+ // keeps track of the amount of data uploaded to the cloud
54
+ let bytes_uploaded_to_cloud = 0;
55
+
56
+ // This will hold the normalized entries to be uploaded
57
+ // Since 'items' could be a DataTransferItemList, FileList, File, or an array of any of these,
58
+ // we need to normalize it into an array of consistently formatted objects which will be held in 'entries'
59
+ let entries;
60
+
61
+ // will hold the total size of the upload
62
+ let total_size = 0;
63
+ let file_count = 0;
64
+
65
+ let seemsToBeParsedDataTransferItems = false;
66
+ if(Array.isArray(items) && items.length > 0){
67
+ for(let i=0; i<items.length; i++){
68
+ if(items[i] instanceof DataTransferItem || items[i] instanceof DataTransferItemList){
69
+ seemsToBeParsedDataTransferItems = true;
70
+ }
71
+ }
72
+ }
73
+
74
+ // DataTransferItemList
75
+ if(items instanceof DataTransferItemList || items instanceof DataTransferItem || items[0] instanceof DataTransferItem || options.parsedDataTransferItems){
76
+ // if parsedDataTransferItems is true, it means the user has already parsed the DataTransferItems
77
+ if(options.parsedDataTransferItems)
78
+ entries = items;
79
+ else
80
+ entries = await puter.ui.getEntriesFromDataTransferItems(items);
81
+
82
+ // Sort entries by size ascending
83
+ entries.sort((entry_a, entry_b) => {
84
+ if ( entry_a.isDirectory && ! entry_b.isDirectory ) return -1;
85
+ if ( ! entry_a.isDirectory && entry_b.isDirectory ) return 1;
86
+ if ( entry_a.isDirectory && entry_b.isDirectory ) return 0;
87
+
88
+ return entry_a.size - entry_b.size;
89
+ });
90
+ }
91
+ // FileList/File
92
+ else if(items instanceof File || items[0] instanceof File || items instanceof FileList || items[0] instanceof FileList){
93
+ if(!Array.isArray(items))
94
+ entries = items instanceof FileList ? Array.from(items) : [items];
95
+ else
96
+ entries = items;
97
+
98
+ // Sort entries by size ascending
99
+ entries.sort((entry_a, entry_b) => {
100
+ return entry_a.size - entry_b.size;
101
+ })
102
+ // add FullPath property to each entry
103
+ for(let i=0; i<entries.length; i++){
104
+ entries[i].filepath = entries[i].name;
105
+ entries[i].fullPath = entries[i].name;
106
+ }
107
+ }
108
+ // blob
109
+ else if(items instanceof Blob){
110
+ // create a File object from the blob
111
+ let file = new File([items], options.name, { type: "application/octet-stream" });
112
+ entries = [file];
113
+ // add FullPath property to each entry
114
+ for(let i=0; i<entries.length; i++){
115
+ entries[i].filepath = entries[i].name;
116
+ entries[i].fullPath = entries[i].name;
117
+ }
118
+ }
119
+ // String
120
+ else if(typeof items === 'string'){
121
+ // create a File object from the string
122
+ let file = new File([items], 'default.txt', { type: "text/plain" });
123
+ entries = [file];
124
+ // add FullPath property to each entry
125
+ for(let i=0; i<entries.length; i++){
126
+ entries[i].filepath = entries[i].name;
127
+ entries[i].fullPath = entries[i].name;
128
+ }
129
+ }
130
+ // Anything else is invalid
131
+ else {
132
+ return error({ code: 'field_invalid', message: 'upload() items parameter is an invalid type' });
133
+ }
134
+
135
+ // Will hold directories and files to be uploaded
136
+ let dirs = [];
137
+ let uniqueDirs = {}
138
+ let files = [];
139
+
140
+ // Separate files from directories
141
+ for(let i=0; i<entries.length; i++){
142
+ // skip empty entries
143
+ if(!entries[i])
144
+ continue;
145
+ //collect dirs
146
+ if(entries[i].isDirectory)
147
+ dirs.push({path: path.join(dirPath, entries[i].finalPath ? entries[i].finalPath : entries[i].fullPath)});
148
+ // also files
149
+ else{
150
+ // Dragged and dropped files do not have a finalPath property and hence the fileItem will go undefined.
151
+ // In such cases, we need default to creating the files as uploaded by the user.
152
+ let fileItem = entries[i].finalPath ? entries[i].finalPath : entries[i].fullPath;
153
+ let [dirLevel, fileName] = [fileItem?.slice(0, fileItem?.lastIndexOf("/")), fileItem?.slice(fileItem?.lastIndexOf("/") + 1)]
154
+
155
+ // If file name is blank then we need to create only an empty directory.
156
+ // On the other hand if the file name is not blank(could be undefined), we need to create the file.
157
+ fileName != "" && files.push(entries[i])
158
+ if (options.createFileParent && fileItem.includes('/')) {
159
+ let incrementalDir;
160
+ dirLevel.split('/').forEach((directory) => {
161
+ incrementalDir = incrementalDir ? incrementalDir + '/' + directory : directory;
162
+ let filePath = path.join(dirPath, incrementalDir)
163
+ // Prevent duplicate parent directory creation
164
+ if(!uniqueDirs[filePath]){
165
+ uniqueDirs[filePath] = true;
166
+ dirs.push({path: filePath});
167
+ }
168
+ })
169
+ }
170
+ }
171
+ // stats about the upload to come
172
+ if(entries[i].size !== undefined){
173
+ total_size += (entries[i].size);
174
+ file_count++;
175
+ }
176
+ }
177
+
178
+ // Continue only if there are actually any files/directories to upload
179
+ if(dirs.length === 0 && files.length === 0){
180
+ return error({code: 'EMPTY_UPLOAD', message: 'No files or directories to upload.'});
181
+ }
182
+
183
+ // Check storage capacity.
184
+ // We need to check the storage capacity before the upload starts because
185
+ // we want to avoid uploading files in case there is not enough storage space.
186
+ // If we didn't check before upload starts, we could end up in a scenario where
187
+ // the user uploads a very large folder/file and then the server rejects it because there is not enough space
188
+ //
189
+ // Space check in 'web' environment is currently not supported since it requires permissions.
190
+ let storage;
191
+ if(puter.env !== 'web'){
192
+ try{
193
+ storage = await this.space();
194
+ if(storage.capacity - storage.used < total_size){
195
+ return error({code: 'NOT_ENOUGH_SPACE', message: 'Not enough storage space available.'});
196
+ }
197
+ }catch(e){
198
+ // Ignored
199
+ }
200
+ }
201
+
202
+ // total size of the upload is doubled because we will be uploading the files to the server
203
+ // and then the server will upload them to the cloud
204
+ total_size = total_size * 2;
205
+
206
+ // holds the data to be sent to the server
207
+ const fd = new FormData();
208
+
209
+ //-------------------------------------------------
210
+ // Generate the requests to create all the
211
+ // folders in this upload
212
+ //-------------------------------------------------
213
+ dirs.sort((a, b) => b.path.length - a.path.length);
214
+ let mkdir_requests = [];
215
+
216
+ for(let i=0; i < dirs.length; i++){
217
+ // update all file paths under this folder if dirname was changed
218
+ for(let j=0; j<files.length; j++){
219
+ // if file is in this folder and has not been processed yet
220
+ if(!files[j].puter_path_param && path.join(dirPath, files[j].filepath).startsWith((dirs[i].path) + '/')){
221
+ files[j].puter_path_param = `$dir_${i}/`+ path.basename(files[j].filepath);
222
+ }
223
+ }
224
+
225
+ // update all subdirs under this dir
226
+ for(let k=0; k < dirs.length; k++){
227
+ if(!dirs[k].puter_path_param && dirs[k].path.startsWith(dirs[i].path + '/')){
228
+ dirs[k].puter_path_param = `$dir_${i}/`+ path.basename(dirs[k].path);
229
+ }
230
+ }
231
+ }
232
+
233
+ for(let i=0; i < dirs.length; i++){
234
+ let parent_path = path.dirname(dirs[i].puter_path_param || dirs[i].path);
235
+ let dir_path = dirs[i].puter_path_param || dirs[i].path;
236
+
237
+ // remove parent path from the beginning of path since path is relative to parent
238
+ if(parent_path !== '/')
239
+ dir_path = dir_path.replace(parent_path, '');
240
+
241
+ mkdir_requests.push({
242
+ op: 'mkdir',
243
+ parent: parent_path,
244
+ path: dir_path,
245
+ overwrite: options.overwrite ?? false,
246
+ dedupe_name: options.dedupeName ?? true,
247
+ create_missing_ancestors: options.createMissingAncestors ?? true,
248
+ as: `dir_${i}`,
249
+ });
250
+ }
251
+
252
+ // inverse mkdir_requests so that the root folder is created first
253
+ // and then go down the tree
254
+ mkdir_requests.reverse();
255
+
256
+ fd.append('operation_id', operation_id);
257
+ fd.append('socket_id', this.socket.id);
258
+ fd.append('original_client_socket_id', this.socket.id);
259
+
260
+ // Append mkdir operations to upload request
261
+ for(let i=0; i<mkdir_requests.length; i++){
262
+ fd.append('operation', JSON.stringify(mkdir_requests[i]));
263
+ }
264
+
265
+ // Append file metadata to upload request
266
+ if(!options.shortcutTo){
267
+ for(let i=0; i<files.length; i++){
268
+ fd.append('fileinfo', JSON.stringify({
269
+ name: files[i].name,
270
+ type: files[i].type,
271
+ size: files[i].size,
272
+ }));
273
+ }
274
+ }
275
+ // Append write operations for each file
276
+ for(let i=0; i<files.length; i++){
277
+ fd.append('operation', JSON.stringify({
278
+ op: options.shortcutTo ? 'shortcut' : 'write',
279
+ dedupe_name: options.dedupeName ?? true,
280
+ overwrite: options.overwrite ?? false,
281
+ create_missing_ancestors: (options.createMissingAncestors || options.createMissingParents),
282
+ operation_id: operation_id,
283
+ path: (
284
+ files[i].puter_path_param &&
285
+ path.dirname(files[i].puter_path_param ?? '')
286
+ ) || (
287
+ files[i].filepath &&
288
+ path.join(dirPath, path.dirname(files[i].filepath))
289
+ ) || '',
290
+ name: path.basename(files[i].filepath),
291
+ item_upload_id: i,
292
+ shortcut_to: options.shortcutTo,
293
+ shortcut_to_uid: options.shortcutTo,
294
+ app_uid: options.appUID,
295
+ }));
296
+ }
297
+
298
+ // Append files to upload
299
+ if(!options.shortcutTo){
300
+ for(let i=0; i<files.length; i++){
301
+ fd.append('file', files[i] ?? '');
302
+ }
303
+ }
304
+
305
+ const progress_handler = (msg) => {
306
+ if(msg.operation_id === operation_id){
307
+ bytes_uploaded_to_cloud += msg.loaded_diff
308
+ }
309
+ }
310
+
311
+ // Handle upload progress events from server
312
+ this.socket.on('upload.progress', progress_handler);
313
+
314
+ // keeps track of the amount of data uploaded to the server
315
+ let previous_chunk_uploaded = null;
316
+
317
+ // open request to server
318
+ xhr.open("post",(this.APIOrigin +'/batch'), true);
319
+ // set auth header
320
+ xhr.setRequestHeader("Authorization", "Bearer " + this.authToken);
321
+
322
+ // -----------------------------------------------
323
+ // Upload progress: client -> server
324
+ // -----------------------------------------------
325
+ xhr.upload.addEventListener('progress', function(e){
326
+ // update operation tracker
327
+ let chunk_uploaded;
328
+ if(previous_chunk_uploaded === null){
329
+ chunk_uploaded = e.loaded;
330
+ previous_chunk_uploaded = 0;
331
+ }else{
332
+ chunk_uploaded = e.loaded - previous_chunk_uploaded;
333
+ }
334
+ previous_chunk_uploaded += chunk_uploaded;
335
+ bytes_uploaded_to_server += chunk_uploaded;
336
+
337
+ // overall operation progress
338
+ let op_progress = ((bytes_uploaded_to_cloud + bytes_uploaded_to_server)/total_size * 100).toFixed(2);
339
+ op_progress = op_progress > 100 ? 100 : op_progress;
340
+
341
+ // progress callback function
342
+ if(options.progress && typeof options.progress === 'function')
343
+ options.progress(operation_id, op_progress);
344
+ })
345
+
346
+ // -----------------------------------------------
347
+ // Upload progress: server -> cloud
348
+ // the following code will check the progress of the upload every 100ms
349
+ // -----------------------------------------------
350
+ let cloud_progress_check_interval = setInterval(function() {
351
+ // operation progress
352
+ let op_progress = ((bytes_uploaded_to_cloud + bytes_uploaded_to_server)/total_size * 100).toFixed(2);
353
+
354
+ op_progress = op_progress > 100 ? 100 : op_progress;
355
+ if(options.progress && typeof options.progress === 'function')
356
+ options.progress(operation_id, op_progress);
357
+ }, 100);
358
+
359
+ // -----------------------------------------------
360
+ // onabort
361
+ // -----------------------------------------------
362
+ xhr.onabort = ()=>{
363
+ // stop the cloud upload progress tracker
364
+ clearInterval(cloud_progress_check_interval);
365
+ // remove progress handler
366
+ this.socket.off('upload.progress', progress_handler);
367
+ // if an 'abort' callback is provided, call it
368
+ if(options.abort && typeof options.abort === 'function')
369
+ options.abort(operation_id);
370
+ }
371
+
372
+ // -----------------------------------------------
373
+ // on success/error
374
+ // -----------------------------------------------
375
+ xhr.onreadystatechange = async (e)=>{
376
+ if (xhr.readyState === 4) {
377
+ const resp = await utils.parseResponse(xhr);
378
+ // Error
379
+ if((xhr.status >= 400 && xhr.status < 600) || (options.strict && xhr.status === 218)) {
380
+ // stop the cloud upload progress tracker
381
+ clearInterval(cloud_progress_check_interval);
382
+
383
+ // remove progress handler
384
+ this.socket.off('upload.progress', progress_handler);
385
+
386
+ // If this is a 'strict' upload (i.e. status code is 218), we need to find out which operation failed
387
+ // and call the error callback with that operation.
388
+ if(options.strict && xhr.status === 218){
389
+ // find the operation that failed
390
+ let failed_operation;
391
+ for(let i=0; i<resp.results?.length; i++){
392
+ if(resp.results[i].status !== 200){
393
+ failed_operation = resp.results[i];
394
+ break;
395
+ }
396
+ }
397
+ return error(failed_operation);
398
+ }
399
+
400
+ return error(resp);
401
+ }
402
+ // Success
403
+ else{
404
+ if(!resp || !resp.results || resp.results.length === 0){
405
+ // no results
406
+ if(puter.debugMode)
407
+ console.log('no results');
408
+ }
409
+
410
+ let items = resp.results;
411
+ items = items.length === 1 ? items[0] : items;
412
+
413
+ // if success callback is provided, call it
414
+ if(options.success && typeof options.success === 'function'){
415
+ options.success(items);
416
+ }
417
+ // stop the cloud upload progress tracker
418
+ clearInterval(cloud_progress_check_interval);
419
+ // remove progress handler
420
+ this.socket.off('upload.progress', progress_handler);
421
+
422
+ return resolve(items);
423
+ }
424
+ }
425
+ }
426
+
427
+ // Fire off the 'start' event
428
+ if(options.start && typeof options.start === 'function'){
429
+ options.start();
430
+ }
431
+
432
+ // todo: EXTREMELY NAIVE CACHE PURGE
433
+ puter._cache.flushall();
434
+
435
+ // send request
436
+ xhr.send(fd);
437
+ })
438
+ }
439
+
440
+ export default upload;
@@ -0,0 +1,65 @@
1
+ import path from "../../../lib/path.js"
2
+ import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
3
+
4
+ const write = async function (targetPath, data, options = {}) {
5
+ // targetPath is required
6
+ if(!targetPath){
7
+ throw new Error({ code: 'NO_TARGET_PATH', message: 'No target path provided.' });
8
+ }
9
+ // if targetPath is a File
10
+ if(targetPath instanceof File && data === undefined){
11
+ data = targetPath;
12
+ targetPath = data.name;
13
+ }
14
+
15
+ // strict mode will cause the upload to fail if even one operation fails
16
+ // for example, if one of the files in a folder fails to upload, the entire upload will fail
17
+ // since write is a wrapper around upload to handle single-file uploads, we need to pass the strict option to upload
18
+ options.strict = true;
19
+
20
+ // by default, we want to overwrite existing files
21
+ options.overwrite = options.overwrite ?? true;
22
+
23
+ // if overwrite is true and dedupeName is not provided, set dedupeName to false
24
+ if(options.overwrite && options.dedupeName === undefined)
25
+ options.dedupeName = false;
26
+
27
+ // if targetPath is not provided or it's not starting with a slash, it means it's a relative path
28
+ // in that case, we need to prepend the app's root directory to it
29
+ targetPath = getAbsolutePathForApp(targetPath);
30
+
31
+ // extract file name from targetPath
32
+ const filename = path.basename(targetPath);
33
+
34
+ // extract the parent directory from targetPath
35
+ const parent = path.dirname(targetPath);
36
+
37
+ // if data is a string, convert it to a File object
38
+ if(typeof data === 'string'){
39
+ data = new File([data ?? ''], filename ?? 'Untitled.txt', { type: "text/plain" });
40
+ }
41
+ // blob
42
+ else if(data instanceof Blob){
43
+ data = new File([data ?? ''], filename ?? 'Untitled', { type: data.type });
44
+ }
45
+ // typed arrays (Uint8Array, Int8Array, etc.) and ArrayBuffer
46
+ else if(data instanceof ArrayBuffer || ArrayBuffer.isView(data)){
47
+ data = new File([data], filename ?? 'Untitled', { type: "application/octet-stream" });
48
+ }
49
+
50
+ if(!data)
51
+ data = new File([data ?? ''], filename);
52
+
53
+ // data should be a File now. If it's not, it's an unsupported type
54
+ if (!(data instanceof File)) {
55
+ throw new Error({ code: 'field_invalid', message: 'write() data parameter is an invalid type' });
56
+ }
57
+
58
+ // todo: EXTREMELY NAIVE CACHE PURGE
59
+ puter._cache.flushall();
60
+
61
+ // perform upload
62
+ return this.upload(data, parent, options);
63
+ }
64
+
65
+ export default write;
@@ -0,0 +1,21 @@
1
+ import path from "../../../lib/path.js"
2
+
3
+ const getAbsolutePathForApp = (relativePath)=>{
4
+ // if we are in the gui environment, return the relative path as is
5
+ if(puter.env === 'gui')
6
+ return relativePath;
7
+
8
+ // if no relative path is provided, use the current working directory
9
+ if(!relativePath)
10
+ relativePath = '.';
11
+
12
+ // If relativePath is not provided, or it's not starting with a slash or tilde,
13
+ // it means it's a relative path. In that case, prepend the app's root directory.
14
+ if (!relativePath || (!relativePath.startsWith('/') && !relativePath.startsWith('~') && puter.appID)) {
15
+ relativePath = path.join('~/AppData', puter.appID, relativePath);
16
+ }
17
+
18
+ return relativePath;
19
+ }
20
+
21
+ export default getAbsolutePathForApp;
@@ -0,0 +1,138 @@
1
+ import * as utils from '../lib/utils.js';
2
+ import getAbsolutePathForApp from './FileSystem/utils/getAbsolutePathForApp.js';
3
+
4
+ class Hosting{
5
+ /**
6
+ * Creates a new instance with the given authentication token, API origin, and app ID,
7
+ *
8
+ * @class
9
+ * @param {string} authToken - Token used to authenticate the user.
10
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
11
+ * @param {string} appID - ID of the app to use.
12
+ */
13
+ constructor (context) {
14
+ this.authToken = context.authToken;
15
+ this.APIOrigin = context.APIOrigin;
16
+ this.appID = context.appID;
17
+ }
18
+
19
+ /**
20
+ * Sets a new authentication token.
21
+ *
22
+ * @param {string} authToken - The new authentication token.
23
+ * @memberof [Router]
24
+ * @returns {void}
25
+ */
26
+ setAuthToken (authToken) {
27
+ this.authToken = authToken;
28
+ }
29
+
30
+ /**
31
+ * Sets the API origin.
32
+ *
33
+ * @param {string} APIOrigin - The new API origin.
34
+ * @memberof [Apps]
35
+ * @returns {void}
36
+ */
37
+ setAPIOrigin (APIOrigin) {
38
+ this.APIOrigin = APIOrigin;
39
+ }
40
+
41
+ // todo document the `Subdomain` object.
42
+ list = async (...args) => {
43
+ return (await utils.make_driver_method([], 'puter-subdomains', undefined, 'select')(...args)).filter(e => !e.subdomain.startsWith("workers.puter."));
44
+ }
45
+
46
+ create = async (...args) => {
47
+ let options = {};
48
+ // * allows for: puter.hosting.new('example-subdomain') *
49
+ if (typeof args[0] === 'string' && args.length === 1) {
50
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
51
+ // and use it as the subdomain. This is to make development easier.
52
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
53
+ args[0] = args[0].split('.')[0];
54
+ }
55
+
56
+ options = { object: { subdomain: args[0] }};
57
+ }
58
+ // if there are two string arguments, assume they are the subdomain and the target directory
59
+ // * allows for: puter.hosting.new('example-subdomain', '/path/to/target') *
60
+ else if (Array.isArray(args) && args.length === 2 && typeof args[0] === 'string') {
61
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
62
+ // and use it as the subdomain. This is to make development easier.
63
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
64
+ args[0] = args[0].split('.')[0];
65
+ }
66
+
67
+ // if the target directory is not an absolute path, make it an absolute path relative to the app's root directory
68
+ if(args[1]){
69
+ args[1] = getAbsolutePathForApp(args[1]);
70
+ }
71
+
72
+ options = { object: { subdomain: args[0], root_dir: args[1] }};
73
+ }
74
+ // allows for: puter.hosting.new({ subdomain: 'subdomain' })
75
+ else if (typeof args[0] === 'object') {
76
+ options = { object: args[0] };
77
+ }
78
+ // Call the original chat.complete method
79
+ return await utils.make_driver_method(['object'], 'puter-subdomains', undefined, 'create').call(this, options);
80
+ }
81
+
82
+ update = async(...args) => {
83
+ let options = {};
84
+ // If there are two string arguments, assume they are the subdomain and the target directory
85
+ // * allows for: puter.hosting.update('example-subdomain', '/path/to/target') *
86
+ if (Array.isArray(args) && typeof args[0] === 'string') {
87
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
88
+ // and use it as the subdomain. This is to make development easier.
89
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
90
+ args[0] = args[0].split('.')[0];
91
+ }
92
+
93
+ // if the target directory is not an absolute path, make it an absolute path relative to the app's root directory
94
+ if(args[1]){
95
+ args[1] = getAbsolutePathForApp(args[1]);
96
+ }
97
+
98
+ options = { id: {subdomain: args[0]}, object: { root_dir: args[1] ?? null }};
99
+ }
100
+
101
+ // Call the original chat.complete method
102
+ return await utils.make_driver_method(['object'], 'puter-subdomains', undefined, 'update').call(this, options);
103
+ }
104
+
105
+ get = async(...args) => {
106
+ let options = {};
107
+ // if there is one string argument, assume it is the subdomain
108
+ // * allows for: puter.hosting.get('example-subdomain') *
109
+ if (Array.isArray(args) && typeof args[0] === 'string') {
110
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
111
+ // and use it as the subdomain. This is to make development easier.
112
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
113
+ args[0] = args[0].split('.')[0];
114
+ }
115
+
116
+ options = { id: {subdomain: args[0]}};
117
+ }
118
+ return utils.make_driver_method(['uid'], 'puter-subdomains', undefined, 'read').call(this, options);
119
+ }
120
+
121
+ delete = async(...args) => {
122
+ let options = {};
123
+ // if there is one string argument, assume it is the subdomain
124
+ // * allows for: puter.hosting.get('example-subdomain') *
125
+ if (Array.isArray(args) && typeof args[0] === 'string') {
126
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
127
+ // and use it as the subdomain. This is to make development easier.
128
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
129
+ args[0] = args[0].split('.')[0];
130
+ }
131
+
132
+ options = { id: {subdomain: args[0]}};
133
+ }
134
+ return utils.make_driver_method(['uid'], 'puter-subdomains', undefined, 'delete').call(this, options);
135
+ }
136
+ }
137
+
138
+ export default Hosting;