@memberjunction/storage 3.1.1 → 3.2.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.
- package/dist/__tests__/FileStorageBase.test.d.ts +6 -0
- package/dist/__tests__/FileStorageBase.test.d.ts.map +1 -0
- package/dist/__tests__/FileStorageBase.test.js +213 -0
- package/dist/__tests__/FileStorageBase.test.js.map +1 -0
- package/dist/__tests__/util.test.d.ts +7 -0
- package/dist/__tests__/util.test.d.ts.map +1 -0
- package/dist/__tests__/util.test.js +326 -0
- package/dist/__tests__/util.test.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -14
- package/dist/config.js.map +1 -1
- package/dist/drivers/AWSFileStorage.d.ts +20 -0
- package/dist/drivers/AWSFileStorage.d.ts.map +1 -1
- package/dist/drivers/AWSFileStorage.js +43 -18
- package/dist/drivers/AWSFileStorage.js.map +1 -1
- package/dist/drivers/AzureFileStorage.d.ts +1 -1
- package/dist/drivers/AzureFileStorage.d.ts.map +1 -1
- package/dist/drivers/AzureFileStorage.js +17 -17
- package/dist/drivers/AzureFileStorage.js.map +1 -1
- package/dist/drivers/BoxFileStorage.d.ts +47 -1
- package/dist/drivers/BoxFileStorage.d.ts.map +1 -1
- package/dist/drivers/BoxFileStorage.js +219 -95
- package/dist/drivers/BoxFileStorage.js.map +1 -1
- package/dist/drivers/DropboxFileStorage.d.ts +59 -0
- package/dist/drivers/DropboxFileStorage.d.ts.map +1 -1
- package/dist/drivers/DropboxFileStorage.js +314 -62
- package/dist/drivers/DropboxFileStorage.js.map +1 -1
- package/dist/drivers/GoogleDriveFileStorage.d.ts +29 -0
- package/dist/drivers/GoogleDriveFileStorage.d.ts.map +1 -1
- package/dist/drivers/GoogleDriveFileStorage.js +220 -72
- package/dist/drivers/GoogleDriveFileStorage.js.map +1 -1
- package/dist/drivers/GoogleFileStorage.d.ts.map +1 -1
- package/dist/drivers/GoogleFileStorage.js +12 -12
- package/dist/drivers/GoogleFileStorage.js.map +1 -1
- package/dist/drivers/SharePointFileStorage.d.ts +64 -5
- package/dist/drivers/SharePointFileStorage.d.ts.map +1 -1
- package/dist/drivers/SharePointFileStorage.js +265 -94
- package/dist/drivers/SharePointFileStorage.js.map +1 -1
- package/dist/generic/FileStorageBase.d.ts +79 -13
- package/dist/generic/FileStorageBase.d.ts.map +1 -1
- package/dist/generic/FileStorageBase.js +57 -12
- package/dist/generic/FileStorageBase.js.map +1 -1
- package/dist/util.d.ts +429 -11
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +677 -16
- package/dist/util.js.map +1 -1
- package/package.json +11 -5
|
@@ -132,6 +132,11 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
132
132
|
* Box SDK client for making API calls
|
|
133
133
|
*/
|
|
134
134
|
_client;
|
|
135
|
+
/**
|
|
136
|
+
* Callback to persist new refresh tokens when they are issued.
|
|
137
|
+
* Box issues new refresh tokens with each token refresh.
|
|
138
|
+
*/
|
|
139
|
+
_onTokenRefresh;
|
|
135
140
|
/**
|
|
136
141
|
* Creates a new BoxFileStorage instance
|
|
137
142
|
*
|
|
@@ -169,6 +174,10 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
169
174
|
* This method must be called after creating a BoxFileStorage instance.
|
|
170
175
|
* It initializes the Box SDK and creates the client for API calls.
|
|
171
176
|
*
|
|
177
|
+
* Can optionally accept a config object with OAuth credentials, which is used
|
|
178
|
+
* for per-user OAuth authentication where credentials come from the database.
|
|
179
|
+
*
|
|
180
|
+
* @param config - Optional configuration object containing OAuth2 credentials
|
|
172
181
|
* @returns A Promise that resolves when initialization is complete
|
|
173
182
|
*
|
|
174
183
|
* @example
|
|
@@ -178,22 +187,47 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
178
187
|
* // Now the storage provider is ready to use
|
|
179
188
|
* ```
|
|
180
189
|
*/
|
|
181
|
-
async initialize() {
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
190
|
+
async initialize(config) {
|
|
191
|
+
// Always call super to store accountId and accountName
|
|
192
|
+
await super.initialize(config);
|
|
193
|
+
// If config is provided (from database OAuth), use those credentials
|
|
194
|
+
if (config) {
|
|
195
|
+
if (config.clientID)
|
|
196
|
+
this._clientId = config.clientID;
|
|
197
|
+
if (config.clientSecret)
|
|
198
|
+
this._clientSecret = config.clientSecret;
|
|
199
|
+
if (config.refreshToken)
|
|
200
|
+
this._refreshToken = config.refreshToken;
|
|
201
|
+
if (config.accessToken)
|
|
202
|
+
this._accessToken = config.accessToken;
|
|
203
|
+
if (config.enterpriseID)
|
|
204
|
+
this._enterpriseId = config.enterpriseID;
|
|
205
|
+
if (config.rootFolderID)
|
|
206
|
+
this._rootFolderId = config.rootFolderID;
|
|
207
|
+
// Store the token refresh callback - CRITICAL for Box since it issues new refresh tokens
|
|
208
|
+
if (config.onTokenRefresh)
|
|
209
|
+
this._onTokenRefresh = config.onTokenRefresh;
|
|
210
|
+
}
|
|
211
|
+
// Always get a fresh access token using refresh token if available.
|
|
212
|
+
// Box access tokens are short-lived (~60 min), so stored tokens are likely expired.
|
|
213
|
+
// We should always refresh rather than trusting a stored access token.
|
|
214
|
+
if (this._refreshToken && this._clientId && this._clientSecret) {
|
|
215
|
+
// Use refresh token to get a fresh access token (OAuth flow)
|
|
216
|
+
console.log('[Box] Refreshing access token during initialization...');
|
|
217
|
+
const tokenData = await this._refreshAccessToken();
|
|
218
|
+
this._accessToken = tokenData.access_token;
|
|
219
|
+
}
|
|
220
|
+
else if (!this._accessToken) {
|
|
221
|
+
// No refresh token and no access token - try other auth methods
|
|
222
|
+
if (this._clientId && this._clientSecret && this._enterpriseId) {
|
|
223
|
+
// Use client credentials to get token (JWT flow)
|
|
186
224
|
this._accessToken = await this._getAccessToken();
|
|
187
225
|
}
|
|
188
|
-
else if (this._refreshToken) {
|
|
189
|
-
// Use refresh token to get access token
|
|
190
|
-
const tokenData = await this._refreshAccessToken();
|
|
191
|
-
this._accessToken = tokenData.access_token;
|
|
192
|
-
}
|
|
193
226
|
else {
|
|
194
227
|
throw new Error('Box storage requires either access token, refresh token, or client credentials');
|
|
195
228
|
}
|
|
196
229
|
}
|
|
230
|
+
// If we only have an access token (no refresh token), use it as-is and hope it's still valid
|
|
197
231
|
// Initialize Box client with developer token auth
|
|
198
232
|
const auth = new box_node_sdk_1.BoxDeveloperTokenAuth({ token: this._accessToken });
|
|
199
233
|
this._client = new box_node_sdk_1.BoxClient({ auth });
|
|
@@ -213,7 +247,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
213
247
|
const response = await fetch('https://api.box.com/oauth2/token', {
|
|
214
248
|
method: 'POST',
|
|
215
249
|
headers: {
|
|
216
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
250
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
217
251
|
},
|
|
218
252
|
body: new URLSearchParams({
|
|
219
253
|
client_id: this._clientId,
|
|
@@ -221,14 +255,14 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
221
255
|
grant_type: 'client_credentials',
|
|
222
256
|
box_subject_type: 'enterprise',
|
|
223
257
|
box_subject_id: this._enterpriseId,
|
|
224
|
-
})
|
|
258
|
+
}),
|
|
225
259
|
});
|
|
226
260
|
if (!response.ok) {
|
|
227
261
|
throw new Error(`Failed to get access token: ${response.status} ${response.statusText}`);
|
|
228
262
|
}
|
|
229
|
-
const tokenData = await response.json();
|
|
263
|
+
const tokenData = (await response.json());
|
|
230
264
|
this._accessToken = tokenData.access_token;
|
|
231
|
-
this._tokenExpiresAt = Date.now() +
|
|
265
|
+
this._tokenExpiresAt = Date.now() + tokenData.expires_in * 1000 - 60000;
|
|
232
266
|
return tokenData.access_token;
|
|
233
267
|
}
|
|
234
268
|
catch (error) {
|
|
@@ -247,22 +281,44 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
247
281
|
const response = await fetch('https://api.box.com/oauth2/token', {
|
|
248
282
|
method: 'POST',
|
|
249
283
|
headers: {
|
|
250
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
284
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
251
285
|
},
|
|
252
286
|
body: new URLSearchParams({
|
|
253
287
|
grant_type: 'refresh_token',
|
|
254
288
|
refresh_token: this._refreshToken,
|
|
255
289
|
client_id: this._clientId,
|
|
256
|
-
client_secret: this._clientSecret
|
|
257
|
-
})
|
|
290
|
+
client_secret: this._clientSecret,
|
|
291
|
+
}),
|
|
258
292
|
});
|
|
259
293
|
if (!response.ok) {
|
|
260
294
|
throw new Error(`Failed to refresh token: ${response.status} ${response.statusText}`);
|
|
261
295
|
}
|
|
262
|
-
const data = await response.json();
|
|
296
|
+
const data = (await response.json());
|
|
263
297
|
this._accessToken = data.access_token;
|
|
264
|
-
this.
|
|
265
|
-
|
|
298
|
+
this._tokenExpiresAt = Date.now() + data.expires_in * 1000 - 60000;
|
|
299
|
+
// CRITICAL: Box issues a NEW refresh token with every token refresh.
|
|
300
|
+
// The old refresh token is immediately invalidated.
|
|
301
|
+
// We MUST persist the new refresh token to the database.
|
|
302
|
+
if (data.refresh_token) {
|
|
303
|
+
this._refreshToken = data.refresh_token;
|
|
304
|
+
// Call the callback to persist the new tokens to the database
|
|
305
|
+
if (this._onTokenRefresh) {
|
|
306
|
+
console.log('[Box] New refresh token received, persisting to database...');
|
|
307
|
+
try {
|
|
308
|
+
await this._onTokenRefresh(data.refresh_token, data.access_token);
|
|
309
|
+
console.log('[Box] New tokens persisted successfully');
|
|
310
|
+
}
|
|
311
|
+
catch (callbackError) {
|
|
312
|
+
console.error('[Box] Failed to persist new tokens:', callbackError);
|
|
313
|
+
// Don't throw here - we still have the tokens in memory and can continue
|
|
314
|
+
// But the next time the server restarts, authentication will fail
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
console.warn('[Box] New refresh token received but no callback registered to persist it!');
|
|
319
|
+
console.warn('[Box] This will cause authentication to fail after server restart.');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
266
322
|
return data;
|
|
267
323
|
}
|
|
268
324
|
catch (error) {
|
|
@@ -294,7 +350,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
294
350
|
if (this._refreshToken && this._clientId && this._clientSecret) {
|
|
295
351
|
const tokenData = await this._refreshAccessToken();
|
|
296
352
|
this._accessToken = tokenData.access_token;
|
|
297
|
-
this._tokenExpiresAt = Date.now() +
|
|
353
|
+
this._tokenExpiresAt = Date.now() + tokenData.expires_in * 1000;
|
|
298
354
|
}
|
|
299
355
|
else if (this._clientId && this._clientSecret && this._enterpriseId) {
|
|
300
356
|
this._accessToken = await this._getAccessToken();
|
|
@@ -362,14 +418,14 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
362
418
|
// Try as file first, then folder
|
|
363
419
|
try {
|
|
364
420
|
const fileInfo = await this._client.files.getFileById(parsedPath.id, {
|
|
365
|
-
queryParams: { fields: ['id', 'type'] }
|
|
421
|
+
queryParams: { fields: ['id', 'type'] },
|
|
366
422
|
});
|
|
367
423
|
return { id: fileInfo.id, type: fileInfo.type };
|
|
368
424
|
}
|
|
369
425
|
catch {
|
|
370
426
|
try {
|
|
371
427
|
const folderInfo = await this._client.folders.getFolderById(parsedPath.id, {
|
|
372
|
-
queryParams: { fields: ['id', 'type'] }
|
|
428
|
+
queryParams: { fields: ['id', 'type'] },
|
|
373
429
|
});
|
|
374
430
|
return { id: folderInfo.id, type: folderInfo.type };
|
|
375
431
|
}
|
|
@@ -396,8 +452,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
396
452
|
queryParams: {
|
|
397
453
|
fields: ['name', 'type', 'id'],
|
|
398
454
|
limit: LIMIT,
|
|
399
|
-
offset: offset
|
|
400
|
-
}
|
|
455
|
+
offset: offset,
|
|
456
|
+
},
|
|
401
457
|
});
|
|
402
458
|
// Look for the item by name
|
|
403
459
|
const item = folderItems.entries?.find((i) => i.name === parsedPath.name);
|
|
@@ -439,17 +495,15 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
439
495
|
name,
|
|
440
496
|
path,
|
|
441
497
|
fullPath,
|
|
442
|
-
size: isDirectory ? 0 :
|
|
443
|
-
contentType: isDirectory ?
|
|
444
|
-
'application/x-directory' :
|
|
445
|
-
(item.content_type || mime.lookup(name) || 'application/octet-stream'),
|
|
498
|
+
size: isDirectory ? 0 : item.size || 0,
|
|
499
|
+
contentType: isDirectory ? 'application/x-directory' : item.content_type || mime.lookup(name) || 'application/octet-stream',
|
|
446
500
|
lastModified: new Date(item.modified_at || item.created_at || Date.now()),
|
|
447
501
|
isDirectory,
|
|
448
502
|
etag: item.etag,
|
|
449
503
|
customMetadata: {
|
|
450
504
|
id: item.id,
|
|
451
|
-
sequence_id: item.sequence_id
|
|
452
|
-
}
|
|
505
|
+
sequence_id: item.sequence_id,
|
|
506
|
+
},
|
|
453
507
|
};
|
|
454
508
|
}
|
|
455
509
|
/**
|
|
@@ -624,16 +678,16 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
624
678
|
await this._client.folders.updateFolderById(sourceInfo.id, {
|
|
625
679
|
requestBody: {
|
|
626
680
|
parent: { id: destParentId },
|
|
627
|
-
name: destPath.name
|
|
628
|
-
}
|
|
681
|
+
name: destPath.name,
|
|
682
|
+
},
|
|
629
683
|
});
|
|
630
684
|
}
|
|
631
685
|
else {
|
|
632
686
|
await this._client.files.updateFileById(sourceInfo.id, {
|
|
633
687
|
requestBody: {
|
|
634
688
|
parent: { id: destParentId },
|
|
635
|
-
name: destPath.name
|
|
636
|
-
}
|
|
689
|
+
name: destPath.name,
|
|
690
|
+
},
|
|
637
691
|
});
|
|
638
692
|
}
|
|
639
693
|
return true;
|
|
@@ -740,38 +794,100 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
740
794
|
*/
|
|
741
795
|
async ListObjects(prefix, delimiter = '/') {
|
|
742
796
|
try {
|
|
797
|
+
// Ensure we have a valid token before making API calls
|
|
798
|
+
await this._ensureValidToken();
|
|
799
|
+
console.log(`[Box ListObjects] Called with prefix: "${prefix}", delimiter: "${delimiter}"`);
|
|
800
|
+
// Normalize the prefix - remove leading/trailing slashes for consistent handling
|
|
801
|
+
let normalizedPrefix = prefix;
|
|
802
|
+
if (normalizedPrefix && normalizedPrefix !== '/') {
|
|
803
|
+
// Remove leading slash
|
|
804
|
+
if (normalizedPrefix.startsWith('/')) {
|
|
805
|
+
normalizedPrefix = normalizedPrefix.substring(1);
|
|
806
|
+
}
|
|
807
|
+
// Remove trailing slash
|
|
808
|
+
if (normalizedPrefix.endsWith('/')) {
|
|
809
|
+
normalizedPrefix = normalizedPrefix.substring(0, normalizedPrefix.length - 1);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
// Empty string or "/" means root
|
|
814
|
+
normalizedPrefix = '';
|
|
815
|
+
}
|
|
816
|
+
console.log(`[Box ListObjects] Normalized prefix: "${normalizedPrefix}"`);
|
|
743
817
|
let folderId;
|
|
744
|
-
|
|
745
|
-
|
|
818
|
+
// Handle root folder case
|
|
819
|
+
if (!normalizedPrefix || normalizedPrefix === '') {
|
|
820
|
+
folderId = this._rootFolderId;
|
|
821
|
+
console.log(`[Box ListObjects] Using root folder ID: ${folderId}`);
|
|
746
822
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
823
|
+
else {
|
|
824
|
+
try {
|
|
825
|
+
const itemInfo = await this._getItemInfoFromPath(normalizedPrefix);
|
|
826
|
+
if (!itemInfo) {
|
|
827
|
+
console.log(`[Box ListObjects] Folder not found for prefix: "${normalizedPrefix}"`);
|
|
828
|
+
return { objects: [], prefixes: [] };
|
|
829
|
+
}
|
|
830
|
+
if (itemInfo.type !== 'folder') {
|
|
831
|
+
console.log(`[Box ListObjects] Path "${normalizedPrefix}" is not a folder (type: ${itemInfo.type})`);
|
|
832
|
+
return { objects: [], prefixes: [] };
|
|
833
|
+
}
|
|
834
|
+
folderId = itemInfo.id;
|
|
835
|
+
}
|
|
836
|
+
catch (error) {
|
|
837
|
+
// If folder doesn't exist, return empty result
|
|
838
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
839
|
+
console.log(`[Box ListObjects] Error resolving path "${normalizedPrefix}": ${errorMsg}`);
|
|
840
|
+
return { objects: [], prefixes: [] };
|
|
841
|
+
}
|
|
750
842
|
}
|
|
843
|
+
console.log(`[Box ListObjects] Listing folder ID: ${folderId} for prefix: "${normalizedPrefix}"`);
|
|
751
844
|
// Get folder contents using SDK
|
|
752
845
|
const result = await this._client.folders.getFolderItems(folderId, {
|
|
753
846
|
queryParams: {
|
|
754
|
-
fields: ['id', 'name', 'type', 'size', 'content_type', 'modified_at', 'created_at', 'etag', 'sequence_id']
|
|
755
|
-
}
|
|
847
|
+
fields: ['id', 'name', 'type', 'size', 'content_type', 'modified_at', 'created_at', 'etag', 'sequence_id'],
|
|
848
|
+
},
|
|
756
849
|
});
|
|
757
850
|
const objects = [];
|
|
758
851
|
const prefixes = [];
|
|
759
|
-
// Process entries
|
|
852
|
+
// Process entries - use normalizedPrefix for consistent path handling
|
|
760
853
|
for (const entry of result.entries) {
|
|
761
|
-
objects.push(this._convertToMetadata(entry,
|
|
854
|
+
objects.push(this._convertToMetadata(entry, normalizedPrefix));
|
|
762
855
|
// If it's a folder, add to prefixes
|
|
763
856
|
if (entry.type === 'folder') {
|
|
764
|
-
const folderPath =
|
|
765
|
-
? (prefix.endsWith('/') ? `${prefix}${entry.name}` : `${prefix}/${entry.name}`)
|
|
766
|
-
: entry.name;
|
|
857
|
+
const folderPath = normalizedPrefix ? `${normalizedPrefix}/${entry.name}` : entry.name;
|
|
767
858
|
prefixes.push(`${folderPath}/`);
|
|
768
859
|
}
|
|
769
860
|
}
|
|
861
|
+
console.log(`[Box ListObjects] Found ${objects.length} objects and ${prefixes.length} prefixes`);
|
|
770
862
|
return { objects, prefixes };
|
|
771
863
|
}
|
|
772
864
|
catch (error) {
|
|
773
|
-
console.error('
|
|
774
|
-
|
|
865
|
+
console.error('[Box ListObjects] Error:', { prefix, error: error instanceof Error ? error.message : error });
|
|
866
|
+
// Re-throw with more context so the error is visible to the user
|
|
867
|
+
throw new Error(`Failed to list Box folder "${prefix}": ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Ensures we have a valid access token, refreshing if necessary.
|
|
872
|
+
* Also reinitializes the Box client with the new token.
|
|
873
|
+
*/
|
|
874
|
+
async _ensureValidToken() {
|
|
875
|
+
// Check if token is expired or about to expire (within 5 minutes)
|
|
876
|
+
const tokenExpiresSoon = this._tokenExpiresAt && Date.now() > this._tokenExpiresAt - 300000;
|
|
877
|
+
if (!this._accessToken || tokenExpiresSoon) {
|
|
878
|
+
console.log('[Box] Token expired or expiring soon, refreshing...');
|
|
879
|
+
if (this._refreshToken && this._clientId && this._clientSecret) {
|
|
880
|
+
const tokenData = await this._refreshAccessToken();
|
|
881
|
+
this._accessToken = tokenData.access_token;
|
|
882
|
+
this._tokenExpiresAt = Date.now() + tokenData.expires_in * 1000 - 60000;
|
|
883
|
+
// Reinitialize the Box client with the new token
|
|
884
|
+
const auth = new box_node_sdk_1.BoxDeveloperTokenAuth({ token: this._accessToken });
|
|
885
|
+
this._client = new box_node_sdk_1.BoxClient({ auth });
|
|
886
|
+
console.log('[Box] Token refreshed successfully');
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
throw new Error('Cannot refresh Box token: missing credentials');
|
|
890
|
+
}
|
|
775
891
|
}
|
|
776
892
|
}
|
|
777
893
|
/**
|
|
@@ -809,14 +925,14 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
809
925
|
*/
|
|
810
926
|
async CreateDirectory(directoryPath) {
|
|
811
927
|
try {
|
|
928
|
+
// Ensure we have a valid token before making API calls
|
|
929
|
+
await this._ensureValidToken();
|
|
812
930
|
// Root directory always exists
|
|
813
931
|
if (!directoryPath || directoryPath === '/' || directoryPath === '') {
|
|
814
932
|
return true;
|
|
815
933
|
}
|
|
816
934
|
// Remove trailing slash if present
|
|
817
|
-
const normalizedPath = directoryPath.endsWith('/')
|
|
818
|
-
? directoryPath.substring(0, directoryPath.length - 1)
|
|
819
|
-
: directoryPath;
|
|
935
|
+
const normalizedPath = directoryPath.endsWith('/') ? directoryPath.substring(0, directoryPath.length - 1) : directoryPath;
|
|
820
936
|
// First check if directory already exists
|
|
821
937
|
try {
|
|
822
938
|
if (await this.DirectoryExists(normalizedPath)) {
|
|
@@ -852,15 +968,14 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
852
968
|
try {
|
|
853
969
|
await this._client.folders.createFolder({
|
|
854
970
|
name: folderName,
|
|
855
|
-
parent: { id: parentFolderId }
|
|
971
|
+
parent: { id: parentFolderId },
|
|
856
972
|
});
|
|
857
973
|
console.log(`✅ Folder created successfully: ${normalizedPath}`);
|
|
858
974
|
return true;
|
|
859
975
|
}
|
|
860
976
|
catch (error) {
|
|
861
977
|
// Handle conflicts - if the folder already exists, that's a success
|
|
862
|
-
if (error.statusCode === 409 ||
|
|
863
|
-
(error.message && error.message.includes('item_name_in_use'))) {
|
|
978
|
+
if (error.statusCode === 409 || (error.message && error.message.includes('item_name_in_use'))) {
|
|
864
979
|
console.log(`Folder already exists (conflict): ${normalizedPath}`);
|
|
865
980
|
return true;
|
|
866
981
|
}
|
|
@@ -873,7 +988,6 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
873
988
|
return false;
|
|
874
989
|
}
|
|
875
990
|
}
|
|
876
|
-
;
|
|
877
991
|
/**
|
|
878
992
|
* Gets file representation information for a Box file
|
|
879
993
|
*
|
|
@@ -912,11 +1026,11 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
912
1026
|
// Get file with representations field - SDK handles auth automatically
|
|
913
1027
|
const file = await this._client.files.getFileById(fileId, {
|
|
914
1028
|
queryParams: {
|
|
915
|
-
fields: ['representations']
|
|
1029
|
+
fields: ['representations'],
|
|
916
1030
|
},
|
|
917
1031
|
headers: {
|
|
918
|
-
xRepHints: repHints
|
|
919
|
-
}
|
|
1032
|
+
xRepHints: repHints,
|
|
1033
|
+
},
|
|
920
1034
|
});
|
|
921
1035
|
// Convert to plain JSON object
|
|
922
1036
|
return JSON.parse(JSON.stringify(file));
|
|
@@ -926,7 +1040,6 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
926
1040
|
throw error;
|
|
927
1041
|
}
|
|
928
1042
|
}
|
|
929
|
-
;
|
|
930
1043
|
/**
|
|
931
1044
|
* Deletes a directory from Box storage
|
|
932
1045
|
*
|
|
@@ -961,9 +1074,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
961
1074
|
async DeleteDirectory(directoryPath, recursive = false) {
|
|
962
1075
|
try {
|
|
963
1076
|
// Remove trailing slash if present
|
|
964
|
-
const normalizedPath = directoryPath.endsWith('/')
|
|
965
|
-
? directoryPath.substring(0, directoryPath.length - 1)
|
|
966
|
-
: directoryPath;
|
|
1077
|
+
const normalizedPath = directoryPath.endsWith('/') ? directoryPath.substring(0, directoryPath.length - 1) : directoryPath;
|
|
967
1078
|
// Get folder ID
|
|
968
1079
|
let folderId;
|
|
969
1080
|
try {
|
|
@@ -977,8 +1088,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
977
1088
|
if (!recursive) {
|
|
978
1089
|
const contents = await this._client.folders.getFolderItems(folderId, {
|
|
979
1090
|
queryParams: {
|
|
980
|
-
limit: 1
|
|
981
|
-
}
|
|
1091
|
+
limit: 1,
|
|
1092
|
+
},
|
|
982
1093
|
});
|
|
983
1094
|
if (contents.entries.length > 0) {
|
|
984
1095
|
throw new Error('Directory is not empty');
|
|
@@ -988,8 +1099,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
988
1099
|
if (recursive) {
|
|
989
1100
|
await this._client.folders.deleteFolderById(folderId, {
|
|
990
1101
|
queryParams: {
|
|
991
|
-
recursive: true
|
|
992
|
-
}
|
|
1102
|
+
recursive: true,
|
|
1103
|
+
},
|
|
993
1104
|
});
|
|
994
1105
|
}
|
|
995
1106
|
else {
|
|
@@ -1050,13 +1161,13 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1050
1161
|
// Try as file first, then folder
|
|
1051
1162
|
try {
|
|
1052
1163
|
const fileInfo = await this._client.files.getFileById(params.objectId, {
|
|
1053
|
-
queryParams: { fields: ['id', 'type'] }
|
|
1164
|
+
queryParams: { fields: ['id', 'type'] },
|
|
1054
1165
|
});
|
|
1055
1166
|
itemInfo = { id: fileInfo.id, type: fileInfo.type };
|
|
1056
1167
|
}
|
|
1057
1168
|
catch {
|
|
1058
1169
|
const folderInfo = await this._client.folders.getFolderById(params.objectId, {
|
|
1059
|
-
queryParams: { fields: ['id', 'type'] }
|
|
1170
|
+
queryParams: { fields: ['id', 'type'] },
|
|
1060
1171
|
});
|
|
1061
1172
|
itemInfo = { id: folderInfo.id, type: folderInfo.type };
|
|
1062
1173
|
}
|
|
@@ -1074,8 +1185,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1074
1185
|
// Get full metadata using the SDK based on type
|
|
1075
1186
|
const options = {
|
|
1076
1187
|
queryParams: {
|
|
1077
|
-
fields: ['id', 'name', 'type', 'size', 'content_type', 'modified_at', 'created_at', 'etag', 'sequence_id']
|
|
1078
|
-
}
|
|
1188
|
+
fields: ['id', 'name', 'type', 'size', 'content_type', 'modified_at', 'created_at', 'etag', 'sequence_id'],
|
|
1189
|
+
},
|
|
1079
1190
|
};
|
|
1080
1191
|
const metadata = itemInfo.type === 'folder'
|
|
1081
1192
|
? await this._client.folders.getFolderById(itemInfo.id, options)
|
|
@@ -1120,6 +1231,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1120
1231
|
*/
|
|
1121
1232
|
async GetObject(params) {
|
|
1122
1233
|
try {
|
|
1234
|
+
// Ensure we have a valid token before making API calls
|
|
1235
|
+
await this._ensureValidToken();
|
|
1123
1236
|
// Validate params
|
|
1124
1237
|
if (!params.objectId && !params.fullPath) {
|
|
1125
1238
|
throw new Error('Either objectId or fullPath must be provided');
|
|
@@ -1201,9 +1314,13 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1201
1314
|
*/
|
|
1202
1315
|
async PutObject(objectName, data, contentType, metadata) {
|
|
1203
1316
|
try {
|
|
1204
|
-
|
|
1317
|
+
// Ensure we have a valid token before making API calls
|
|
1318
|
+
await this._ensureValidToken();
|
|
1319
|
+
console.log(`[Box PutObject] Starting upload for: ${objectName}, size: ${data.length} bytes`);
|
|
1320
|
+
console.log(`[Box PutObject] Account: ${this.AccountName}, Root folder ID: ${this._rootFolderId}`);
|
|
1205
1321
|
// Get the parent folder ID and file name
|
|
1206
1322
|
const parsedPath = this._parsePath(objectName);
|
|
1323
|
+
console.log(`[Box PutObject] Parsed path:`, { name: parsedPath.name, parent: parsedPath.parent });
|
|
1207
1324
|
let parentId = this._rootFolderId;
|
|
1208
1325
|
if (parsedPath.parent) {
|
|
1209
1326
|
try {
|
|
@@ -1232,33 +1349,43 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1232
1349
|
try {
|
|
1233
1350
|
// Convert Buffer to Readable stream for the SDK
|
|
1234
1351
|
const fileStream = stream_1.Readable.from(data);
|
|
1352
|
+
console.log(`[Box PutObject] Uploading to parent folder ID: ${parentId}, filename: ${parsedPath.name}`);
|
|
1235
1353
|
if (fileId) {
|
|
1236
1354
|
// Update existing file (upload new version)
|
|
1355
|
+
console.log(`[Box PutObject] File exists (ID: ${fileId}), uploading new version...`);
|
|
1237
1356
|
await this._client.uploads.uploadFileVersion(fileId, {
|
|
1238
1357
|
attributes: {
|
|
1239
|
-
name: parsedPath.name
|
|
1358
|
+
name: parsedPath.name,
|
|
1240
1359
|
},
|
|
1241
|
-
file: fileStream
|
|
1360
|
+
file: fileStream,
|
|
1242
1361
|
});
|
|
1243
1362
|
console.log(`✅ File updated successfully: ${objectName}`);
|
|
1244
1363
|
}
|
|
1245
1364
|
else {
|
|
1246
1365
|
// Upload new file
|
|
1366
|
+
console.log(`[Box PutObject] File does not exist, creating new file...`);
|
|
1247
1367
|
await this._client.uploads.uploadFile({
|
|
1248
1368
|
attributes: {
|
|
1249
1369
|
name: parsedPath.name,
|
|
1250
|
-
parent: { id: parentId }
|
|
1370
|
+
parent: { id: parentId },
|
|
1251
1371
|
},
|
|
1252
|
-
file: fileStream
|
|
1372
|
+
file: fileStream,
|
|
1253
1373
|
});
|
|
1254
1374
|
console.log(`✅ File uploaded successfully: ${objectName}`);
|
|
1255
1375
|
}
|
|
1256
1376
|
return true;
|
|
1257
1377
|
}
|
|
1258
1378
|
catch (uploadError) {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1379
|
+
const error = uploadError;
|
|
1380
|
+
console.error(`[Box PutObject] Error uploading file:`, {
|
|
1381
|
+
statusCode: error.statusCode,
|
|
1382
|
+
message: error.message,
|
|
1383
|
+
code: error.code,
|
|
1384
|
+
contextInfo: error.context_info,
|
|
1385
|
+
fullError: uploadError,
|
|
1386
|
+
});
|
|
1387
|
+
if (error.statusCode === 409) {
|
|
1388
|
+
console.log(`[Box PutObject] File already exists (conflict): ${objectName}`);
|
|
1262
1389
|
return true;
|
|
1263
1390
|
}
|
|
1264
1391
|
return false;
|
|
@@ -1269,7 +1396,6 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1269
1396
|
return false;
|
|
1270
1397
|
}
|
|
1271
1398
|
}
|
|
1272
|
-
;
|
|
1273
1399
|
/**
|
|
1274
1400
|
* Copies a file from one location to another
|
|
1275
1401
|
*
|
|
@@ -1327,7 +1453,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1327
1453
|
// Copy the file using SDK
|
|
1328
1454
|
await this._client.files.copyFile(sourceInfo.id, {
|
|
1329
1455
|
parent: { id: destParentId },
|
|
1330
|
-
name: destPath.name
|
|
1456
|
+
name: destPath.name,
|
|
1331
1457
|
});
|
|
1332
1458
|
return true;
|
|
1333
1459
|
}
|
|
@@ -1401,9 +1527,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1401
1527
|
return true;
|
|
1402
1528
|
}
|
|
1403
1529
|
// Remove trailing slash if present
|
|
1404
|
-
const normalizedPath = directoryPath.endsWith('/')
|
|
1405
|
-
? directoryPath.substring(0, directoryPath.length - 1)
|
|
1406
|
-
: directoryPath;
|
|
1530
|
+
const normalizedPath = directoryPath.endsWith('/') ? directoryPath.substring(0, directoryPath.length - 1) : directoryPath;
|
|
1407
1531
|
try {
|
|
1408
1532
|
const folderId = await this._findFolderIdByPath(normalizedPath);
|
|
1409
1533
|
console.log(`✅ Directory ${normalizedPath} exists with ID: ${folderId}`);
|
|
@@ -1411,8 +1535,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1411
1535
|
try {
|
|
1412
1536
|
const folderInfo = await this._client.folders.getFolderById(folderId, {
|
|
1413
1537
|
queryParams: {
|
|
1414
|
-
fields: ['type']
|
|
1415
|
-
}
|
|
1538
|
+
fields: ['type'],
|
|
1539
|
+
},
|
|
1416
1540
|
});
|
|
1417
1541
|
return folderInfo.type === 'folder';
|
|
1418
1542
|
}
|
|
@@ -1447,7 +1571,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1447
1571
|
async _findFolderIdByPath(path) {
|
|
1448
1572
|
try {
|
|
1449
1573
|
// Split the path into segments
|
|
1450
|
-
const pathSegments = path.split('/').filter(segment => segment.length > 0);
|
|
1574
|
+
const pathSegments = path.split('/').filter((segment) => segment.length > 0);
|
|
1451
1575
|
// Handle "All Files" special case - it's not an actual folder name in the API
|
|
1452
1576
|
if (pathSegments.length > 0 && pathSegments[0] === 'All Files') {
|
|
1453
1577
|
pathSegments.shift(); // Remove "All Files" from the path
|
|
@@ -1468,8 +1592,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1468
1592
|
queryParams: {
|
|
1469
1593
|
fields: ['name', 'type', 'id'],
|
|
1470
1594
|
limit: LIMIT,
|
|
1471
|
-
offset: offset
|
|
1472
|
-
}
|
|
1595
|
+
offset: offset,
|
|
1596
|
+
},
|
|
1473
1597
|
});
|
|
1474
1598
|
// Filter to only folders
|
|
1475
1599
|
const folders = items.entries?.filter((item) => item.type === 'folder') || [];
|
|
@@ -1548,7 +1672,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1548
1672
|
query,
|
|
1549
1673
|
limit: maxResults,
|
|
1550
1674
|
fields: ['id', 'name', 'type', 'size', 'content_type', 'modified_at', 'created_at', 'etag', 'path_collection', 'parent'],
|
|
1551
|
-
type: 'file' // Only search for files, not folders
|
|
1675
|
+
type: 'file', // Only search for files, not folders
|
|
1552
1676
|
};
|
|
1553
1677
|
// Build file extensions filter from fileTypes option
|
|
1554
1678
|
if (options?.fileTypes && options.fileTypes.length > 0) {
|
|
@@ -1590,7 +1714,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1590
1714
|
// First verify the folder is accessible to avoid 404 errors with enterprise auth
|
|
1591
1715
|
try {
|
|
1592
1716
|
await this._client.folders.getFolderById(this._rootFolderId, {
|
|
1593
|
-
queryParams: { fields: ['id'] }
|
|
1717
|
+
queryParams: { fields: ['id'] },
|
|
1594
1718
|
});
|
|
1595
1719
|
console.log(`✅ Scoping search to configured root folder: ${this._rootFolderId}`);
|
|
1596
1720
|
searchParams.ancestorFolderIds = [this._rootFolderId];
|
|
@@ -1643,12 +1767,12 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1643
1767
|
matchInFilename: undefined,
|
|
1644
1768
|
customMetadata: {
|
|
1645
1769
|
id: fileItem.id || '',
|
|
1646
|
-
etag: fileItem.etag || ''
|
|
1770
|
+
etag: fileItem.etag || '',
|
|
1647
1771
|
},
|
|
1648
1772
|
providerData: {
|
|
1649
1773
|
boxItemId: fileItem.id,
|
|
1650
|
-
boxItemType: fileItem.type
|
|
1651
|
-
}
|
|
1774
|
+
boxItemType: fileItem.type,
|
|
1775
|
+
},
|
|
1652
1776
|
});
|
|
1653
1777
|
}
|
|
1654
1778
|
}
|
|
@@ -1660,7 +1784,7 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
|
|
|
1660
1784
|
totalMatches,
|
|
1661
1785
|
hasMore,
|
|
1662
1786
|
// Box SDK v10 search doesn't provide pagination tokens in the standard response
|
|
1663
|
-
nextPageToken: undefined
|
|
1787
|
+
nextPageToken: undefined,
|
|
1664
1788
|
};
|
|
1665
1789
|
}
|
|
1666
1790
|
catch (error) {
|