@unboundcx/sdk 1.2.0 → 1.2.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/services/storage.js +281 -310
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -3,6 +3,193 @@ export class StorageService {
3
3
  this.sdk = sdk;
4
4
  }
5
5
 
6
+ // Private helper method to detect content type from filename
7
+ _getContentType(fileName) {
8
+ if (!fileName) return 'application/octet-stream';
9
+
10
+ try {
11
+ // Try to use mime-types package if available
12
+ const mime = import('mime-types');
13
+ return mime.lookup ? mime.lookup(fileName) || 'application/octet-stream' : this._getFallbackContentType(fileName);
14
+ } catch (error) {
15
+ return this._getFallbackContentType(fileName);
16
+ }
17
+ }
18
+
19
+ // Private fallback content type detection
20
+ _getFallbackContentType(fileName) {
21
+ const ext = fileName.split('.').pop().toLowerCase();
22
+ const commonTypes = {
23
+ // Documents
24
+ pdf: 'application/pdf',
25
+ doc: 'application/msword',
26
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
27
+ xls: 'application/vnd.ms-excel',
28
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
29
+ ppt: 'application/vnd.ms-powerpoint',
30
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
31
+ odt: 'application/vnd.oasis.opendocument.text',
32
+ ods: 'application/vnd.oasis.opendocument.spreadsheet',
33
+ odp: 'application/vnd.oasis.opendocument.presentation',
34
+ rtf: 'application/rtf',
35
+ // Images
36
+ jpg: 'image/jpeg',
37
+ jpeg: 'image/jpeg',
38
+ png: 'image/png',
39
+ gif: 'image/gif',
40
+ bmp: 'image/bmp',
41
+ webp: 'image/webp',
42
+ svg: 'image/svg+xml',
43
+ ico: 'image/x-icon',
44
+ tiff: 'image/tiff',
45
+ tif: 'image/tiff',
46
+ psd: 'image/vnd.adobe.photoshop',
47
+ raw: 'image/x-canon-cr2',
48
+ heic: 'image/heic',
49
+ heif: 'image/heif',
50
+ // Audio
51
+ mp3: 'audio/mpeg',
52
+ wav: 'audio/wav',
53
+ flac: 'audio/flac',
54
+ aac: 'audio/aac',
55
+ ogg: 'audio/ogg',
56
+ wma: 'audio/x-ms-wma',
57
+ m4a: 'audio/mp4',
58
+ opus: 'audio/opus',
59
+ // Video
60
+ mp4: 'video/mp4',
61
+ avi: 'video/avi',
62
+ mkv: 'video/mkv',
63
+ mov: 'video/quicktime',
64
+ wmv: 'video/x-ms-wmv',
65
+ flv: 'video/x-flv',
66
+ webm: 'video/webm',
67
+ // Archives
68
+ zip: 'application/zip',
69
+ rar: 'application/x-rar-compressed',
70
+ '7z': 'application/x-7z-compressed',
71
+ tar: 'application/x-tar',
72
+ gz: 'application/gzip',
73
+ // Text
74
+ txt: 'text/plain',
75
+ csv: 'text/csv',
76
+ json: 'application/json',
77
+ xml: 'application/xml',
78
+ html: 'text/html',
79
+ css: 'text/css',
80
+ js: 'application/javascript',
81
+ // Other
82
+ sql: 'application/sql',
83
+ log: 'text/plain',
84
+ };
85
+ return commonTypes[ext] || 'application/octet-stream';
86
+ }
87
+
88
+ // Private helper to create FormData for Node.js environment
89
+ _createNodeFormData(file, fileName, formFields) {
90
+ const boundary = `----formdata-${Date.now()}-${Math.random().toString(36)}`;
91
+ const CRLF = '\r\n';
92
+ let body = '';
93
+
94
+ // Add file field with proper MIME type detection
95
+ const contentType = this._getContentType(fileName);
96
+
97
+ body += `--${boundary}${CRLF}`;
98
+ body += `Content-Disposition: form-data; name="files"; filename="${fileName || 'file'}"${CRLF}`;
99
+ body += `Content-Type: ${contentType}${CRLF}${CRLF}`;
100
+
101
+ // Convert to buffers and combine
102
+ const headerBuffer = (typeof Buffer !== 'undefined') ? Buffer.from(body, 'utf8') : new TextEncoder().encode(body);
103
+ const fileBuffer = (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(file)) ? file : ((typeof Buffer !== 'undefined') ? Buffer.from(file) : new TextEncoder().encode(file));
104
+
105
+ // Add form fields
106
+ let fieldsBuffer = (typeof Buffer !== 'undefined') ? Buffer.alloc(0) : new Uint8Array(0);
107
+ for (const [name, value] of formFields) {
108
+ const fieldData = `${CRLF}--${boundary}${CRLF}Content-Disposition: form-data; name="${name}"${CRLF}${CRLF}${value}`;
109
+ const fieldDataBuffer = (typeof Buffer !== 'undefined') ? Buffer.from(fieldData, 'utf8') : new TextEncoder().encode(fieldData);
110
+ if (typeof Buffer !== 'undefined') {
111
+ fieldsBuffer = Buffer.concat([fieldsBuffer, fieldDataBuffer]);
112
+ } else {
113
+ const newBuffer = new Uint8Array(fieldsBuffer.length + fieldDataBuffer.length);
114
+ newBuffer.set(fieldsBuffer);
115
+ newBuffer.set(fieldDataBuffer, fieldsBuffer.length);
116
+ fieldsBuffer = newBuffer;
117
+ }
118
+ }
119
+
120
+ // Final boundary
121
+ const endBoundary = (typeof Buffer !== 'undefined') ? Buffer.from(`${CRLF}--${boundary}--${CRLF}`, 'utf8') : new TextEncoder().encode(`${CRLF}--${boundary}--${CRLF}`);
122
+
123
+ // Combine all parts
124
+ let formData;
125
+ if (typeof Buffer !== 'undefined') {
126
+ formData = Buffer.concat([headerBuffer, fileBuffer, fieldsBuffer, endBoundary]);
127
+ } else {
128
+ const totalLength = headerBuffer.length + fileBuffer.length + fieldsBuffer.length + endBoundary.length;
129
+ formData = new Uint8Array(totalLength);
130
+ let offset = 0;
131
+ formData.set(headerBuffer, offset); offset += headerBuffer.length;
132
+ formData.set(fileBuffer, offset); offset += fileBuffer.length;
133
+ formData.set(fieldsBuffer, offset); offset += fieldsBuffer.length;
134
+ formData.set(endBoundary, offset);
135
+ }
136
+
137
+ return {
138
+ formData,
139
+ headers: {
140
+ 'content-type': `multipart/form-data; boundary=${boundary}`
141
+ }
142
+ };
143
+ }
144
+
145
+ // Private helper to create FormData for browser environment
146
+ _createBrowserFormData(file, fileName, formFields) {
147
+ const formData = new FormData();
148
+
149
+ // Add the file - handle both Buffer and File objects
150
+ if ((typeof Buffer !== 'undefined') && Buffer.isBuffer && Buffer.isBuffer(file)) {
151
+ const blob = new Blob([file]);
152
+ formData.append('files', blob, fileName || 'file');
153
+ } else if (file instanceof File) {
154
+ formData.append('files', file);
155
+ } else {
156
+ throw new Error('In browser environment, file must be a Buffer or File object');
157
+ }
158
+
159
+ // Add other parameters
160
+ for (const [name, value] of formFields) {
161
+ formData.append(name, value);
162
+ }
163
+
164
+ return {
165
+ formData,
166
+ headers: {} // Don't set Content-Type - let browser handle it automatically
167
+ };
168
+ }
169
+
170
+ // Shared upload logic
171
+ async _performUpload(file, fileName, formFields, endpoint = '/storage/upload') {
172
+ const isNode = typeof window === 'undefined';
173
+ let formData, headers;
174
+
175
+ if (isNode) {
176
+ const result = this._createNodeFormData(file, fileName, formFields);
177
+ formData = result.formData;
178
+ headers = result.headers;
179
+ } else {
180
+ const result = this._createBrowserFormData(file, fileName, formFields);
181
+ formData = result.formData;
182
+ headers = result.headers;
183
+ }
184
+
185
+ const params = {
186
+ body: formData,
187
+ headers,
188
+ };
189
+
190
+ return await this.sdk._fetch(endpoint, 'POST', params, true);
191
+ }
192
+
6
193
  async upload({
7
194
  classification = 'generic',
8
195
  folder,
@@ -42,197 +229,21 @@ export class StorageService {
42
229
  },
43
230
  );
44
231
 
45
- const isNode = typeof window === 'undefined';
46
- let formData;
47
- const headers = {};
48
-
49
- if (isNode) {
50
- // Node.js environment - use direct buffer approach
51
- // Create a simple body with the file buffer and metadata
52
- const boundary = `----formdata-${Date.now()}-${Math.random().toString(
53
- 36,
54
- )}`;
55
- const CRLF = '\r\n';
56
- let body = '';
57
-
58
- // Add file field with proper MIME type detection
59
- let contentType = 'application/octet-stream';
60
- if (fileName) {
61
- try {
62
- // Try to use mime-types package if available
63
- const mime = await import('mime-types');
64
- contentType = mime.lookup(fileName) || 'application/octet-stream';
65
- } catch (error) {
66
- // Fallback to basic extension mapping
67
- const ext = fileName.split('.').pop().toLowerCase();
68
- const commonTypes = {
69
- // Documents
70
- pdf: 'application/pdf',
71
- doc: 'application/msword',
72
- docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
73
- xls: 'application/vnd.ms-excel',
74
- xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
75
- ppt: 'application/vnd.ms-powerpoint',
76
- pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
77
- odt: 'application/vnd.oasis.opendocument.text',
78
- ods: 'application/vnd.oasis.opendocument.spreadsheet',
79
- odp: 'application/vnd.oasis.opendocument.presentation',
80
- rtf: 'application/rtf',
81
- // Images
82
- jpg: 'image/jpeg',
83
- jpeg: 'image/jpeg',
84
- png: 'image/png',
85
- gif: 'image/gif',
86
- bmp: 'image/bmp',
87
- webp: 'image/webp',
88
- svg: 'image/svg+xml',
89
- ico: 'image/x-icon',
90
- tiff: 'image/tiff',
91
- tif: 'image/tiff',
92
- psd: 'image/vnd.adobe.photoshop',
93
- raw: 'image/x-canon-cr2',
94
- heic: 'image/heic',
95
- heif: 'image/heif',
96
- // Audio
97
- mp3: 'audio/mpeg',
98
- wav: 'audio/wav',
99
- flac: 'audio/flac',
100
- aac: 'audio/aac',
101
- ogg: 'audio/ogg',
102
- wma: 'audio/x-ms-wma',
103
- m4a: 'audio/mp4',
104
- opus: 'audio/opus',
105
- // Video
106
- mp4: 'video/mp4',
107
- avi: 'video/avi',
108
- mkv: 'video/mkv',
109
- mov: 'video/quicktime',
110
- wmv: 'video/x-ms-wmv',
111
- flv: 'video/x-flv',
112
- webm: 'video/webm',
113
- // Archives
114
- zip: 'application/zip',
115
- rar: 'application/x-rar-compressed',
116
- '7z': 'application/x-7z-compressed',
117
- tar: 'application/x-tar',
118
- gz: 'application/gzip',
119
- // Text
120
- txt: 'text/plain',
121
- csv: 'text/csv',
122
- json: 'application/json',
123
- xml: 'application/xml',
124
- html: 'text/html',
125
- css: 'text/css',
126
- js: 'application/javascript',
127
- // Other
128
- sql: 'application/sql',
129
- log: 'text/plain',
130
- };
131
- contentType = commonTypes[ext] || 'application/octet-stream';
132
- }
133
- }
134
-
135
- body += `--${boundary}${CRLF}`;
136
- body += `Content-Disposition: form-data; name="files"; filename="${
137
- fileName || 'file'
138
- }"${CRLF}`;
139
- body += `Content-Type: ${contentType}${CRLF}${CRLF}`;
140
-
141
- // Add other form fields
142
- const formFields = [];
143
- if (classification) formFields.push(['classification', classification]);
144
- if (folder) formFields.push(['folder', folder]);
145
- if (isPublic !== undefined)
146
- formFields.push(['isPublic', isPublic.toString()]);
147
- if (country) formFields.push(['country', country]);
148
- if (expireAfter) formFields.push(['expireAfter', expireAfter]);
149
- if (relatedId) formFields.push(['relatedId', relatedId]);
150
- if (createAccessKey !== undefined)
151
- formFields.push(['createAccessKey', createAccessKey.toString()]);
152
- if (accessKeyExpiresIn)
153
- formFields.push(['accessKeyExpiresIn', accessKeyExpiresIn]);
154
-
155
- // Convert to buffers and combine
156
- const headerBuffer = (typeof Buffer !== 'undefined') ? Buffer.from(body, 'utf8') : new TextEncoder().encode(body);
157
- const fileBuffer = (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(file)) ? file : ((typeof Buffer !== 'undefined') ? Buffer.from(file) : new TextEncoder().encode(file));
158
-
159
- // Add form fields
160
- let fieldsBuffer = (typeof Buffer !== 'undefined') ? Buffer.alloc(0) : new Uint8Array(0);
161
- for (const [name, value] of formFields) {
162
- const fieldData = `${CRLF}--${boundary}${CRLF}Content-Disposition: form-data; name="${name}"${CRLF}${CRLF}${value}`;
163
- const fieldDataBuffer = (typeof Buffer !== 'undefined') ? Buffer.from(fieldData, 'utf8') : new TextEncoder().encode(fieldData);
164
- if (typeof Buffer !== 'undefined') {
165
- fieldsBuffer = Buffer.concat([fieldsBuffer, fieldDataBuffer]);
166
- } else {
167
- const newBuffer = new Uint8Array(fieldsBuffer.length + fieldDataBuffer.length);
168
- newBuffer.set(fieldsBuffer);
169
- newBuffer.set(fieldDataBuffer, fieldsBuffer.length);
170
- fieldsBuffer = newBuffer;
171
- }
172
- }
173
-
174
- // Final boundary
175
- const endBoundary = (typeof Buffer !== 'undefined') ? Buffer.from(`${CRLF}--${boundary}--${CRLF}`, 'utf8') : new TextEncoder().encode(`${CRLF}--${boundary}--${CRLF}`);
176
-
177
- // Combine all parts
178
- if (typeof Buffer !== 'undefined') {
179
- formData = Buffer.concat([headerBuffer, fileBuffer, fieldsBuffer, endBoundary]);
180
- } else {
181
- const totalLength = headerBuffer.length + fileBuffer.length + fieldsBuffer.length + endBoundary.length;
182
- formData = new Uint8Array(totalLength);
183
- let offset = 0;
184
- formData.set(headerBuffer, offset); offset += headerBuffer.length;
185
- formData.set(fileBuffer, offset); offset += fileBuffer.length;
186
- formData.set(fieldsBuffer, offset); offset += fieldsBuffer.length;
187
- formData.set(endBoundary, offset);
188
- }
189
-
190
- // Set proper Content-Type header
191
- headers['content-type'] = `multipart/form-data; boundary=${boundary}`;
192
- } else {
193
- // Browser environment - use native FormData
194
- formData = new FormData();
195
-
196
- // Add the file - handle both Buffer and File objects
197
- if ((typeof Buffer !== 'undefined') && Buffer.isBuffer && Buffer.isBuffer(file)) {
198
- const blob = new Blob([file]);
199
- formData.append('files', blob, fileName || 'file');
200
- } else if (file instanceof File) {
201
- formData.append('files', file);
202
- } else {
203
- throw new Error(
204
- 'In browser environment, file must be a Buffer or File object',
205
- );
206
- }
207
-
208
- // Add other parameters
209
- if (classification) formData.append('classification', classification);
210
- if (folder) formData.append('folder', folder);
211
- if (isPublic !== undefined)
212
- formData.append('isPublic', isPublic.toString());
213
- if (country) formData.append('country', country);
214
- if (expireAfter) formData.append('expireAfter', expireAfter);
215
- if (relatedId) formData.append('relatedId', relatedId);
216
- if (createAccessKey !== undefined)
217
- formData.append('createAccessKey', createAccessKey.toString());
218
- if (accessKeyExpiresIn)
219
- formData.append('accessKeyExpiresIn', accessKeyExpiresIn);
220
-
221
- // Don't set Content-Type - let browser handle it automatically
222
- }
223
-
224
- const params = {
225
- body: formData,
226
- headers,
227
- };
228
-
229
- const result = await this.sdk._fetch(
230
- '/storage/upload',
231
- 'POST',
232
- params,
233
- true,
234
- );
235
- return result;
232
+ // Build form fields
233
+ const formFields = [];
234
+ if (classification) formFields.push(['classification', classification]);
235
+ if (folder) formFields.push(['folder', folder]);
236
+ if (isPublic !== undefined)
237
+ formFields.push(['isPublic', isPublic.toString()]);
238
+ if (country) formFields.push(['country', country]);
239
+ if (expireAfter) formFields.push(['expireAfter', expireAfter]);
240
+ if (relatedId) formFields.push(['relatedId', relatedId]);
241
+ if (createAccessKey !== undefined)
242
+ formFields.push(['createAccessKey', createAccessKey.toString()]);
243
+ if (accessKeyExpiresIn)
244
+ formFields.push(['accessKeyExpiresIn', accessKeyExpiresIn]);
245
+
246
+ return this._performUpload(file, fileName, formFields);
236
247
  }
