@heyputer/puter.js 2.0.13 → 2.0.15
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/dist/puter.cjs +4 -0
- package/index.d.ts +23 -5
- package/package.json +9 -6
- package/src/index.js +59 -16
- package/src/init.cjs +1 -1
- package/src/modules/FileSystem/index.js +137 -9
- package/src/modules/FileSystem/operations/copy.js +0 -3
- package/src/modules/FileSystem/operations/mkdir.js +1 -4
- package/src/modules/FileSystem/operations/move.js +2 -6
- package/src/modules/FileSystem/operations/readdir.js +100 -44
- package/src/modules/FileSystem/operations/rename.js +1 -2
- package/src/modules/FileSystem/operations/stat.js +1 -5
- package/src/modules/FileSystem/operations/upload.js +1 -4
- package/src/modules/FileSystem/operations/write.js +1 -4
- package/src/modules/KV.js +53 -26
- package/src/modules/UI.js +30 -2
- package/src/modules/networking/PTLS.js +4 -0
- package/src/services/XDIncoming.js +1 -1
- package/dist/puter.js +0 -4
package/index.d.ts
CHANGED
|
@@ -32,11 +32,19 @@ declare class Puter {
|
|
|
32
32
|
|
|
33
33
|
// AI Module
|
|
34
34
|
interface AI {
|
|
35
|
-
|
|
36
|
-
chat(prompt: string,
|
|
37
|
-
chat(prompt: string,
|
|
38
|
-
chat(prompt: string,
|
|
39
|
-
chat(
|
|
35
|
+
// Streaming overloads
|
|
36
|
+
chat(prompt: string, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
|
|
37
|
+
chat(prompt: string, testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
|
|
38
|
+
chat(prompt: string, imageURL: string, testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
|
|
39
|
+
chat(prompt: string, imageURLArray: string[], testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
|
|
40
|
+
chat(messages: ChatMessage[], testMode: boolean, options: StreamingChatOptions): AsyncIterable<ChatResponseChunk>;
|
|
41
|
+
|
|
42
|
+
// Non-streaming overloads
|
|
43
|
+
chat(prompt: string, options?: NonStreamingChatOptions): Promise<ChatResponse>;
|
|
44
|
+
chat(prompt: string, testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
|
|
45
|
+
chat(prompt: string, imageURL?: string, testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
|
|
46
|
+
chat(prompt: string, imageURLArray?: string[], testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
|
|
47
|
+
chat(messages: ChatMessage[], testMode?: boolean, options?: NonStreamingChatOptions): Promise<ChatResponse>;
|
|
40
48
|
|
|
41
49
|
img2txt(image: string | File | Blob, testMode?: boolean): Promise<string>;
|
|
42
50
|
|
|
@@ -50,6 +58,9 @@ interface AI {
|
|
|
50
58
|
txt2speech(text: string, language?: string, voice?: string, engine?: string): Promise<HTMLAudioElement>;
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
type StreamingChatOptions = Omit<ChatOptions, "stream"> & { stream: true };
|
|
62
|
+
type NonStreamingChatOptions = Omit<ChatOptions, "stream"> & { stream?: false | undefined };
|
|
63
|
+
|
|
53
64
|
interface ChatOptions {
|
|
54
65
|
model?: string;
|
|
55
66
|
stream?: boolean;
|
|
@@ -109,6 +120,11 @@ interface Txt2SpeechOptions {
|
|
|
109
120
|
engine?: 'standard' | 'neural' | 'generative';
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
interface ChatResponseChunk {
|
|
124
|
+
text?: string;
|
|
125
|
+
[key: string]: any;
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
// Apps Module
|
|
113
129
|
interface Apps {
|
|
114
130
|
create(name: string, indexURL: string): Promise<App>;
|
|
@@ -281,7 +297,9 @@ interface KV {
|
|
|
281
297
|
set(key: string, value: string | number | boolean | object | any[]): Promise<boolean>;
|
|
282
298
|
get(key: string): Promise<any>;
|
|
283
299
|
del(key: string): Promise<boolean>;
|
|
300
|
+
incr(key: string, pathAndAmount: { [key: string]: number }): Promise<number>;
|
|
284
301
|
incr(key: string, amount?: number): Promise<number>;
|
|
302
|
+
decr(key: string, pathAndAmount: { [key: string]: number }): Promise<number>;
|
|
285
303
|
decr(key: string, amount?: number): Promise<number>;
|
|
286
304
|
list(pattern?: string, returnValues?: boolean): Promise<string[] | KVPair[]>;
|
|
287
305
|
list(returnValues?: boolean): Promise<string[] | KVPair[]>;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyputer/puter.js",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.15",
|
|
4
4
|
"description": "Puter.js - A JavaScript library for interacting with Puter services.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"typings": "index.d.ts",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"files": [
|
|
10
|
-
"dist/puter.
|
|
10
|
+
"dist/puter.cjs",
|
|
11
11
|
"src/",
|
|
12
12
|
"index.d.ts"
|
|
13
13
|
],
|
|
@@ -25,18 +25,21 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"start-server": "npx http-server --cors -c-1",
|
|
28
|
-
"start-webpack": "webpack && webpack --output-filename puter.dev.js --watch --devtool source-map",
|
|
28
|
+
"start-webpack": "webpack --stats=errors-only && webpack --output-filename puter.dev.js --watch --devtool source-map --stats=errors-only",
|
|
29
29
|
"start": "concurrently \"npm run start-server\" \"npm run start-webpack\"",
|
|
30
|
-
"
|
|
30
|
+
"test": "npx http-server --cors -c-1 -o /test",
|
|
31
|
+
"build": "webpack && { echo \"// Copyright 2024-present Puter Technologies Inc. All rights reserved.\"; echo \"// Generated on $(date '+%Y-%m-%d %H:%M')\n\"; cat ./dist/puter.js; } > temp && mv temp ./dist/puter.js",
|
|
32
|
+
"prepublishOnly": "npm run build && mv dist/puter.js dist/puter.cjs && npm version patch"
|
|
31
33
|
},
|
|
32
34
|
"author": "Puter Technologies Inc.",
|
|
33
35
|
"license": "Apache-2.0",
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"concurrently": "^8.2.2",
|
|
38
|
+
"http-server": "^14.1.1",
|
|
36
39
|
"webpack-cli": "^5.1.4"
|
|
37
40
|
},
|
|
38
41
|
"dependencies": {
|
|
39
|
-
"@heyputer/kv.js": "^0.1
|
|
42
|
+
"@heyputer/kv.js": "^0.2.1",
|
|
40
43
|
"@heyputer/putility": "^1.0.3"
|
|
41
44
|
}
|
|
42
|
-
}
|
|
45
|
+
}
|
package/src/index.js
CHANGED
|
@@ -130,7 +130,8 @@ const puterInit = (function() {
|
|
|
130
130
|
constructor() {
|
|
131
131
|
|
|
132
132
|
// Initialize the cache using kv.js
|
|
133
|
-
this._cache = new kvjs();
|
|
133
|
+
this._cache = new kvjs({dbName: 'puter_cache'});
|
|
134
|
+
this._opscache = new kvjs();
|
|
134
135
|
|
|
135
136
|
// "modules" in puter.js are external interfaces for the developer
|
|
136
137
|
this.modules_ = [];
|
|
@@ -493,10 +494,21 @@ const puterInit = (function() {
|
|
|
493
494
|
console.error('Error accessing localStorage:', error);
|
|
494
495
|
}
|
|
495
496
|
}
|
|
497
|
+
// initialize loop for updating caches for major directories
|
|
498
|
+
if(this.env === 'gui'){
|
|
499
|
+
// check and update gui fs cache regularly
|
|
500
|
+
setInterval(puter.checkAndUpdateGUIFScache, 10000);
|
|
501
|
+
}
|
|
496
502
|
// reinitialize submodules
|
|
497
503
|
this.updateSubmodules();
|
|
504
|
+
|
|
498
505
|
// rao
|
|
499
506
|
this.request_rao_();
|
|
507
|
+
|
|
508
|
+
// perform whoami and cache results
|
|
509
|
+
puter.getUser().then((user) => {
|
|
510
|
+
this.whoami = user;
|
|
511
|
+
});
|
|
500
512
|
};
|
|
501
513
|
|
|
502
514
|
setAPIOrigin = function(APIOrigin) {
|
|
@@ -666,7 +678,12 @@ const puterInit = (function() {
|
|
|
666
678
|
// If we went from online to offline, purge the cache
|
|
667
679
|
if ( wasOnline && !isOnline ) {
|
|
668
680
|
console.log('Network connection lost - purging cache');
|
|
669
|
-
|
|
681
|
+
try {
|
|
682
|
+
this._cache.flushall();
|
|
683
|
+
console.log('Cache purged successfully');
|
|
684
|
+
} catch( error ) {
|
|
685
|
+
console.error('Error purging cache:', error);
|
|
686
|
+
}
|
|
670
687
|
}
|
|
671
688
|
|
|
672
689
|
// Update the previous state
|
|
@@ -688,22 +705,48 @@ const puterInit = (function() {
|
|
|
688
705
|
};
|
|
689
706
|
|
|
690
707
|
/**
|
|
691
|
-
*
|
|
692
|
-
* @
|
|
708
|
+
* Checks and updates the GUI FS cache for most-commonly used paths
|
|
709
|
+
* @private
|
|
693
710
|
*/
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
711
|
+
checkAndUpdateGUIFScache = function(){
|
|
712
|
+
// only run in gui environment
|
|
713
|
+
if(puter.env !== 'gui') return;
|
|
714
|
+
// only run if user is authenticated
|
|
715
|
+
if(!puter.whoami) return;
|
|
716
|
+
|
|
717
|
+
let username = puter.whoami.username;
|
|
718
|
+
|
|
719
|
+
// common paths
|
|
720
|
+
let home_path = `/${username}`;
|
|
721
|
+
let desktop_path = `/${username}/Desktop`;
|
|
722
|
+
let documents_path = `/${username}/Documents`;
|
|
723
|
+
let public_path = `/${username}/Public`;
|
|
724
|
+
|
|
725
|
+
// Home
|
|
726
|
+
if(!puter._cache.get('readdir:' + home_path)){
|
|
727
|
+
console.log(`/${username} is not cached, refetching cache`);
|
|
728
|
+
// fetch home
|
|
729
|
+
puter.fs.readdir(home_path);
|
|
730
|
+
}
|
|
731
|
+
// Desktop
|
|
732
|
+
if(!puter._cache.get('readdir:' + desktop_path)){
|
|
733
|
+
console.log(`/${username}/Desktop is not cached, refetching cache`);
|
|
734
|
+
// fetch desktop
|
|
735
|
+
puter.fs.readdir(desktop_path);
|
|
736
|
+
}
|
|
737
|
+
// Documents
|
|
738
|
+
if(!puter._cache.get('readdir:' + documents_path)){
|
|
739
|
+
console.log(`/${username}/Documents is not cached, refetching cache`);
|
|
740
|
+
// fetch documents
|
|
741
|
+
puter.fs.readdir(documents_path);
|
|
742
|
+
}
|
|
743
|
+
// Public
|
|
744
|
+
if(!puter._cache.get('readdir:' + public_path)){
|
|
745
|
+
console.log(`/${username}/Public is not cached, refetching cache`);
|
|
746
|
+
// fetch public
|
|
747
|
+
puter.fs.readdir(public_path);
|
|
704
748
|
}
|
|
705
|
-
}
|
|
706
|
-
|
|
749
|
+
}
|
|
707
750
|
}
|
|
708
751
|
|
|
709
752
|
// Create a new Puter object and return it
|
package/src/init.cjs
CHANGED
|
@@ -19,7 +19,7 @@ const init = (authToken) => {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
goodContext.globalThis = goodContext;
|
|
22
|
-
const code = readFileSync(`${resolve(__filename, '..')}/../dist/puter.
|
|
22
|
+
const code = readFileSync(`${resolve(__filename, '..')}/../dist/puter.cjs`, 'utf8');
|
|
23
23
|
const context = vm.createContext(goodContext);
|
|
24
24
|
vm.runInNewContext(code, context);
|
|
25
25
|
if ( authToken ) {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import io from '../../lib/socket.io/socket.io.esm.min.js';
|
|
2
|
+
import * as utils from '../../lib/utils.js';
|
|
3
|
+
import path from '../../lib/path.js';
|
|
4
|
+
|
|
5
|
+
// Constants
|
|
6
|
+
//
|
|
7
|
+
// The last valid time of the local cache.
|
|
8
|
+
const LAST_VALID_TS = 'last_valid_ts';
|
|
2
9
|
|
|
3
10
|
// Operations
|
|
4
11
|
import copy from './operations/copy.js';
|
|
@@ -20,6 +27,7 @@ import FSItem from '../FSItem.js';
|
|
|
20
27
|
import deleteFSEntry from './operations/deleteFSEntry.js';
|
|
21
28
|
import getReadURL from './operations/getReadUrl.js';
|
|
22
29
|
|
|
30
|
+
|
|
23
31
|
export class PuterJSFileSystemModule extends AdvancedBase {
|
|
24
32
|
|
|
25
33
|
space = space;
|
|
@@ -67,6 +75,7 @@ export class PuterJSFileSystemModule extends AdvancedBase {
|
|
|
67
75
|
this.APIOrigin = context.APIOrigin;
|
|
68
76
|
this.appID = context.appID;
|
|
69
77
|
this.context = context;
|
|
78
|
+
this.cacheUpdateTimer = null;
|
|
70
79
|
// Connect socket.
|
|
71
80
|
this.initializeSocket();
|
|
72
81
|
|
|
@@ -103,21 +112,40 @@ export class PuterJSFileSystemModule extends AdvancedBase {
|
|
|
103
112
|
});
|
|
104
113
|
|
|
105
114
|
this.bindSocketEvents();
|
|
106
|
-
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
bindSocketEvents() {
|
|
118
|
+
// this.socket.on('cache.updated', (msg) => {
|
|
119
|
+
// // check original_client_socket_id and if it matches this.socket.id, don't post update
|
|
120
|
+
// if (msg.original_client_socket_id !== this.socket.id) {
|
|
121
|
+
// this.invalidateCache();
|
|
122
|
+
// }
|
|
123
|
+
// });
|
|
124
|
+
|
|
125
|
+
this.socket.on('item.renamed', (item) => {
|
|
126
|
+
puter._cache.flushall();
|
|
127
|
+
console.log('Flushed cache for item.renamed');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.socket.on('item.removed', (item) => {
|
|
131
|
+
// check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
|
|
132
|
+
puter._cache.flushall();
|
|
133
|
+
console.log('Flushed cache for item.removed');
|
|
134
|
+
});
|
|
135
|
+
|
|
110
136
|
this.socket.on('item.added', (item) => {
|
|
111
|
-
// todo: NAIVE PURGE
|
|
112
137
|
puter._cache.flushall();
|
|
138
|
+
console.log('Flushed cache for item.added');
|
|
113
139
|
});
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
|
|
141
|
+
this.socket.on('item.updated', (item) => {
|
|
116
142
|
puter._cache.flushall();
|
|
143
|
+
console.log('Flushed cache for item.updated');
|
|
117
144
|
});
|
|
145
|
+
|
|
118
146
|
this.socket.on('item.moved', (item) => {
|
|
119
|
-
// todo: NAIVE PURGE
|
|
120
147
|
puter._cache.flushall();
|
|
148
|
+
console.log('Flushed cache for item.moved');
|
|
121
149
|
});
|
|
122
150
|
|
|
123
151
|
this.socket.on('connect', () => {
|
|
@@ -132,10 +160,6 @@ export class PuterJSFileSystemModule extends AdvancedBase {
|
|
|
132
160
|
{
|
|
133
161
|
console.log('FileSystem Socket: Disconnected');
|
|
134
162
|
}
|
|
135
|
-
|
|
136
|
-
// todo: NAIVE PURGE
|
|
137
|
-
// purge cache on disconnect since we may have become out of sync
|
|
138
|
-
puter._cache.flushall();
|
|
139
163
|
});
|
|
140
164
|
|
|
141
165
|
this.socket.on('reconnect', (attempt) => {
|
|
@@ -183,6 +207,14 @@ export class PuterJSFileSystemModule extends AdvancedBase {
|
|
|
183
207
|
*/
|
|
184
208
|
setAuthToken(authToken) {
|
|
185
209
|
this.authToken = authToken;
|
|
210
|
+
|
|
211
|
+
// Check cache timestamp and purge if needed (only in GUI environment)
|
|
212
|
+
if (this.context.env === 'gui') {
|
|
213
|
+
this.checkCacheAndPurge();
|
|
214
|
+
// Start background task to update LAST_VALID_TS every 1 second
|
|
215
|
+
this.startCacheUpdateTimer();
|
|
216
|
+
}
|
|
217
|
+
|
|
186
218
|
// reset socket
|
|
187
219
|
this.initializeSocket();
|
|
188
220
|
}
|
|
@@ -199,4 +231,100 @@ export class PuterJSFileSystemModule extends AdvancedBase {
|
|
|
199
231
|
// reset socket
|
|
200
232
|
this.initializeSocket();
|
|
201
233
|
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The cache-related actions after local and remote updates.
|
|
237
|
+
*
|
|
238
|
+
* @memberof PuterJSFileSystemModule
|
|
239
|
+
* @returns {void}
|
|
240
|
+
*/
|
|
241
|
+
invalidateCache() {
|
|
242
|
+
// Action: Update last valid time
|
|
243
|
+
// Set to 0, which means the cache is not up to date.
|
|
244
|
+
localStorage.setItem(LAST_VALID_TS, '0');
|
|
245
|
+
puter._cache.flushall();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Calls the cache API to get the last change timestamp from the server.
|
|
250
|
+
*
|
|
251
|
+
* @memberof PuterJSFileSystemModule
|
|
252
|
+
* @returns {Promise<number>} The timestamp from the server
|
|
253
|
+
*/
|
|
254
|
+
async getCacheTimestamp() {
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
const xhr = utils.initXhr('/cache/last-change-timestamp', this.APIOrigin, this.authToken, 'get', 'application/json');
|
|
257
|
+
|
|
258
|
+
// set up event handlers for load and error events
|
|
259
|
+
utils.setupXhrEventHandlers(xhr, undefined, undefined, async (result) => {
|
|
260
|
+
try {
|
|
261
|
+
const response = typeof result === 'string' ? JSON.parse(result) : result;
|
|
262
|
+
resolve(response.timestamp || Date.now());
|
|
263
|
+
} catch (e) {
|
|
264
|
+
reject(new Error('Failed to parse response'));
|
|
265
|
+
}
|
|
266
|
+
}, reject);
|
|
267
|
+
|
|
268
|
+
xhr.send();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Checks cache timestamp and purges cache if needed.
|
|
274
|
+
* Only runs in GUI environment.
|
|
275
|
+
*
|
|
276
|
+
* @memberof PuterJSFileSystemModule
|
|
277
|
+
* @returns {void}
|
|
278
|
+
*/
|
|
279
|
+
async checkCacheAndPurge() {
|
|
280
|
+
try {
|
|
281
|
+
const serverTimestamp = await this.getCacheTimestamp();
|
|
282
|
+
const localValidTs = parseInt(localStorage.getItem(LAST_VALID_TS)) || 0;
|
|
283
|
+
|
|
284
|
+
if (serverTimestamp - localValidTs > 2000) {
|
|
285
|
+
console.log('Cache is not up to date, purging cache');
|
|
286
|
+
// Server has newer data, purge local cache
|
|
287
|
+
puter._cache.flushall();
|
|
288
|
+
localStorage.setItem(LAST_VALID_TS, '0');
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
// If we can't get the server timestamp, silently fail
|
|
292
|
+
// This ensures the socket initialization doesn't break
|
|
293
|
+
console.error('Error checking cache timestamp:', error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Starts the background task to update LAST_VALID_TS every 1 second.
|
|
299
|
+
* Only runs in GUI environment.
|
|
300
|
+
*
|
|
301
|
+
* @memberof PuterJSFileSystemModule
|
|
302
|
+
* @returns {void}
|
|
303
|
+
*/
|
|
304
|
+
startCacheUpdateTimer() {
|
|
305
|
+
if (this.context.env !== 'gui') {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Clear any existing timer
|
|
310
|
+
// this.stopCacheUpdateTimer();
|
|
311
|
+
|
|
312
|
+
// Start new timer
|
|
313
|
+
this.cacheUpdateTimer = setInterval(() => {
|
|
314
|
+
localStorage.setItem(LAST_VALID_TS, Date.now().toString());
|
|
315
|
+
}, 1000);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Stops the background cache update timer.
|
|
320
|
+
*
|
|
321
|
+
* @memberof PuterJSFileSystemModule
|
|
322
|
+
* @returns {void}
|
|
323
|
+
*/
|
|
324
|
+
stopCacheUpdateTimer() {
|
|
325
|
+
if (this.cacheUpdateTimer) {
|
|
326
|
+
clearInterval(this.cacheUpdateTimer);
|
|
327
|
+
this.cacheUpdateTimer = null;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
202
330
|
}
|
|
@@ -55,9 +55,6 @@ const copy = function (...args) {
|
|
|
55
55
|
// if user is copying an item to where its source is, change the name so there is no conflict
|
|
56
56
|
dedupe_name: (options.dedupe_name || options.dedupeName),
|
|
57
57
|
}));
|
|
58
|
-
|
|
59
|
-
// todo: EXTREMELY NAIVE CACHE PURGE
|
|
60
|
-
puter._cache.flushall();
|
|
61
58
|
})
|
|
62
59
|
}
|
|
63
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import path from "../../../lib/path.js";
|
|
1
2
|
import * as utils from '../../../lib/utils.js';
|
|
2
3
|
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
|
3
|
-
import path from "../../../lib/path.js"
|
|
4
4
|
|
|
5
5
|
const mkdir = function (...args) {
|
|
6
6
|
let options = {};
|
|
@@ -53,9 +53,6 @@ const mkdir = function (...args) {
|
|
|
53
53
|
original_client_socket_id: this.socket.id,
|
|
54
54
|
create_missing_parents: (options.recursive || options.createMissingParents) ?? false,
|
|
55
55
|
}));
|
|
56
|
-
|
|
57
|
-
// todo: EXTREMELY NAIVE CACHE PURGE
|
|
58
|
-
puter._cache.flushall();
|
|
59
56
|
})
|
|
60
57
|
}
|
|
61
58
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import path from "../../../lib/path.js";
|
|
1
2
|
import * as utils from '../../../lib/utils.js';
|
|
2
3
|
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
|
3
|
-
import stat from "./stat.js"
|
|
4
|
-
import path from "../../../lib/path.js"
|
|
4
|
+
import stat from "./stat.js";
|
|
5
5
|
|
|
6
6
|
const move = function (...args) {
|
|
7
7
|
let options;
|
|
@@ -65,10 +65,6 @@ const move = function (...args) {
|
|
|
65
65
|
new_metadata: (options.new_metadata || options.newMetadata),
|
|
66
66
|
original_client_socket_id: options.excludeSocketID,
|
|
67
67
|
}));
|
|
68
|
-
|
|
69
|
-
// todo: EXTREMELY NAIVE CACHE PURGE
|
|
70
|
-
puter._cache.flushall();
|
|
71
|
-
|
|
72
68
|
})
|
|
73
69
|
}
|
|
74
70
|
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import * as utils from '../../../lib/utils.js';
|
|
2
2
|
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
|
|
3
3
|
|
|
4
|
+
// Track in-flight requests to avoid duplicate backend calls
|
|
5
|
+
// Each entry stores: { promise, timestamp }
|
|
6
|
+
const inflightRequests = new Map();
|
|
7
|
+
|
|
8
|
+
// Time window (in ms) to group duplicate requests together
|
|
9
|
+
// Requests made within this window will share the same backend call
|
|
10
|
+
const DEDUPLICATION_WINDOW_MS = 2000; // 2 seconds
|
|
11
|
+
|
|
4
12
|
const readdir = async function (...args) {
|
|
5
13
|
let options;
|
|
6
14
|
|
|
@@ -31,8 +39,6 @@ const readdir = async function (...args) {
|
|
|
31
39
|
let cacheKey;
|
|
32
40
|
if(options.path){
|
|
33
41
|
cacheKey = 'readdir:' + options.path;
|
|
34
|
-
}else if(options.uid){
|
|
35
|
-
cacheKey = 'readdir:' + options.uid;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
if(options.consistency === 'eventual'){
|
|
@@ -44,58 +50,108 @@ const readdir = async function (...args) {
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// Generate deduplication key based on all request parameters
|
|
54
|
+
const deduplicationKey = JSON.stringify({
|
|
55
|
+
path: options.path,
|
|
56
|
+
uid: options.uid,
|
|
57
|
+
no_thumbs: options.no_thumbs,
|
|
58
|
+
no_assocs: options.no_assocs,
|
|
59
|
+
consistency: options.consistency,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Check if there's already an in-flight request for the same parameters
|
|
63
|
+
const existingEntry = inflightRequests.get(deduplicationKey);
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
|
|
66
|
+
if (existingEntry) {
|
|
67
|
+
const timeSinceRequest = now - existingEntry.timestamp;
|
|
68
|
+
|
|
69
|
+
// Only reuse the request if it's within the deduplication window
|
|
70
|
+
if (timeSinceRequest < DEDUPLICATION_WINDOW_MS) {
|
|
71
|
+
// Wait for the existing request and return its result
|
|
72
|
+
try {
|
|
73
|
+
const result = await existingEntry.promise;
|
|
74
|
+
resolve(result);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
reject(error);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
} else {
|
|
80
|
+
// Request is too old, remove it from the tracker
|
|
81
|
+
inflightRequests.delete(deduplicationKey);
|
|
55
82
|
}
|
|
56
83
|
}
|
|
57
84
|
|
|
58
|
-
//
|
|
59
|
-
const
|
|
85
|
+
// Create a promise for this request and store it to deduplicate concurrent calls
|
|
86
|
+
const requestPromise = new Promise(async (resolveRequest, rejectRequest) => {
|
|
87
|
+
// If auth token is not provided and we are in the web environment,
|
|
88
|
+
// try to authenticate with Puter
|
|
89
|
+
if(!puter.authToken && puter.env === 'web'){
|
|
90
|
+
try{
|
|
91
|
+
await puter.ui.authenticateWithPuter();
|
|
92
|
+
}catch(e){
|
|
93
|
+
// if authentication fails, throw an error
|
|
94
|
+
rejectRequest('Authentication failed.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
60
98
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// Calculate the size of the result for cache eligibility check
|
|
64
|
-
const resultSize = JSON.stringify(result).length;
|
|
65
|
-
|
|
66
|
-
// Cache the result if it's not bigger than MAX_CACHE_SIZE
|
|
67
|
-
const MAX_CACHE_SIZE = 20 * 1024 * 1024;
|
|
68
|
-
const EXPIRE_TIME = 60 * 60; // 1 hour
|
|
99
|
+
// create xhr object
|
|
100
|
+
const xhr = utils.initXhr('/readdir', this.APIOrigin, undefined, "post", "text/plain;actually=json");
|
|
69
101
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
// set up event handlers for load and error events
|
|
103
|
+
utils.setupXhrEventHandlers(xhr, options.success, options.error, async (result) => {
|
|
104
|
+
// Calculate the size of the result for cache eligibility check
|
|
105
|
+
const resultSize = JSON.stringify(result).length;
|
|
106
|
+
|
|
107
|
+
// Cache the result if it's not bigger than MAX_CACHE_SIZE
|
|
108
|
+
const MAX_CACHE_SIZE = 100 * 1024 * 1024;
|
|
109
|
+
|
|
110
|
+
if(resultSize <= MAX_CACHE_SIZE){
|
|
111
|
+
// UPSERT the cache
|
|
112
|
+
puter._cache.set(cacheKey, result);
|
|
113
|
+
}
|
|
74
114
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
115
|
+
// set each individual item's cache
|
|
116
|
+
for(const item of result){
|
|
117
|
+
puter._cache.set('item:' + item.path, item);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
resolveRequest(result);
|
|
121
|
+
}, rejectRequest);
|
|
122
|
+
|
|
123
|
+
// Build request payload - support both path and uid parameters
|
|
124
|
+
const payload = {
|
|
125
|
+
no_thumbs: options.no_thumbs,
|
|
126
|
+
no_assocs: options.no_assocs,
|
|
127
|
+
auth_token: this.authToken
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Add either uid or path to the payload
|
|
131
|
+
if (options.uid) {
|
|
132
|
+
payload.uid = options.uid;
|
|
133
|
+
} else if (options.path) {
|
|
134
|
+
payload.path = getAbsolutePathForApp(options.path);
|
|
79
135
|
}
|
|
80
|
-
|
|
81
|
-
resolve(result);
|
|
82
|
-
}, reject);
|
|
83
136
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
no_thumbs: options.no_thumbs,
|
|
87
|
-
no_assocs: options.no_assocs,
|
|
88
|
-
auth_token: this.authToken
|
|
89
|
-
};
|
|
137
|
+
xhr.send(JSON.stringify(payload));
|
|
138
|
+
});
|
|
90
139
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
140
|
+
// Store the promise and timestamp in the in-flight tracker
|
|
141
|
+
inflightRequests.set(deduplicationKey, {
|
|
142
|
+
promise: requestPromise,
|
|
143
|
+
timestamp: now,
|
|
144
|
+
});
|
|
97
145
|
|
|
98
|
-
|
|
146
|
+
// Wait for the request to complete and clean up
|
|
147
|
+
try {
|
|
148
|
+
const result = await requestPromise;
|
|
149
|
+
inflightRequests.delete(deduplicationKey);
|
|
150
|
+
resolve(result);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
inflightRequests.delete(deduplicationKey);
|
|
153
|
+
reject(error);
|
|
154
|
+
}
|
|
99
155
|
})
|
|
100
156
|
}
|
|
101
157
|
|