@heyputer/puter.js 2.0.14 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyputer/puter.js",
3
- "version": "2.0.14",
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",
@@ -25,15 +25,17 @@
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
+ "test": "npx http-server --cors -c-1 -o /test",
30
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",
31
- "prepublishOnly": "npm run build && mv dist/puter.js dist/puter.cjs && npm version patch && git add package.json && git commit -m \"chore: bump puter-js version and publish \" && git push"
32
+ "prepublishOnly": "npm run build && mv dist/puter.js dist/puter.cjs && npm version patch"
32
33
  },
33
34
  "author": "Puter Technologies Inc.",
34
35
  "license": "Apache-2.0",
35
36
  "devDependencies": {
36
37
  "concurrently": "^8.2.2",
38
+ "http-server": "^14.1.1",
37
39
  "webpack-cli": "^5.1.4"
38
40
  },
39
41
  "dependencies": {
package/src/index.js CHANGED
@@ -131,6 +131,7 @@ const puterInit = (function() {
131
131
 
132
132
  // Initialize the cache using kv.js
133
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
- this.purgeCache();
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,18 +705,48 @@ const puterInit = (function() {
688
705
  };
689
706
 
690
707
  /**
691
- * Purges all cached data
692
- * @public
708
+ * Checks and updates the GUI FS cache for most-commonly used paths
709
+ * @private
693
710
  */
694
- purgeCache = function() {
695
- try {
696
- this._cache.flushall();
697
- console.log('Cache purged successfully');
698
- } catch( error ) {
699
- console.error('Error purging cache:', error);
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);
700
748
  }
701
- };
702
-
749
+ }
703
750
  }
704
751
 
705
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.js`, 'utf8');
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 ) {
@@ -123,62 +123,27 @@ export class PuterJSFileSystemModule extends AdvancedBase {
123
123
  // });
124
124
 
125
125
  this.socket.on('item.renamed', (item) => {
126
- // delete old item from cache
127
- puter._cache.del('item:' + item.old_path);
128
- // if a directory
129
- if(item.is_dir){
130
- // delete readdir
131
- puter._cache.del('readdir:' + item.old_path);
132
- // descendants items
133
- const descendants = puter._cache.keys('item:' + item.old_path + '/*');
134
- for(const descendant of descendants){
135
- console.log('Deleting cache for:', descendant);
136
- puter._cache.del(descendant);
137
- }
138
- // descendants readdirs
139
- const descendants_readdir = puter._cache.keys('readdir:' + item.old_path + '/*');
140
- for(const descendant of descendants_readdir){
141
- console.log('Deleting cache for:', descendant);
142
- puter._cache.del(descendant);
143
- }
144
- }
145
- // parent readdir
146
- puter._cache.del('readdir:' + path.dirname(item.old_path));
126
+ puter._cache.flushall();
127
+ console.log('Flushed cache for item.renamed');
147
128
  });
148
129
 
149
130
  this.socket.on('item.removed', (item) => {
150
131
  // check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
151
132
  puter._cache.flushall();
152
- console.log('Flushed cache for item.deleted');
133
+ console.log('Flushed cache for item.removed');
153
134
  });
154
135
 
155
136
  this.socket.on('item.added', (item) => {
156
- // delete item from cache
157
- puter._cache.del('item:' + item.path);
158
- // delete readdir from cache
159
- puter._cache.del('readdir:' + item.path);
160
- // delete descendant items from cache
161
- const descendant_items = puter._cache.keys('item:' + item.path + '/*');
162
- for(const descendant of descendant_items){
163
- puter._cache.del(descendant);
164
- }
165
- // delete descendant readdirs from cache
166
- const descendant_readdirs = puter._cache.keys('readdir:' + item.path + '/*');
167
- for(const descendant of descendant_readdirs){
168
- puter._cache.del(descendant);
169
- }
170
- // delete parent readdir from cache
171
- puter._cache.del('readdir:' + path.dirname(item.path));
137
+ puter._cache.flushall();
138
+ console.log('Flushed cache for item.added');
172
139
  });
173
140
 
174
141
  this.socket.on('item.updated', (item) => {
175
- // check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
176
142
  puter._cache.flushall();
177
143
  console.log('Flushed cache for item.updated');
178
144
  });
179
145
 
180
146
  this.socket.on('item.moved', (item) => {
181
- // check original_client_socket_id and if it matches this.socket.id, don't invalidate cache
182
147
  puter._cache.flushall();
183
148
  console.log('Flushed cache for item.moved');
184
149
  });
@@ -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
 
@@ -42,56 +50,108 @@ const readdir = async function (...args) {
42
50
  }
43
51
  }
44
52
 
45
- // If auth token is not provided and we are in the web environment,
46
- // try to authenticate with Puter
47
- if(!puter.authToken && puter.env === 'web'){
48
- try{
49
- await puter.ui.authenticateWithPuter();
50
- }catch(e){
51
- // if authentication fails, throw an error
52
- reject('Authentication failed.');
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);
53
82
  }
54
83
  }
55
84
 
56
- // create xhr object
57
- const xhr = utils.initXhr('/readdir', this.APIOrigin, undefined, "post", "text/plain;actually=json");
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
+ }
58
98
 
59
- // set up event handlers for load and error events
60
- utils.setupXhrEventHandlers(xhr, options.success, options.error, async (result) => {
61
- // Calculate the size of the result for cache eligibility check
62
- const resultSize = JSON.stringify(result).length;
63
-
64
- // Cache the result if it's not bigger than MAX_CACHE_SIZE
65
- const MAX_CACHE_SIZE = 100 * 1024 * 1024;
99
+ // create xhr object
100
+ const xhr = utils.initXhr('/readdir', this.APIOrigin, undefined, "post", "text/plain;actually=json");
66
101
 
67
- if(resultSize <= MAX_CACHE_SIZE){
68
- // UPSERT the cache
69
- puter._cache.set(cacheKey, result);
70
- }
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
+ }
71
114
 
72
- // set each individual item's cache
73
- for(const item of result){
74
- puter._cache.set('item:' + item.path, item);
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);
75
135
  }
76
-
77
- resolve(result);
78
- }, reject);
79
136
 
80
- // Build request payload - support both path and uid parameters
81
- const payload = {
82
- no_thumbs: options.no_thumbs,
83
- no_assocs: options.no_assocs,
84
- auth_token: this.authToken
85
- };
137
+ xhr.send(JSON.stringify(payload));
138
+ });
86
139
 
87
- // Add either uid or path to the payload
88
- if (options.uid) {
89
- payload.uid = options.uid;
90
- } else if (options.path) {
91
- payload.path = getAbsolutePathForApp(options.path);
92
- }
140
+ // Store the promise and timestamp in the in-flight tracker
141
+ inflightRequests.set(deduplicationKey, {
142
+ promise: requestPromise,
143
+ timestamp: now,
144
+ });
93
145
 
94
- xhr.send(JSON.stringify(payload));
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
+ }
95
155
  })
96
156
  }
97
157