237
248
 
238
249
  async uploadFiles(files, options = {}) {
@@ -243,57 +254,94 @@ export class StorageService {
243
254
  throw new Error('Files parameter is required');
244
255
  }
245
256
 
246
- // Handle different file input formats
247
- let formData;
257
+ // Handle different file input formats and convert to array for processing
258
+ let fileArray = [];
259
+
248
260
  if (typeof window !== 'undefined' && files instanceof FormData) {
249
- // Browser FormData
250
- formData = files;
261
+ throw new Error('FormData input not supported for uploadFiles. Use individual File objects or FileList.');
251
262
  } else if (typeof window !== 'undefined' && files instanceof FileList) {
252
263
  // Browser FileList
253
- formData = new FormData();
254
264
  for (let i = 0; i < files.length; i++) {
255
- formData.append('files', files[i]);
265
+ fileArray.push({ file: files[i], fileName: files[i].name });
256
266
  }
257
267
  } else if (Array.isArray(files)) {
258
268
  // File array (Node.js or Browser)
259
- formData = new FormData();
260
- files.forEach((file, index) => {
261
- formData.append('files', file);
262
- });
263
- } else if (typeof files === 'object' && files.path) {
264
- // Single file object (Node.js)
265
- formData = new FormData();
266
- formData.append('files', files);
269
+ fileArray = files.map((file, index) => ({
270
+ file,
271
+ fileName: file.name || `file-${index}`
272
+ }));
273
+ } else if (typeof files === 'object' && (files.path || files instanceof File)) {
274
+ // Single file object (Node.js) or File (Browser)
275
+ fileArray = [{ file: files, fileName: files.name || files.path || 'file' }];
267
276
  } else {
268
277
  throw new Error(
269
- 'Invalid files format. Expected FormData, FileList, File array, or File object',
278
+ 'Invalid files format. Expected FileList, File array, or File object',
270
279
  );
271
280
  }
272
281
 
273
- // Add optional parameters to FormData
274
- if (classification) formData.append('classification', classification);
275
- if (expireAfter) formData.append('expireAfter', expireAfter);
276
- if (isPublic !== undefined)
277
- formData.append('isPublic', isPublic.toString());
278
- if (metadata) formData.append('metadata', JSON.stringify(metadata));
282
+ // For multiple files, we need to handle them individually since our shared helper is for single files
283
+ // We could enhance the shared helper for multiple files, but for now let's use the existing approach
284
+ const isNode = typeof window === 'undefined';
285
+ let formData;
286
+ const headers = {};
287
+
288
+ if (isNode) {
289
+ // Node.js: Create multipart form data manually
290
+ const boundary = `----formdata-${Date.now()}-${Math.random().toString(36)}`;
291
+ const CRLF = '\r\n';
292
+ let body = '';
293
+
294
+ const bufferParts = [];
295
+
296
+ // Add all files
297
+ for (const { file, fileName } of fileArray) {
298
+ const contentType = this._getContentType(fileName);
299
+ const fileHeader = `--${boundary}${CRLF}Content-Disposition: form-data; name="files"; filename="${fileName}"${CRLF}Content-Type: ${contentType}${CRLF}${CRLF}`;
300
+
301
+ bufferParts.push(Buffer.from(fileHeader, 'utf8'));
302
+ bufferParts.push(Buffer.isBuffer(file) ? file : Buffer.from(file));
303
+ bufferParts.push(Buffer.from(CRLF, 'utf8'));
304
+ }
305
+
306
+ // Add form fields
307
+ const formFields = [];
308
+ if (classification) formFields.push(['classification', classification]);
309
+ if (expireAfter) formFields.push(['expireAfter', expireAfter]);
310
+ if (isPublic !== undefined) formFields.push(['isPublic', isPublic.toString()]);
311
+ if (metadata) formFields.push(['metadata', JSON.stringify(metadata)]);
312
+
313
+ for (const [name, value] of formFields) {
314
+ const fieldData = `--${boundary}${CRLF}Content-Disposition: form-data; name="${name}"${CRLF}${CRLF}${value}${CRLF}`;
315
+ bufferParts.push(Buffer.from(fieldData, 'utf8'));
316
+ }
317
+
318
+ // Final boundary
319
+ bufferParts.push(Buffer.from(`--${boundary}--${CRLF}`, 'utf8'));
320
+
321
+ formData = Buffer.concat(bufferParts);
322
+ headers['content-type'] = `multipart/form-data; boundary=${boundary}`;
323
+ } else {
324
+ // Browser: Use native FormData
325
+ formData = new FormData();
326
+
327
+ // Add all files
328
+ for (const { file, fileName } of fileArray) {
329
+ formData.append('files', file, fileName);
330
+ }
331
+
332
+ // Add optional parameters
333
+ if (classification) formData.append('classification', classification);
334
+ if (expireAfter) formData.append('expireAfter', expireAfter);
335
+ if (isPublic !== undefined) formData.append('isPublic', isPublic.toString());
336
+ if (metadata) formData.append('metadata', JSON.stringify(metadata));
337
+ }
279
338
 
280
339
  const params = {
281
340
  body: formData,
282
- headers: {
283
- // Let browser/Node.js set Content-Type with boundary for multipart/form-data
284
- },
341
+ headers,
285
342
  };
286
343
 
287
- // Remove Content-Type to let FormData set it properly
288
- delete params.headers['Content-Type'];
289
-
290
- const result = await this.sdk._fetch(
291
- '/storage/upload',
292
- 'POST',
293
- params,
294
- true,
295
- );
296
- return result;
344
+ return await this.sdk._fetch('/storage/upload', 'POST', params, true);
297
345
  }
