@memberjunction/storage 2.103.0 → 2.104.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.
@@ -37,7 +37,9 @@ const global_1 = require("@memberjunction/global");
37
37
  const env = __importStar(require("env-var"));
38
38
  const mime = __importStar(require("mime-types"));
39
39
  const FileStorageBase_1 = require("../generic/FileStorageBase");
40
- const https = __importStar(require("https"));
40
+ const box_node_sdk_1 = require("box-node-sdk");
41
+ const stream_1 = require("stream");
42
+ const config_1 = require("../config");
41
43
  /**
42
44
  * FileStorageBase implementation for Box.com cloud storage
43
45
  *
@@ -126,6 +128,10 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
126
128
  * Box enterprise ID for JWT auth
127
129
  */
128
130
  _enterpriseId;
131
+ /**
132
+ * Box SDK client for making API calls
133
+ */
134
+ _client;
129
135
  /**
130
136
  * Creates a new BoxFileStorage instance
131
137
  *
@@ -136,24 +142,25 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
136
142
  */
137
143
  constructor() {
138
144
  super();
145
+ // Try to get config from centralized configuration
146
+ const config = (0, config_1.getProviderConfig)('box');
139
147
  // Box auth can be via access token or refresh token
140
- this._accessToken = env.get('STORAGE_BOX_ACCESS_TOKEN').asString();
141
- this._refreshToken = env.get('STORAGE_BOX_REFRESH_TOKEN').asString();
142
- this._clientId = env.get('STORAGE_BOX_CLIENT_ID').asString();
143
- this._clientSecret = env.get('STORAGE_BOX_CLIENT_SECRET').asString();
144
- this._enterpriseId = env.get('STORAGE_BOX_ENTERPRISE_ID').asString();
148
+ this._accessToken = config?.accessToken || env.get('STORAGE_BOX_ACCESS_TOKEN').asString();
149
+ this._refreshToken = config?.refreshToken || env.get('STORAGE_BOX_REFRESH_TOKEN').asString();
150
+ this._clientId = config?.clientID || env.get('STORAGE_BOX_CLIENT_ID').asString();
151
+ this._clientSecret = config?.clientSecret || env.get('STORAGE_BOX_CLIENT_SECRET').asString();
152
+ this._enterpriseId = config?.enterpriseID || env.get('STORAGE_BOX_ENTERPRISE_ID').asString();
145
153
  if (this._refreshToken && (!this._clientId || !this._clientSecret)) {
146
154
  throw new Error('Box storage with refresh token requires STORAGE_BOX_CLIENT_ID and STORAGE_BOX_CLIENT_SECRET');
147
155
  }
148
156
  // Root folder ID, optional (defaults to '0' which is root)
149
- this._rootFolderId = env.get('STORAGE_BOX_ROOT_FOLDER_ID').default('0').asString();
157
+ this._rootFolderId = config?.rootFolderID || env.get('STORAGE_BOX_ROOT_FOLDER_ID').default('0').asString();
150
158
  }
151
159
  /**
152
160
  * Initializes the Box storage driver
153
161
  *
154
- * This method must be called after creating a BoxFileStorage instance
155
- * when using client credentials (JWT) authentication. It obtains the
156
- * initial access token required for API calls.
162
+ * This method must be called after creating a BoxFileStorage instance.
163
+ * It initializes the Box SDK and creates the client for API calls.
157
164
  *
158
165
  * @returns A Promise that resolves when initialization is complete
159
166
  *
@@ -165,9 +172,24 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
165
172
  * ```
166
173
  */
