@heyputer/puter.js 1.0.1 → 2.0.1
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.
- package/README.md +14 -43
- package/index.d.ts +479 -0
- package/package.json +13 -3
- package/APACHE_LICENSE.txt +0 -201
- package/doc/devlog.md +0 -49
- package/src/bg.png +0 -0
- package/src/bg.webp +0 -0
- package/src/lib/APICallLogger.js +0 -110
- package/src/lib/EventListener.js +0 -51
- package/src/lib/RequestError.js +0 -6
- package/src/lib/filesystem/APIFS.js +0 -73
- package/src/lib/filesystem/CacheFS.js +0 -243
- package/src/lib/filesystem/PostMessageFS.js +0 -40
- package/src/lib/filesystem/definitions.js +0 -39
- package/src/lib/path.js +0 -509
- package/src/lib/polyfills/localStorage.js +0 -92
- package/src/lib/polyfills/xhrshim.js +0 -233
- package/src/lib/socket.io/socket.io.esm.min.js +0 -7
- package/src/lib/socket.io/socket.io.esm.min.js.map +0 -1
- package/src/lib/socket.io/socket.io.js +0 -4385
- package/src/lib/socket.io/socket.io.js.map +0 -1
- package/src/lib/socket.io/socket.io.min.js +0 -7
- package/src/lib/socket.io/socket.io.min.js.map +0 -1
- package/src/lib/socket.io/socket.io.msgpack.min.js +0 -7
- package/src/lib/socket.io/socket.io.msgpack.min.js.map +0 -1
- package/src/lib/utils.js +0 -620
- package/src/lib/xdrpc.js +0 -104
- package/src/modules/AI.js +0 -680
- package/src/modules/Apps.js +0 -215
- package/src/modules/Auth.js +0 -171
- package/src/modules/Debug.js +0 -39
- package/src/modules/Drivers.js +0 -278
- package/src/modules/FSItem.js +0 -139
- package/src/modules/FileSystem/index.js +0 -187
- package/src/modules/FileSystem/operations/copy.js +0 -64
- package/src/modules/FileSystem/operations/deleteFSEntry.js +0 -59
- package/src/modules/FileSystem/operations/getReadUrl.js +0 -42
- package/src/modules/FileSystem/operations/mkdir.js +0 -62
- package/src/modules/FileSystem/operations/move.js +0 -75
- package/src/modules/FileSystem/operations/read.js +0 -46
- package/src/modules/FileSystem/operations/readdir.js +0 -102
- package/src/modules/FileSystem/operations/rename.js +0 -58
- package/src/modules/FileSystem/operations/sign.js +0 -103
- package/src/modules/FileSystem/operations/space.js +0 -40
- package/src/modules/FileSystem/operations/stat.js +0 -95
- package/src/modules/FileSystem/operations/symlink.js +0 -55
- package/src/modules/FileSystem/operations/upload.js +0 -440
- package/src/modules/FileSystem/operations/write.js +0 -65
- package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +0 -21
- package/src/modules/Hosting.js +0 -138
- package/src/modules/KV.js +0 -301
- package/src/modules/OS.js +0 -95
- package/src/modules/Perms.js +0 -109
- package/src/modules/PuterDialog.js +0 -481
- package/src/modules/Threads.js +0 -75
- package/src/modules/UI.js +0 -1555
- package/src/modules/Util.js +0 -38
- package/src/modules/Workers.js +0 -120
- package/src/modules/networking/PSocket.js +0 -87
- package/src/modules/networking/PTLS.js +0 -100
- package/src/modules/networking/PWispHandler.js +0 -89
- package/src/modules/networking/parsers.js +0 -157
- package/src/modules/networking/requests.js +0 -282
- package/src/services/APIAccess.js +0 -46
- package/src/services/FSRelay.js +0 -20
- package/src/services/Filesystem.js +0 -122
- package/src/services/NoPuterYet.js +0 -20
- package/src/services/XDIncoming.js +0 -44
- package/test/ai.test.js +0 -214
- package/test/fs.test.js +0 -798
- package/test/index.html +0 -1183
- package/test/kv.test.js +0 -548
- package/test/txt2speech.test.js +0 -178
- package/webpack.config.js +0 -25
|
@@ -1,440 +0,0 @@
|
|
|
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;
|
|
@@ -1,65 +0,0 @@
|
|
|
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;
|
|
@@ -1,21 +0,0 @@
|
|
|
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;
|
package/src/modules/Hosting.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
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;
|