298
346
 
299
347
  async getFile(storageId, download = false) {
@@ -379,91 +427,14 @@ export class StorageService {
379
427
  );
380
428
  }
381
429
 
382
- const isNode = typeof window === 'undefined';
383
- let formData;
384
- const headers = {};
385
-
386
- if (isNode) {
387
- // Node.js environment
388
- const boundary = `----formdata-${Date.now()}-${Math.random().toString(
389
- 36,
390
- )}`;
391
- const CRLF = '\r\n';
392
- let body = '';
393
-
394
- // Add file field
395
- let contentType = 'application/octet-stream';
396
- if (fileName) {
397
- const ext = fileName.split('.').pop().toLowerCase();
398
- const imageTypes = {
399
- jpg: 'image/jpeg',
400
- jpeg: 'image/jpeg',
401
- png: 'image/png',
402
- gif: 'image/gif',
403
- webp: 'image/webp',
404
- };
405
- contentType = imageTypes[ext] || 'image/jpeg';
406
- }
407
-
408
- body += `--${boundary}${CRLF}`;
409
- body += `Content-Disposition: form-data; name="files"; filename="${
410
- fileName || 'profile-image.jpg'
411
- }"${CRLF}`;
412
- body += `Content-Type: ${contentType}${CRLF}${CRLF}`;
413
-
414
- const headerBuffer = (typeof Buffer !== 'undefined') ? Buffer.from(body, 'utf8') : new TextEncoder().encode(body);
415
- const fileBuffer = (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(file)) ? file : ((typeof Buffer !== 'undefined') ? Buffer.from(file) : new TextEncoder().encode(file));
416
-
417
- // Add classification field
418
- const classificationField = `${CRLF}--${boundary}${CRLF}Content-Disposition: form-data; name="classification"${CRLF}${CRLF}${classification}`;
419
- const fieldsBuffer = (typeof Buffer !== 'undefined') ? Buffer.from(classificationField, 'utf8') : new TextEncoder().encode(classificationField);
420
-
421
- // Final boundary
422
- const endBoundary = (typeof Buffer !== 'undefined') ? Buffer.from(`${CRLF}--${boundary}--${CRLF}`, 'utf8') : new TextEncoder().encode(`${CRLF}--${boundary}--${CRLF}`);
423
-
424
- // Combine all parts
425
- if (typeof Buffer !== 'undefined') {
426
- formData = Buffer.concat([headerBuffer, fileBuffer, fieldsBuffer, endBoundary]);
427
- } else {
428
- const totalLength = headerBuffer.length + fileBuffer.length + fieldsBuffer.length + endBoundary.length;
429
- formData = new Uint8Array(totalLength);
430
- let offset = 0;
431
- formData.set(headerBuffer, offset); offset += headerBuffer.length;
432
- formData.set(fileBuffer, offset); offset += fileBuffer.length;
433
- formData.set(fieldsBuffer, offset); offset += fieldsBuffer.length;
434
- formData.set(endBoundary, offset);
435
- }
436
- headers['content-type'] = `multipart/form-data; boundary=${boundary}`;
437
- } else {
438
- // Browser environment
439
- formData = new FormData();
440
-
441
- if ((typeof Buffer !== 'undefined') && Buffer.isBuffer && Buffer.isBuffer(file)) {
442
- const blob = new Blob([file]);
443
- formData.append('files', blob, fileName || 'profile-image.jpg');
444
- } else if (file instanceof File) {
445
- formData.append('files', file);
446
- } else {
447
- throw new Error(
448
- 'In browser environment, file must be a Buffer or File object',
449
- );
450
- }
451
-
452
- formData.append('classification', classification);
453
- }
454
-
455
- const params = {
456
- body: formData,
457
- headers,
458
- };
459
-
460
- const result = await this.sdk._fetch(
461
- '/storage/upload-profile-image',
462
- 'POST',
463
- params,
464
- true,
465
- );
466
- return result;
430
+ // Use the same robust upload logic as the main upload method
431
+ return this.upload({
432
+ classification,
433
+ fileName: fileName || 'profile-image.jpg',
434
+ file,
435
+ isPublic: false,
436
+ country: 'US',
437
+ });
467
438
  }
468
439
 
469
440
  async getFileInfo(storageId) {