167
174
  async initialize() {
168
- if (!this._accessToken && this._clientId && this._clientSecret && this._enterpriseId) {
169
- await this._setAccessToken();
175
+ // Get access token if not provided
176
+ if (!this._accessToken) {
177
+ if (this._clientId && this._clientSecret && this._enterpriseId) {
178
+ // Use client credentials to get token
179
+ this._accessToken = await this._getAccessToken();
180
+ }
181
+ else if (this._refreshToken) {
182
+ // Use refresh token to get access token
183
+ const tokenData = await this._refreshAccessToken();
184
+ this._accessToken = tokenData.access_token;
185
+ }
186
+ else {
187
+ throw new Error('Box storage requires either access token, refresh token, or client credentials');
188
+ }
170
189
  }
190
+ // Initialize Box client with developer token auth
191
+ const auth = new box_node_sdk_1.BoxDeveloperTokenAuth({ token: this._accessToken });
192
+ this._client = new box_node_sdk_1.BoxClient({ auth });
171
193
  }
172
194
  /**
173
195
  * Obtains an access token using client credentials flow
@@ -176,10 +198,10 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
176
198
  * flow (JWT) with the enterprise as the subject.
177
199
  *
178
200
  * @private
179
- * @returns A Promise that resolves when the access token is obtained
201
+ * @returns A Promise that resolves with the access token
180
202
  * @throws Error if token acquisition fails
181
203
  */
182
- async _setAccessToken() {
204
+ async _getAccessToken() {
183
205
  try {
184
206
  const response = await fetch('https://api.box.com/oauth2/token', {
185
207
  method: 'POST',
@@ -198,15 +220,49 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
198
220
  throw new Error(`Failed to get access token: ${response.status} ${response.statusText}`);
199
221
  }
200
222
  const tokenData = await response.json();
201
- const { access_token, expires_in } = tokenData;
202
- this._accessToken = access_token;
203
- this._tokenExpiresAt = Date.now() + (expires_in * 1000) - 60000; // Subtract 1 minute for safety
223
+ this._accessToken = tokenData.access_token;
224
+ this._tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000) - 60000;
225
+ return tokenData.access_token;
204
226
  }
205
227
  catch (error) {
206
228
  console.error('Error getting Box access token', error);
207
229
  throw new Error('Failed to authenticate with Box: ' + (error instanceof Error ? error.message : String(error)));
208
230
  }
209
231
  }
232
+ /**
233
+ * Refreshes the access token using the refresh token
234
+ *
235
+ * @private
236
+ * @returns A Promise that resolves with the token data
237
+ */
238
+ async _refreshAccessToken() {
239
+ try {
240
+ const response = await fetch('https://api.box.com/oauth2/token', {
241
+ method: 'POST',
242
+ headers: {
243
+ 'Content-Type': 'application/x-www-form-urlencoded'
244
+ },
245
+ body: new URLSearchParams({
246
+ grant_type: 'refresh_token',
247
+ refresh_token: this._refreshToken,
248
+ client_id: this._clientId,
249
+ client_secret: this._clientSecret
250
+ })
251
+ });
252
+ if (!response.ok) {
253
+ throw new Error(`Failed to refresh token: ${response.status} ${response.statusText}`);
254
+ }
255
+ const data = await response.json();
256
+ this._accessToken = data.access_token;
257
+ this._refreshToken = data.refresh_token || this._refreshToken;
258
+ this._tokenExpiresAt = Date.now() + (data.expires_in * 1000) - 60000;
259
+ return data;
260
+ }
261
+ catch (error) {
262
+ console.error('Error refreshing Box access token', error);
263
+ throw new Error('Failed to refresh token: ' + (error instanceof Error ? error.message : String(error)));
264
+ }
265
+ }
210
266
  /**
211
267
  * Returns the current Box API access token
212
268
  *
@@ -223,126 +279,20 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
223
279
  * ```
224
280
  */
225
281
  async AccessToken() {
226
- await this._ensureValidToken();
227
- return this._accessToken;
228
- }
229
- /**
230
- * Ensures a valid access token is available for API requests
231
- *
232
- * This method checks if the current token is valid, and if not, attempts
233
- * to refresh or obtain a new token using the configured authentication method.
234
- *
235
- * @private
236
- * @returns A Promise that resolves to a valid access token
237
- * @throws Error if no valid token can be obtained
238
- */
239
- async _ensureValidToken() {
240
- // If we have a valid token, use it
282
+ // If we have a valid token, return it
241
283
  if (this._accessToken && Date.now() < this._tokenExpiresAt) {
242
284
  return this._accessToken;
243
285
  }
244
- // If we have refresh token, try to get a new access token
286
+ // Otherwise refresh using SDK's built-in token management
245
287
  if (this._refreshToken && this._clientId && this._clientSecret) {
246
- try {
247
- const response = await fetch('https://api.box.com/oauth2/token', {
248
- method: 'POST',
249
- headers: {
250
- 'Content-Type': 'application/x-www-form-urlencoded'
251
- },
252
- body: new URLSearchParams({
253
- grant_type: 'refresh_token',
254
- refresh_token: this._refreshToken,
255
- client_id: this._clientId,
256
- client_secret: this._clientSecret
257
- })
258
- });
259
- if (!response.ok) {
260
- throw new Error(`Failed to refresh token: ${response.status} ${response.statusText}`);
261
- }
262
- const data = await response.json();
263
- this._accessToken = data.access_token;
264
- this._refreshToken = data.refresh_token || this._refreshToken;
265
- this._tokenExpiresAt = Date.now() + (data.expires_in * 1000) - 60000; // Subtract 1 minute for safety
266
- return this._accessToken;
267
- }
268
- catch (error) {
269
- console.error('Error refreshing Box access token', error);
270
- // Fall through to client credentials if refresh fails
271
- }
272
- }
273
- // If we have client credentials, try to get a new access token
274
- if (this._clientId && this._clientSecret && this._enterpriseId) {
275
- try {
276
- await this._setAccessToken();
277
- return this._accessToken;
278
- }
279
- catch (error) {
280
- console.error('Error getting new access token via client credentials', error);
281
- throw new Error('Failed to authenticate with Box: ' + (error instanceof Error ? error.message : String(error)));
282
- }
283
- }
284
- // If we have an access token but it's expired and we can't refresh it
285
- if (this._accessToken) {
286
- console.warn('Using expired Box access token as no refresh mechanism is available');
287
- return this._accessToken;
288
- }
289
- throw new Error('No valid Box access token available and no authentication method configured');
290
- }
291
- /**
292
- * Makes an authenticated API request to the Box API
293
- *
294
- * This helper method handles authentication, request formatting, and
295
- * response parsing for all Box API calls.
296
- *
297
- * @private
298
- * @param endpoint - The API endpoint to call (e.g., '/files/123')
299
- * @param method - The HTTP method to use (default: 'GET')
300
- * @param body - Optional request body (will be serialized as JSON unless it's FormData)
301
- * @param headers - Optional additional headers
302
- * @param baseUrl - Base URL to use (defaults to standard API URL)
303
- * @returns A Promise that resolves to the API response data
304
- * @throws Error if the API request fails
305
- */
306
- async _apiRequest(endpoint, method = 'GET', body, headers = {}, baseUrl = this._baseApiUrl) {
307
- const token = await this._ensureValidToken();
308
- const requestHeaders = {
309
- 'Authorization': `Bearer ${token}`,
310
- ...headers
311
- };
312
- if (body && typeof body !== 'string' && !(body instanceof FormData) && !(body instanceof URLSearchParams)) {
313
- requestHeaders['Content-Type'] = 'application/json';
314
- body = JSON.stringify(body);
315
- }
316
- const url = `${baseUrl}${endpoint}`;
317
- const response = await fetch(url, {
318
- method,
319
- headers: requestHeaders,
320
- body
321
- });
322
- if (response.status === 204 || response.status === 202) {
323
- return null; // No content response
288
+ const tokenData = await this._refreshAccessToken();
289
+ this._accessToken = tokenData.access_token;
290
+ this._tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000);
324
291
  }
325
- // For downloading files, return the response directly
326
- if (endpoint.startsWith('/files/') && endpoint.includes('/content') && response.ok) {
327
- return response;
292
+ else if (this._clientId && this._clientSecret && this._enterpriseId) {
293
+ this._accessToken = await this._getAccessToken();
328
294
  }
329
- // If the response is not JSON, handle it specially
330
- const contentType = response.headers.get('content-type');
331
- if (contentType && !contentType.includes('application/json')) {
332
- if (response.ok) {
333
- return response;
334
- }
335
- else {
336
- throw new Error(`Box API error: ${response.status} - ${response.statusText}`);
337
- }
338
- }
339
- const data = await response.json();
340
- if (!response.ok) {
341
- console.error('Box API error', { status: response.status, data });
342
- const errorData = data;
343
- throw new Error(`Box API error: ${response.status} - ${errorData.message || errorData.error_description || errorData.error || JSON.stringify(data)}`);
344
- }
345
- return data;
295
+ return this._accessToken;
346
296
  }
347
297
  /**
348
298
  * Parses a path string into Box API components
@@ -387,16 +337,43 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
387
337
  * @throws Error if the item does not exist
388
338
  */
389
339
  async _getIdFromPath(path) {
340
+ const itemInfo = await this._getItemInfoFromPath(path);
341
+ return itemInfo?.id || null;
342
+ }
343
+ /**
344
+ * Gets both ID and type information for an item at the given path
345
+ *
346
+ * @param path - Path to the item
347
+ * @returns Object with id and type, or null if not found
348
+ */
349
+ async _getItemInfoFromPath(path) {
390
350
  try {
391
351
  // Parse the path
392
352
  const parsedPath = this._parsePath(path);
393
- // If the id is already in the path, return it
353
+ // If the id is already in the path, we need to determine type separately
394
354
  if (parsedPath.id) {
395
- return parsedPath.id;
355
+ // Try as file first, then folder
356
+ try {
357
+ const fileInfo = await this._client.files.getFileById(parsedPath.id, {
358
+ queryParams: { fields: ['id', 'type'] }
359
+ });
360
+ return { id: fileInfo.id, type: fileInfo.type };
361
+ }
362
+ catch {
363
+ try {
364
+ const folderInfo = await this._client.folders.getFolderById(parsedPath.id, {
365
+ queryParams: { fields: ['id', 'type'] }
366
+ });
367
+ return { id: folderInfo.id, type: folderInfo.type };
368
+ }
369
+ catch {
370
+ return null;
371
+ }
372
+ }
396
373
  }
397
374
  // If it's root, return root folder id
398
375
  if (!parsedPath.name) {
399
- return this._rootFolderId;
376
+ return { id: this._rootFolderId, type: 'folder' };
400
377
  }
401
378
  // First, find the parent folder ID
402
379
  let parentFolderId = this._rootFolderId;
@@ -408,27 +385,29 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
408
385
  let hasMoreItems = true;
409
386
  const LIMIT = 1000;
410
387
  while (hasMoreItems) {
411
- const folderItems = await this._apiRequest(`/folders/${parentFolderId}/items`, 'GET', null, {
412
- 'fields': 'name,type,id',
413
- 'limit': `${LIMIT}`,
414
- 'offset': `${offset}`
388
+ const folderItems = await this._client.folders.getFolderItems(parentFolderId, {
389
+ queryParams: {
390
+ fields: ['name', 'type', 'id'],
391
+ limit: LIMIT,
392
+ offset: offset
393
+ }
415
394
  });
416
395
  // Look for the item by name
417
- const item = folderItems.entries.find((i) => i.name === parsedPath.name);
418
- if (item) {
419
- return item.id;
396
+ const item = folderItems.entries?.find((i) => i.name === parsedPath.name);
397
+ if (item && item.id && item.type) {
398
+ return { id: item.id, type: item.type };
420
399
  }
421
400
  // Update pagination variables
422
- offset += folderItems.entries.length;
401
+ offset += folderItems.entries?.length || 0;
423
402
  // Check if we've processed all items
424
- hasMoreItems = folderItems.entries.length === LIMIT && offset < folderItems.total_count;
403
+ hasMoreItems = (folderItems.entries?.length || 0) === LIMIT && offset < (folderItems.totalCount || 0);
425
404
  }
426
405
  // If we get here, the item was not found
427
406
  console.log(`Item not found: ${parsedPath.name}`);
428
407
  return null;
429
408
  }
430
409
  catch (error) {
431
- console.error('Error in _getIdFromPath', { path, error });
410
+ console.error('Error in _getItemInfoFromPath', { path, error });
432
411
  return null;
433
412
  }
434
413
  }
@@ -521,17 +500,11 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
521
500
  }
522
501
  }
523
502
  }
524
- // Create a file upload session
525
- const data = await this._apiRequest('/files/upload_sessions', 'POST', {
526
- folder_id: parentId,
527
- file_name: parsedPath.name,
528
- file_size: 0 // We'll use this later for chunked uploads
529
- }, {}, this._uploadApiUrl);
530
- // Return the upload URL with the session ID as the provider key
531
- return {
532
- UploadUrl: data.session_endpoints.upload_part,
533
- ProviderKey: `session:${data.id}:${objectName}`
534
- };
503
+ // Box SDK v10 doesn't have a simple createUploadSession on files manager
504
+ // We need to use chunkedUploads manager instead
505
+ // For now, return a simplified URL structure
506
+ // TODO: Implement proper chunked upload session support
507
+ throw new Error('Pre-authenticated upload URLs are not currently supported with Box SDK v10. Use PutObject instead.');
535
508
  }
536
509
  catch (error) {
537
510
  console.error('Error creating pre-auth upload URL', { objectName, error });
@@ -576,12 +549,12 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
576
549
  }
577
550
  // Get the file ID
578
551
  const fileId = await this._getIdFromPath(objectName);
579
- // Create a download URL that's good for 60 minutes
580
- const data = await this._apiRequest(`/files/${fileId}?fields=download_url`, 'GET');
581
- if (!data.download_url) {
552
+ // Get download URL using SDK
553
+ const downloadUrl = await this._client.downloads.getDownloadFileUrl(fileId);
554
+ if (!downloadUrl) {
582
555
  throw new Error(`No download URL available for: ${objectName}`);
583
556
  }
584
- return data.download_url;
557
+ return downloadUrl;
585
558
  }
586
559
  catch (error) {
587
560
  console.error('Error creating pre-auth download URL', { objectName, error });
@@ -621,8 +594,11 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
621
594
  async MoveObject(oldObjectName, newObjectName) {
622
595
  try {
623
596
  // Get source info
624
- const sourceId = await this._getIdFromPath(oldObjectName);
625
- const sourceInfo = await this._apiRequest(`/items/${sourceId}`);
597
+ const sourceInfo = await this._getItemInfoFromPath(oldObjectName);
598
+ if (!sourceInfo) {
599
+ console.log(`Item not found: ${oldObjectName}`);
600
+ return false;
601
+ }
626
602
  // Get destination info
627
603
  const destPath = this._parsePath(newObjectName);
628
604
  let destParentId = this._rootFolderId;
@@ -636,12 +612,23 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
636
612
  destParentId = await this._getIdFromPath(destPath.parent);
637
613
  }
638
614
  }
639
- // Move the item
640
- const endpoint = sourceInfo.type === 'folder' ? '/folders/' : '/files/';
641
- await this._apiRequest(`${endpoint}${sourceId}`, 'PUT', {
642
- parent: { id: destParentId },
643
- name: destPath.name
644
- });
615
+ // Move the item using SDK
616
+ if (sourceInfo.type === 'folder') {
617
+ await this._client.folders.updateFolderById(sourceInfo.id, {
618
+ requestBody: {
619
+ parent: { id: destParentId },
620
+ name: destPath.name
621
+ }
622
+ });
623
+ }
624
+ else {
625
+ await this._client.files.updateFileById(sourceInfo.id, {
626
+ requestBody: {
627
+ parent: { id: destParentId },
628
+ name: destPath.name
629
+ }
630
+ });
631
+ }
645
632
  return true;
646
633
  }
647
634
  catch (error) {
@@ -683,15 +670,22 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
683
670
  try {
684
671
  // Handle session objects specially
685
672
  if (objectName.startsWith('session:')) {
686
- const [, sessionId] = objectName.split(':');
687
- await this._apiRequest(`/files/upload_sessions/${sessionId}`, 'DELETE');
673
+ // Session support not implemented in SDK v10 migration
674
+ // Just return true for now
688
675
  return true;
689
676
  }
690
- const itemId = await this._getIdFromPath(objectName);
691
- const itemInfo = await this._apiRequest(`/items/${itemId}`);
692
- // Delete the item
693
- const endpoint = itemInfo.type === 'folder' ? '/folders/' : '/files/';
694
- await this._apiRequest(`${endpoint}${itemId}`, 'DELETE');
677
+ const itemInfo = await this._getItemInfoFromPath(objectName);
678
+ if (!itemInfo) {
679
+ console.log(`Item not found: ${objectName}`);
680
+ return true; // Already deleted/doesn't exist
681
+ }
682
+ // Delete the item using SDK
683
+ if (itemInfo.type === 'folder') {
684
+ await this._client.folders.deleteFolderById(itemInfo.id);
685
+ }
686
+ else {
687
+ await this._client.files.deleteFileById(itemInfo.id);
688
+ }
695
689
  return true;
696
690
  }
697
691
  catch (error) {
@@ -747,9 +741,11 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
747
741
  // If folder doesn't exist, return empty result
748
742
  return { objects: [], prefixes: [] };
749
743
  }
750
- // Get folder contents
751
- const result = await this._apiRequest(`/folders/${folderId}/items`, 'GET', null, {
752
- 'fields': 'id,name,type,size,content_type,modified_at,created_at,etag,sequence_id'
744
+ // Get folder contents using SDK
745
+ const result = await this._client.folders.getFolderItems(folderId, {
746
+ queryParams: {
747
+ fields: ['id', 'name', 'type', 'size', 'content_type', 'modified_at', 'created_at', 'etag', 'sequence_id']
748
+ }
753
749
  });
754
750
  const objects = [];
755
751
  const prefixes = [];
@@ -845,9 +841,9 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
845
841
  return false;
846
842
  }
847
843
  }
848
- // Create the folder
844
+ // Create the folder using SDK
849
845
  try {
850
- await this._apiRequest('/folders', 'POST', {
846
+ await this._client.folders.createFolder({
851
847
  name: folderName,
852
848
  parent: { id: parentFolderId }
853
849
  });
@@ -856,8 +852,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
856
852
  }
857
853
  catch (error) {
858
854
  // Handle conflicts - if the folder already exists, that's a success
859
- if (error.message && (error.message.includes('409') ||
860
- error.message.includes('item_name_in_use'))) {
855
+ if (error.statusCode === 409 ||
856
+ (error.message && error.message.includes('item_name_in_use'))) {
861
857
  console.log(`Folder already exists (conflict): ${normalizedPath}`);
862
858
  return true;
863
859
  }
@@ -906,20 +902,17 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
906
902
  */
907
903
  async GetFileRepresentations(fileId, repHints = 'png?dimensions=2048x2048') {
908
904
  try {
909
- const token = await this._ensureValidToken();
910
- const response = await fetch(`${this._baseApiUrl}/files/${fileId}?fields=representations`, {
911
- method: 'GET',
905
+ // Get file with representations field - SDK handles auth automatically
906
+ const file = await this._client.files.getFileById(fileId, {
907
+ queryParams: {
908
+ fields: ['representations']
909
+ },
912
910
  headers: {
913
- 'Authorization': `Bearer ${token}`,
914
- 'Content-Type': 'application/json',
915
- 'X-Rep-Hints': repHints
911
+ xRepHints: repHints
916
912
  }
917
913
  });
918
- if (!response.ok) {
919
- throw new Error(`Failed to get file representations: ${response.status} ${response.statusText}`);
920
- }
921
- const data = await response.json();
922
- return data;
914
+ // Convert to plain JSON object
915
+ return JSON.parse(JSON.stringify(file));
923
916
  }
924
917
  catch (error) {
925
918
  console.error('Error getting file representations:', error);
@@ -927,43 +920,6 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
927
920
  }
928
921
  }
929
922
  ;
930
- /**
931
- * Helper function for making HTTP requests
932
- *
933
- * This method provides a Promise-based wrapper around Node.js https requests,
934
- * simplifying the process of making API calls to the Box API.
935
- *
936
- * @private
937
- * @param options - The HTTPS request options (URL, method, headers, etc.)
938
- * @param data - Optional string data to send with the request
939
- * @returns A Promise that resolves to the response data as a string
940
- * @throws Error if the request fails or returns a non-2xx status code
941
- */
942
- async _makeRequest(options, data) {
943
- return new Promise((resolve, reject) => {
944
- const req = https.request(options, (res) => {
945
- let responseData = '';
946
- res.on('data', (chunk) => {
947
- responseData += chunk;
948
- });
949
- res.on('end', () => {
950
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
951
- resolve(responseData);
952
- }
953
- else {
954
- reject(new Error(`Request failed with status code ${res.statusCode}: ${responseData}`));
955
- }
956
- });
957
- });
958
- req.on('error', (error) => {
959
- reject(error);
960
- });
961
- if (data) {
962
- req.write(data);
963
- }
964
- req.end();
965
- });
966
- }
967
923
  /**
968
924
  * Deletes a directory from Box storage
969
925
  *
@@ -1012,17 +968,26 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1012
968
  }
1013
969
  // Check if folder is empty if not recursive
1014
970
  if (!recursive) {
1015
- const contents = await this._apiRequest(`/folders/${folderId}/items`, 'GET', null, {
1016
- 'limit': '1'
971
+ const contents = await this._client.folders.getFolderItems(folderId, {
972
+ queryParams: {
973
+ limit: 1
974
+ }
1017
975
  });
1018
976
  if (contents.entries.length > 0) {
1019
977
  throw new Error('Directory is not empty');
1020
978
  }
1021
979
  }
1022
- // Delete the folder
1023
- await this._apiRequest(`/folders/${folderId}`, 'DELETE', null, {
1024
- 'recursive': recursive ? 'true' : 'false'
1025
- });
980
+ // Delete the folder using SDK
981
+ if (recursive) {
982
+ await this._client.folders.deleteFolderById(folderId, {
983
+ queryParams: {
984
+ recursive: true
985
+ }
986
+ });
987
+ }
988
+ else {
989
+ await this._client.folders.deleteFolderById(folderId);
990
+ }
1026
991
  return true;
1027
992
  }
1028
993
  catch (error) {
@@ -1066,14 +1031,19 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1066
1031
  */
1067
1032
  async GetObjectMetadata(objectName) {
1068
1033
  try {
1069
- const itemId = await this._getIdFromPath(objectName);
1070
- // Determine if it's a file or folder
1071
- const itemInfo = await this._apiRequest(`/items/${itemId}`);
1072
- const fullEndpoint = itemInfo.type === 'folder' ? `/folders/${itemId}` : `/files/${itemId}`;
1073
- // Get full metadata
1074
- const metadata = await this._apiRequest(fullEndpoint, 'GET', null, {
1075
- 'fields': 'id,name,type,size,content_type,modified_at,created_at,etag,sequence_id'
1076
- });
1034
+ const itemInfo = await this._getItemInfoFromPath(objectName);
1035
+ if (!itemInfo) {
1036
+ throw new Error(`Object not found: ${objectName}`);
1037
+ }
1038
+ // Get full metadata using the SDK based on type
1039
+ const options = {
1040
+ queryParams: {
1041
+ fields: ['id', 'name', 'type', 'size', 'content_type', 'modified_at', 'created_at', 'etag', 'sequence_id']
1042
+ }
1043
+ };
1044
+ const metadata = itemInfo.type === 'folder'
1045
+ ? await this._client.folders.getFolderById(itemInfo.id, options)
1046
+ : await this._client.files.getFileById(itemInfo.id, options);
1077
1047
  // Parse path to get parent path
1078
1048
  const parsedPath = this._parsePath(objectName);
1079
1049
  return this._convertToMetadata(metadata, parsedPath.parent);
@@ -1116,64 +1086,30 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1116
1086
  */
1117
1087
  async GetObject(objectName) {
1118
1088
  try {
1119
- // Extract directory path and filename
1120
- const lastSlashIndex = objectName.lastIndexOf('/');
1121
- const directoryPath = lastSlashIndex === -1 ? '' : objectName.substring(0, lastSlashIndex);
1122
- const fileName = lastSlashIndex === -1 ? objectName : objectName.substring(lastSlashIndex + 1);
1123
- // Find folder ID for the directory
1124
- try {
1125
- const folderId = await this._findFolderIdByPath(directoryPath);
1126
- // Use pagination to handle large folders
1127
- let file = null;
1128
- let offset = 0;
1129
- let hasMoreItems = true;
1130
- const LIMIT = 1000;
1131
- while (hasMoreItems && !file) {
1132
- const folderItems = await this._apiRequest(`/folders/${folderId}/items`, 'GET', null, {
1133
- 'fields': 'name,type,id,size,created_at,modified_at',
1134
- 'limit': `${LIMIT}`,
1135
- 'offset': `${offset}`
1136
- });
1137
- // Look for the file
1138
- file = folderItems.entries.find((item) => item.type === 'file' && item.name === fileName);
1139
- // If file is found, break out of pagination loop
1140
- if (file) {
1141
- break;
1142
- }
1143
- // Update pagination variables
1144
- offset += folderItems.entries.length;
1145
- // Check if we've processed all items
1146
- hasMoreItems = folderItems.entries.length === LIMIT && offset < folderItems.total_count;
1147
- }
1148
- if (file) {
1149
- console.log(`✅ File found: ${file.name} (${file.id})`);
1150
- // Use the file ID to get the content
1151
- const fileResponse = await this._apiRequest(`/files/${file.id}/content`, 'GET');
1152
- // If the API request returned a response object (for direct download)
1153
- if (fileResponse instanceof Response) {
1154
- const arrayBuffer = await fileResponse.arrayBuffer();
1155
- return Buffer.from(arrayBuffer);
1156
- }
1157
- else {
1158
- throw new Error('Unexpected response format when downloading file');
1159
- }
1160
- }
1161
- else {
1162
- console.log(`❌ File not found in directory`);
1163
- throw new Error(`File not found: ${fileName}`);
1164
- }
1089
+ // Get file ID using path resolution
1090
+ const fileId = await this._getIdFromPath(objectName);
1091
+ if (!fileId) {
1092
+ throw new Error(`File not found: ${objectName}`);
1165
1093
  }
1166
- catch (error) {
1167
- console.log(`❌ Error finding file: ${error}`);
1168
- throw error;
1094
+ console.log(`✅ File found with ID: ${fileId}`);
1095
+ // Use SDK to download file content as a stream
1096
+ const stream = await this._client.downloads.downloadFile(fileId);
1097
+ if (!stream) {
1098
+ throw new Error(`Failed to download file: ${objectName}`);
1169
1099
  }
1100
+ // Convert stream to buffer
1101
+ return new Promise((resolve, reject) => {
1102
+ const chunks = [];
1103
+ stream.on('data', (chunk) => chunks.push(chunk));
1104
+ stream.on('error', reject);
1105
+ stream.on('end', () => resolve(Buffer.concat(chunks)));
1106
+ });
1170
1107
  }
1171
1108
  catch (error) {
1172
1109
  console.error('Error getting object', { objectName, error });
1173
1110
  throw new Error(`Failed to get object: ${objectName}`);
1174
1111
  }
1175
1112
  }
1176
- ;
1177
1113
  /**
1178
1114
  * Uploads a file to Box storage
1179
1115
  *
@@ -1240,30 +1176,44 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1240
1176
  }
1241
1177
  }
1242
1178
  // Check if file already exists
1243
- const fileId = await this._getIdFromPath(objectName);
1244
- const formData = new FormData();
1245
- // Add file metadata
1246
- const fileMetadata = {
1247
- name: parsedPath.name,
1248
- parent: { id: parentId }
1249
- };
1250
- formData.append('attributes', JSON.stringify(fileMetadata));
1251
- // Create a file blob with the correct content type
1252
- const fileBlob = new Blob([data], { type: contentType || 'application/octet-stream' });
1253
- formData.append('file', fileBlob, parsedPath.name);
1254
- // Upload the file
1255
- const endpoint = fileId ? `/files/${fileId}/content` : '/files/content';
1256
- console.log(`Uploading file using endpoint: ${endpoint}`);
1179
+ let fileId = null;
1257
1180
  try {
1258
- await this._apiRequest(endpoint, 'POST', formData, {}, this._uploadApiUrl);
1259
- console.log(`✅ File uploaded successfully: ${objectName}`);
1181
+ fileId = await this._getIdFromPath(objectName);
1182
+ }
1183
+ catch (error) {
1184
+ // File doesn't exist, we'll upload as new
1185
+ }
1186
+ try {
1187
+ // Convert Buffer to Readable stream for the SDK
1188
+ const fileStream = stream_1.Readable.from(data);
1189
+ if (fileId) {
1190
+ // Update existing file (upload new version)
1191
+ await this._client.uploads.uploadFileVersion(fileId, {
1192
+ attributes: {
1193
+ name: parsedPath.name
1194
+ },
1195
+ file: fileStream
1196
+ });
1197
+ console.log(`✅ File updated successfully: ${objectName}`);
1198
+ }
1199
+ else {
1200
+ // Upload new file
1201
+ await this._client.uploads.uploadFile({
1202
+ attributes: {
1203
+ name: parsedPath.name,
1204
+ parent: { id: parentId }
1205
+ },
1206
+ file: fileStream
1207
+ });
1208
+ console.log(`✅ File uploaded successfully: ${objectName}`);
1209
+ }
1260
1210
  return true;
1261
1211
  }
1262
1212
  catch (uploadError) {
1263
- console.error(`Error uploading file: ${uploadError.message}`);
1264
- if (uploadError.message && uploadError.message.includes('item_name_in_use')) {
1213
+ console.error(`Error uploading file:`, uploadError);
1214
+ if (uploadError.statusCode === 409) {
1265
1215
  console.log(`File already exists (conflict): ${objectName}`);
1266
- return false;
1216
+ return true;
1267
1217
  }
1268
1218
  return false;
1269
1219
  }
@@ -1307,8 +1257,11 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1307
1257
  async CopyObject(sourceObjectName, destinationObjectName) {
1308
1258
  try {
1309
1259
  // Get source info
1310
- const sourceId = await this._getIdFromPath(sourceObjectName);
1311
- const sourceInfo = await this._apiRequest(`/items/${sourceId}`);
1260
+ const sourceInfo = await this._getItemInfoFromPath(sourceObjectName);
1261
+ if (!sourceInfo) {
1262
+ console.log(`Source item not found: ${sourceObjectName}`);
1263
+ return false;
1264
+ }
1312
1265
  if (sourceInfo.type !== 'file') {
1313
1266
  throw new Error('Only files can be copied with CopyObject');
1314
1267
  }
@@ -1325,8 +1278,8 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1325
1278
  destParentId = await this._getIdFromPath(destPath.parent);
1326
1279
  }
1327
1280
  }
1328
- // Copy the file
1329
- await this._apiRequest(`/files/${sourceId}/copy`, 'POST', {
1281
+ // Copy the file using SDK
1282
+ await this._client.files.copyFile(sourceInfo.id, {
1330
1283
  parent: { id: destParentId },
1331
1284
  name: destPath.name
1332
1285
  });
@@ -1408,9 +1361,13 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1408
1361
  try {
1409
1362
  const folderId = await this._findFolderIdByPath(normalizedPath);
1410
1363
  console.log(`✅ Directory ${normalizedPath} exists with ID: ${folderId}`);
1411
- // Make a direct call to verify it's a folder
1364
+ // Make a direct call to verify it's a folder using SDK
1412
1365
  try {
1413
- const folderInfo = await this._apiRequest(`/folders/${folderId}`, 'GET');
1366
+ const folderInfo = await this._client.folders.getFolderById(folderId, {
1367
+ queryParams: {
1368
+ fields: ['type']
1369
+ }
1370
+ });
1414
1371
  return folderInfo.type === 'folder';
1415
1372
  }
1416
1373
  catch (error) {
@@ -1461,13 +1418,15 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1461
1418
  let hasMoreItems = true;
1462
1419
  const LIMIT = 1000;
1463
1420
  while (hasMoreItems && !folder) {
1464
- const items = await this._apiRequest(`/folders/${currentFolderId}/items`, 'GET', null, {
1465
- 'fields': 'name,type,id',
1466
- 'limit': `${LIMIT}`,
1467
- 'offset': `${offset}`
1421
+ const items = await this._client.folders.getFolderItems(currentFolderId, {
1422
+ queryParams: {
1423
+ fields: ['name', 'type', 'id'],
1424
+ limit: LIMIT,
1425
+ offset: offset
1426
+ }
1468
1427
  });
1469
1428
  // Filter to only folders
1470
- const folders = items.entries.filter((item) => item.type === 'folder');
1429
+ const folders = items.entries?.filter((item) => item.type === 'folder') || [];
1471
1430
  // Look for the target folder
1472
1431
  folder = folders.find((item) => item.name === segment);
1473
1432
  // If folder is found, break out of pagination loop
@@ -1475,11 +1434,11 @@ let BoxFileStorage = class BoxFileStorage extends FileStorageBase_1.FileStorageB
1475
1434
  break;
1476
1435
  }
1477
1436
  // Update pagination variables
1478
- offset += items.entries.length;
1437
+ offset += items.entries?.length || 0;
1479
1438
  // Check if we've processed all items
1480
- hasMoreItems = items.entries.length === LIMIT && offset < items.total_count;
1439
+ hasMoreItems = (items.entries?.length || 0) === LIMIT && offset < (items.totalCount || 0);
1481
1440
  }
1482
- if (!folder) {
1441
+ if (!folder || !folder.id) {
1483
1442
  throw new Error(`Folder not found: ${segment}`);
1484
1443
  }
1485
1444
  currentFolderId = folder.id;