@unboundcx/sdk 1.1.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.
- package/package.json +1 -1
- package/services/login.js +32 -4
- package/services/storage.js +281 -310
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unboundcx/sdk",
|
|
3
|
-
"version": "1.
|
|
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",
|
package/services/login.js
CHANGED
|
@@ -62,21 +62,49 @@ export class LoginService {
|
|
|
62
62
|
return validation;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
async changePassword(
|
|
65
|
+
async changePassword(newPassword) {
|
|
66
66
|
this.sdk.validateParams(
|
|
67
|
-
{
|
|
67
|
+
{ newPassword },
|
|
68
68
|
{
|
|
69
|
-
currentPassword: { type: 'string', required: true },
|
|
70
69
|
newPassword: { type: 'string', required: true },
|
|
71
70
|
},
|
|
72
71
|
);
|
|
73
72
|
|
|
74
73
|
const options = {
|
|
75
|
-
body: {
|
|
74
|
+
body: { password: newPassword },
|
|
76
75
|
};
|
|
77
76
|
|
|
78
77
|
const result = await this.sdk._fetch(
|
|
79
78
|
'/login/changePassword',
|
|
79
|
+
'PUT',
|
|
80
|
+
options,
|
|
81
|
+
);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getPasswordRequirements() {
|
|
86
|
+
const result = await this.sdk._fetch(
|
|
87
|
+
'/login/passwordRequirements',
|
|
88
|
+
'GET',
|
|
89
|
+
{},
|
|
90
|
+
);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async validatePasswordStrength(password) {
|
|
95
|
+
this.sdk.validateParams(
|
|
96
|
+
{ password },
|
|
97
|
+
{
|
|
98
|
+
password: { type: 'string', required: true },
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const options = {
|
|
103
|
+
body: { password },
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const result = await this.sdk._fetch(
|
|
107
|
+
'/login/validatePasswordStrength',
|
|
80
108
|
'POST',
|
|
81
109
|
options,
|
|
82
110
|
);
|
package/services/storage.js
CHANGED
|
@@ -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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
});
|
|
263
|
-
} else if (typeof files === 'object' && files.path) {
|
|
264
|
-
// Single file object (Node.js)
|
|
265
|
-
|
|
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
|
|
278
|
+
'Invalid files format. Expected FileList, File array, or File object',
|
|
270
279
|
);
|
|
271
280
|
}
|
|
272
281
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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) {
|