oblien 1.2.4 → 1.2.6
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/README.md +11 -7
- package/browser.js +6 -0
- package/cdn.js +6 -0
- package/index.d.ts +300 -0
- package/index.js +6 -0
- package/package.json +13 -3
- package/src/browser/index.js +424 -0
- package/src/cdn/index.js +744 -0
package/src/cdn/index.js
ADDED
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oblien CDN Module
|
|
3
|
+
* Upload and manage files on Oblien CDN with automatic token management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class OblienCDN {
|
|
7
|
+
constructor(client, config = {}) {
|
|
8
|
+
if (!client) throw new Error('Oblien client is required');
|
|
9
|
+
this.client = client;
|
|
10
|
+
this.cdnURL = config.cdnURL || 'https://cdn.oblien.com/api';
|
|
11
|
+
|
|
12
|
+
// Token cache for reuse within expiration window
|
|
13
|
+
this._tokenCache = {
|
|
14
|
+
user: null,
|
|
15
|
+
admin: null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate a user CDN token (upload/process permissions)
|
|
21
|
+
* Tokens expire in 1 minute for security
|
|
22
|
+
*
|
|
23
|
+
* @returns {Promise<Object>} Token response with token, scope, and permissions
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const tokenData = await cdn.generateUserToken();
|
|
27
|
+
* // Returns:
|
|
28
|
+
* // {
|
|
29
|
+
* // success: true,
|
|
30
|
+
* // token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
31
|
+
* // scope: 'user',
|
|
32
|
+
* // permissions: ['upload', 'process'],
|
|
33
|
+
* // expiresIn: '1m'
|
|
34
|
+
* // }
|
|
35
|
+
*/
|
|
36
|
+
async generateUserToken() {
|
|
37
|
+
const response = await this.client.post('cdn/token');
|
|
38
|
+
|
|
39
|
+
// Cache token for reuse
|
|
40
|
+
if (response.success && response.token) {
|
|
41
|
+
this._tokenCache.user = {
|
|
42
|
+
token: response.token,
|
|
43
|
+
expiresAt: Date.now() + 50000 // 50 seconds (buffer before 1m expiry)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate an admin CDN token (full access permissions)
|
|
52
|
+
* Requires admin privileges on the API account
|
|
53
|
+
* Tokens expire in 1 minute for security
|
|
54
|
+
*
|
|
55
|
+
* @returns {Promise<Object>} Token response with token, scope, and permissions
|
|
56
|
+
* @throws {Error} If user doesn't have admin privileges
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const tokenData = await cdn.generateAdminToken();
|
|
60
|
+
* // Returns:
|
|
61
|
+
* // {
|
|
62
|
+
* // success: true,
|
|
63
|
+
* // token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
64
|
+
* // scope: 'admin',
|
|
65
|
+
* // permissions: ['upload', 'process', 'read', 'list', 'info', 'delete'],
|
|
66
|
+
* // expiresIn: '1m'
|
|
67
|
+
* // }
|
|
68
|
+
*/
|
|
69
|
+
async generateAdminToken() {
|
|
70
|
+
const response = await this.client.post('cdn/token/admin');
|
|
71
|
+
|
|
72
|
+
// Cache token for reuse
|
|
73
|
+
if (response.success && response.token) {
|
|
74
|
+
this._tokenCache.admin = {
|
|
75
|
+
token: response.token,
|
|
76
|
+
expiresAt: Date.now() + 50000 // 50 seconds (buffer before 1m expiry)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return response;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a valid token (user or admin)
|
|
85
|
+
* Automatically generates new token if cache is empty or expired
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
async _getToken(scope = 'user') {
|
|
89
|
+
const cached = this._tokenCache[scope];
|
|
90
|
+
|
|
91
|
+
// Use cached token if still valid
|
|
92
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
93
|
+
return cached.token;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Generate new token
|
|
97
|
+
const tokenData = scope === 'admin'
|
|
98
|
+
? await this.generateAdminToken()
|
|
99
|
+
: await this.generateUserToken();
|
|
100
|
+
|
|
101
|
+
return tokenData.token;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Normalize file input to buffer and filename
|
|
106
|
+
* Supports: Buffer, Base64, Multer files, Stream
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
_normalizeFile(file, providedFilename) {
|
|
110
|
+
let buffer;
|
|
111
|
+
let filename = providedFilename;
|
|
112
|
+
let mimetype;
|
|
113
|
+
|
|
114
|
+
// Handle Multer file object (from Express.js file uploads)
|
|
115
|
+
if (file && file.buffer && file.originalname) {
|
|
116
|
+
buffer = file.buffer;
|
|
117
|
+
filename = filename || file.originalname;
|
|
118
|
+
mimetype = file.mimetype;
|
|
119
|
+
}
|
|
120
|
+
// Handle Base64 string
|
|
121
|
+
else if (typeof file === 'string') {
|
|
122
|
+
// Check if it's a data URL (data:image/jpeg;base64,...)
|
|
123
|
+
const base64Match = file.match(/^data:([^;]+);base64,(.+)$/);
|
|
124
|
+
if (base64Match) {
|
|
125
|
+
mimetype = base64Match[1];
|
|
126
|
+
buffer = Buffer.from(base64Match[2], 'base64');
|
|
127
|
+
// Extract extension from mimetype if no filename
|
|
128
|
+
if (!filename) {
|
|
129
|
+
const ext = mimetype.split('/')[1] || 'bin';
|
|
130
|
+
filename = `file.${ext}`;
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
// Plain base64 string
|
|
134
|
+
buffer = Buffer.from(file, 'base64');
|
|
135
|
+
filename = filename || 'file.bin';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Handle Buffer or Uint8Array
|
|
139
|
+
else if (file instanceof Buffer || file instanceof Uint8Array) {
|
|
140
|
+
buffer = file;
|
|
141
|
+
filename = filename || 'file.bin';
|
|
142
|
+
}
|
|
143
|
+
// Handle Node.js ReadableStream
|
|
144
|
+
else if (file && typeof file.pipe === 'function') {
|
|
145
|
+
throw new Error('Streams are not directly supported. Please convert to Buffer first using stream.toArray() or similar.');
|
|
146
|
+
}
|
|
147
|
+
// Handle other types (File, Blob, etc.) - pass through
|
|
148
|
+
else {
|
|
149
|
+
return { file, filename: filename || 'file' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { buffer, filename, mimetype };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Upload a single file to CDN
|
|
157
|
+
* Automatically handles token generation if not provided
|
|
158
|
+
* Supports: Buffer, Base64 strings, Multer file objects
|
|
159
|
+
*
|
|
160
|
+
* @param {Buffer|string|Object} file - File to upload
|
|
161
|
+
* - Buffer: Raw file buffer
|
|
162
|
+
* - string: Base64 encoded file or data URL (data:image/jpeg;base64,...)
|
|
163
|
+
* - Object: Multer file object ({ buffer, originalname, mimetype })
|
|
164
|
+
* @param {Object} options - Upload options
|
|
165
|
+
* @param {string} options.token - CDN token (optional, will auto-generate if not provided)
|
|
166
|
+
* @param {string} options.filename - Custom filename (optional)
|
|
167
|
+
* @param {string} options.mimetype - File mimetype (optional)
|
|
168
|
+
* @param {string} options.scope - Token scope to use: 'user' or 'admin' (default: 'user')
|
|
169
|
+
* @returns {Promise<Object>} Upload result with file URL and metadata
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* // Upload from Buffer
|
|
173
|
+
* const fs = require('fs');
|
|
174
|
+
* const fileBuffer = fs.readFileSync('image.jpg');
|
|
175
|
+
* const result = await cdn.upload(fileBuffer, { filename: 'image.jpg' });
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* // Upload from Base64
|
|
179
|
+
* const base64 = 'data:image/jpeg;base64,/9j/4AAQSkZJRg...';
|
|
180
|
+
* const result = await cdn.upload(base64, { filename: 'image.jpg' });
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* // Upload from Base64 (plain)
|
|
184
|
+
* const base64 = '/9j/4AAQSkZJRg...';
|
|
185
|
+
* const result = await cdn.upload(base64, { filename: 'image.jpg' });
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // Upload from Multer (Express.js)
|
|
189
|
+
* app.post('/upload', upload.single('file'), async (req, res) => {
|
|
190
|
+
* const result = await cdn.upload(req.file);
|
|
191
|
+
* res.json(result);
|
|
192
|
+
* });
|
|
193
|
+
*
|
|
194
|
+
* // Returns:
|
|
195
|
+
* // {
|
|
196
|
+
* // success: true,
|
|
197
|
+
* // file: {
|
|
198
|
+
* // url: 'https://cdn.oblien.com/abc123/image.jpg',
|
|
199
|
+
* // filename: 'image.jpg',
|
|
200
|
+
* // size: 102400,
|
|
201
|
+
* // mimetype: 'image/jpeg'
|
|
202
|
+
* // }
|
|
203
|
+
* // }
|
|
204
|
+
*/
|
|
205
|
+
async upload(file, options = {}) {
|
|
206
|
+
const { token, filename, mimetype, scope = 'user' } = options;
|
|
207
|
+
|
|
208
|
+
// Get token (use provided or generate new one)
|
|
209
|
+
const cdnToken = token || await this._getToken(scope);
|
|
210
|
+
|
|
211
|
+
// Normalize file input
|
|
212
|
+
const normalized = this._normalizeFile(file, filename);
|
|
213
|
+
|
|
214
|
+
// Create FormData
|
|
215
|
+
const FormData = globalThis.FormData || (await import('form-data')).default;
|
|
216
|
+
const formData = new FormData();
|
|
217
|
+
|
|
218
|
+
// Add file to form data
|
|
219
|
+
if (normalized.buffer) {
|
|
220
|
+
const Blob = globalThis.Blob || (await import('buffer')).Blob;
|
|
221
|
+
const fileBlob = new Blob([normalized.buffer], {
|
|
222
|
+
type: mimetype || normalized.mimetype || 'application/octet-stream'
|
|
223
|
+
});
|
|
224
|
+
formData.append('file', fileBlob, normalized.filename);
|
|
225
|
+
} else {
|
|
226
|
+
formData.append('file', normalized.file, normalized.filename);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Upload to CDN
|
|
230
|
+
const response = await fetch(`${this.cdnURL}/`, {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
headers: {
|
|
233
|
+
'Authorization': `Bearer ${cdnToken}`
|
|
234
|
+
},
|
|
235
|
+
body: formData
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
const error = await response.json().catch(() => ({}));
|
|
240
|
+
throw new Error(error.message || `Upload failed: ${response.status}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return response.json();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Upload multiple files to CDN
|
|
248
|
+
* Automatically handles token generation if not provided
|
|
249
|
+
* Supports: Buffers, Base64 strings, Multer file objects
|
|
250
|
+
*
|
|
251
|
+
* @param {Array<Buffer|string|Object>} files - Array of files to upload
|
|
252
|
+
* - Buffer: Raw file buffers
|
|
253
|
+
* - string: Base64 encoded files
|
|
254
|
+
* - Object: Multer file objects
|
|
255
|
+
* @param {Object} options - Upload options
|
|
256
|
+
* @param {string} options.token - CDN token (optional, will auto-generate if not provided)
|
|
257
|
+
* @param {Array<string>} options.filenames - Array of custom filenames (optional)
|
|
258
|
+
* @param {string} options.scope - Token scope to use: 'user' or 'admin' (default: 'user')
|
|
259
|
+
* @returns {Promise<Object>} Upload result with array of file URLs and metadata
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* // Upload multiple buffers
|
|
263
|
+
* const files = [
|
|
264
|
+
* fs.readFileSync('image1.jpg'),
|
|
265
|
+
* fs.readFileSync('image2.jpg')
|
|
266
|
+
* ];
|
|
267
|
+
* const result = await cdn.uploadMultiple(files, {
|
|
268
|
+
* filenames: ['image1.jpg', 'image2.jpg']
|
|
269
|
+
* });
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* // Upload multiple base64 strings
|
|
273
|
+
* const files = [
|
|
274
|
+
* 'data:image/jpeg;base64,/9j/4AAQ...',
|
|
275
|
+
* 'data:image/png;base64,iVBORw0KGgo...'
|
|
276
|
+
* ];
|
|
277
|
+
* const result = await cdn.uploadMultiple(files);
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* // Upload from Multer (Express.js)
|
|
281
|
+
* app.post('/upload', upload.array('files'), async (req, res) => {
|
|
282
|
+
* const result = await cdn.uploadMultiple(req.files);
|
|
283
|
+
* res.json(result);
|
|
284
|
+
* });
|
|
285
|
+
*
|
|
286
|
+
* // Returns:
|
|
287
|
+
* // {
|
|
288
|
+
* // success: true,
|
|
289
|
+
* // files: [
|
|
290
|
+
* // { url: 'https://cdn.oblien.com/abc123/image1.jpg', ... },
|
|
291
|
+
* // { url: 'https://cdn.oblien.com/def456/image2.jpg', ... }
|
|
292
|
+
* // ]
|
|
293
|
+
* // }
|
|
294
|
+
*/
|
|
295
|
+
async uploadMultiple(files, options = {}) {
|
|
296
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
297
|
+
throw new Error('Files must be a non-empty array');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const { token, filenames = [], scope = 'user' } = options;
|
|
301
|
+
|
|
302
|
+
// Get token (use provided or generate new one)
|
|
303
|
+
const cdnToken = token || await this._getToken(scope);
|
|
304
|
+
|
|
305
|
+
// Create FormData
|
|
306
|
+
const FormData = globalThis.FormData || (await import('form-data')).default;
|
|
307
|
+
const formData = new FormData();
|
|
308
|
+
|
|
309
|
+
// Add all files
|
|
310
|
+
for (let i = 0; i < files.length; i++) {
|
|
311
|
+
const file = files[i];
|
|
312
|
+
const filename = filenames[i];
|
|
313
|
+
|
|
314
|
+
// Normalize file input
|
|
315
|
+
const normalized = this._normalizeFile(file, filename);
|
|
316
|
+
|
|
317
|
+
if (normalized.buffer) {
|
|
318
|
+
const Blob = globalThis.Blob || (await import('buffer')).Blob;
|
|
319
|
+
const fileBlob = new Blob([normalized.buffer], {
|
|
320
|
+
type: normalized.mimetype || 'application/octet-stream'
|
|
321
|
+
});
|
|
322
|
+
formData.append('files', fileBlob, normalized.filename);
|
|
323
|
+
} else {
|
|
324
|
+
formData.append('files', normalized.file, normalized.filename);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Upload to CDN
|
|
329
|
+
const response = await fetch(`${this.cdnURL}/multiple`, {
|
|
330
|
+
method: 'POST',
|
|
331
|
+
headers: {
|
|
332
|
+
'Authorization': `Bearer ${cdnToken}`
|
|
333
|
+
},
|
|
334
|
+
body: formData
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
if (!response.ok) {
|
|
338
|
+
const error = await response.json().catch(() => ({}));
|
|
339
|
+
throw new Error(error.message || `Upload failed: ${response.status}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return response.json();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Upload files from URLs (download and upload to CDN)
|
|
347
|
+
* CDN will download the images from URLs and process them
|
|
348
|
+
* Automatically handles token generation if not provided
|
|
349
|
+
*
|
|
350
|
+
* @param {Array<string>} urls - Array of image URLs to download and upload
|
|
351
|
+
* @param {Object} options - Upload options
|
|
352
|
+
* @param {string} options.token - CDN token (optional, will auto-generate if not provided)
|
|
353
|
+
* @param {string} options.scope - Token scope to use: 'user' or 'admin' (default: 'user')
|
|
354
|
+
* @returns {Promise<Object>} Upload result with processed file URLs
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* const result = await cdn.uploadFromUrls([
|
|
358
|
+
* 'https://example.com/image1.jpg',
|
|
359
|
+
* 'https://example.com/image2.png'
|
|
360
|
+
* ]);
|
|
361
|
+
*
|
|
362
|
+
* // Returns:
|
|
363
|
+
* // {
|
|
364
|
+
* // success: true,
|
|
365
|
+
* // files: [
|
|
366
|
+
* // {
|
|
367
|
+
* // url: 'https://cdn.oblien.com/abc123/image1.jpg',
|
|
368
|
+
* // originalUrl: 'https://example.com/image1.jpg',
|
|
369
|
+
* // filename: 'image1.jpg',
|
|
370
|
+
* // size: 102400,
|
|
371
|
+
* // mimetype: 'image/jpeg',
|
|
372
|
+
* // variants: { ... }
|
|
373
|
+
* // },
|
|
374
|
+
* // {
|
|
375
|
+
* // url: 'https://cdn.oblien.com/def456/image2.png',
|
|
376
|
+
* // originalUrl: 'https://example.com/image2.png',
|
|
377
|
+
* // filename: 'image2.png',
|
|
378
|
+
* // size: 204800,
|
|
379
|
+
* // mimetype: 'image/png',
|
|
380
|
+
* // variants: { ... }
|
|
381
|
+
* // }
|
|
382
|
+
* // ]
|
|
383
|
+
* // }
|
|
384
|
+
*/
|
|
385
|
+
async uploadFromUrls(urls, options = {}) {
|
|
386
|
+
if (!Array.isArray(urls) || urls.length === 0) {
|
|
387
|
+
throw new Error('URLs must be a non-empty array');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const { token, scope = 'user' } = options;
|
|
391
|
+
|
|
392
|
+
// Get token (use provided or generate new one)
|
|
393
|
+
const cdnToken = token || await this._getToken(scope);
|
|
394
|
+
|
|
395
|
+
// Upload from URLs
|
|
396
|
+
const response = await fetch(`${this.cdnURL}/process-urls`, {
|
|
397
|
+
method: 'POST',
|
|
398
|
+
headers: {
|
|
399
|
+
'Authorization': `Bearer ${cdnToken}`,
|
|
400
|
+
'Content-Type': 'application/json'
|
|
401
|
+
},
|
|
402
|
+
body: JSON.stringify({ urls })
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
if (!response.ok) {
|
|
406
|
+
const error = await response.json().catch(() => ({}));
|
|
407
|
+
throw new Error(error.message || `Upload from URLs failed: ${response.status}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return response.json();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* @deprecated Use uploadFromUrls() instead
|
|
415
|
+
* Process URLs (download and process images from URLs)
|
|
416
|
+
*/
|
|
417
|
+
async processUrls(urls, options = {}) {
|
|
418
|
+
console.warn('cdn.processUrls() is deprecated. Use cdn.uploadFromUrls() instead.');
|
|
419
|
+
return this.uploadFromUrls(urls, options);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Get file information (ADMIN ONLY)
|
|
424
|
+
* Requires admin token
|
|
425
|
+
*
|
|
426
|
+
* @param {string} filePath - File path on CDN
|
|
427
|
+
* @param {Object} options - Options
|
|
428
|
+
* @param {string} options.token - Admin CDN token (optional, will auto-generate if not provided)
|
|
429
|
+
* @returns {Promise<Object>} File information
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* const info = await cdn.getFileInfo('abc123/image.jpg');
|
|
433
|
+
*
|
|
434
|
+
* // Returns:
|
|
435
|
+
* // {
|
|
436
|
+
* // success: true,
|
|
437
|
+
* // file: {
|
|
438
|
+
* // path: 'abc123/image.jpg',
|
|
439
|
+
* // size: 102400,
|
|
440
|
+
* // mimetype: 'image/jpeg',
|
|
441
|
+
* // created: '2024-01-01T00:00:00.000Z'
|
|
442
|
+
* // }
|
|
443
|
+
* // }
|
|
444
|
+
*/
|
|
445
|
+
async getFileInfo(filePath, options = {}) {
|
|
446
|
+
if (!filePath) {
|
|
447
|
+
throw new Error('File path is required');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const { token } = options;
|
|
451
|
+
|
|
452
|
+
// Get admin token (use provided or generate new one)
|
|
453
|
+
const cdnToken = token || await this._getToken('admin');
|
|
454
|
+
|
|
455
|
+
// Get file info
|
|
456
|
+
const response = await fetch(`${this.cdnURL}/info/${filePath}`, {
|
|
457
|
+
method: 'GET',
|
|
458
|
+
headers: {
|
|
459
|
+
'Authorization': `Bearer ${cdnToken}`
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
if (!response.ok) {
|
|
464
|
+
const error = await response.json().catch(() => ({}));
|
|
465
|
+
throw new Error(error.message || `Failed to get file info: ${response.status}`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return response.json();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get available image variant options
|
|
473
|
+
*
|
|
474
|
+
* @param {Object} options - Options
|
|
475
|
+
* @param {string} options.token - CDN token (optional, will auto-generate if not provided)
|
|
476
|
+
* @returns {Promise<Object>} Available variants
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* const variants = await cdn.getVariants();
|
|
480
|
+
*
|
|
481
|
+
* // Returns:
|
|
482
|
+
* // {
|
|
483
|
+
* // variants: {
|
|
484
|
+
* // thumb: { width: 150, height: 150, fit: 'inside' },
|
|
485
|
+
* // medium: { width: 800, height: 600, fit: 'inside' },
|
|
486
|
+
* // blur: { width: 75, height: 75, fit: 'cover', blur: 5 }
|
|
487
|
+
* // }
|
|
488
|
+
* // }
|
|
489
|
+
*/
|
|
490
|
+
async getVariants(options = {}) {
|
|
491
|
+
const { token } = options;
|
|
492
|
+
|
|
493
|
+
// Get token (use provided or generate new one)
|
|
494
|
+
const cdnToken = token || await this._getToken('user');
|
|
495
|
+
|
|
496
|
+
const response = await fetch(`${this.cdnURL}/variants`, {
|
|
497
|
+
method: 'GET',
|
|
498
|
+
headers: {
|
|
499
|
+
'Authorization': `Bearer ${cdnToken}`
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
if (!response.ok) {
|
|
504
|
+
const error = await response.json().catch(() => ({}));
|
|
505
|
+
throw new Error(error.message || `Failed to get variants: ${response.status}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return response.json();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Get file size limits
|
|
513
|
+
*
|
|
514
|
+
* @param {Object} options - Options
|
|
515
|
+
* @param {string} options.token - CDN token (optional, will auto-generate if not provided)
|
|
516
|
+
* @returns {Promise<Object>} File size limits
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* const limits = await cdn.getLimits();
|
|
520
|
+
*
|
|
521
|
+
* // Returns:
|
|
522
|
+
* // {
|
|
523
|
+
* // limits: {
|
|
524
|
+
* // 'image/jpeg': 5242880,
|
|
525
|
+
* // 'image/png': 8388608,
|
|
526
|
+
* // 'default': 10485760
|
|
527
|
+
* // }
|
|
528
|
+
* // }
|
|
529
|
+
*/
|
|
530
|
+
async getLimits(options = {}) {
|
|
531
|
+
const { token } = options;
|
|
532
|
+
|
|
533
|
+
// Get token (use provided or generate new one)
|
|
534
|
+
const cdnToken = token || await this._getToken('user');
|
|
535
|
+
|
|
536
|
+
const response = await fetch(`${this.cdnURL}/limits`, {
|
|
537
|
+
method: 'GET',
|
|
538
|
+
headers: {
|
|
539
|
+
'Authorization': `Bearer ${cdnToken}`
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
if (!response.ok) {
|
|
544
|
+
const error = await response.json().catch(() => ({}));
|
|
545
|
+
throw new Error(error.message || `Failed to get limits: ${response.status}`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return response.json();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Clear token cache
|
|
553
|
+
* Useful when you want to force fresh token generation
|
|
554
|
+
*
|
|
555
|
+
* @param {string} scope - Scope to clear: 'user', 'admin', or 'all' (default: 'all')
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* cdn.clearTokenCache(); // Clear all
|
|
559
|
+
* cdn.clearTokenCache('user'); // Clear user token only
|
|
560
|
+
*/
|
|
561
|
+
clearTokenCache(scope = 'all') {
|
|
562
|
+
if (scope === 'all') {
|
|
563
|
+
this._tokenCache.user = null;
|
|
564
|
+
this._tokenCache.admin = null;
|
|
565
|
+
} else if (scope === 'user' || scope === 'admin') {
|
|
566
|
+
this._tokenCache[scope] = null;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ===== FILE MANAGEMENT ENDPOINTS =====
|
|
571
|
+
// These endpoints use session authentication (not CDN tokens)
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* List user's uploaded files
|
|
575
|
+
* Uses session authentication (requires logged in user)
|
|
576
|
+
*
|
|
577
|
+
* @param {Object} options - Query options
|
|
578
|
+
* @param {number} options.page - Page number (default: 1)
|
|
579
|
+
* @param {number} options.limit - Items per page (default: 50, max: 100)
|
|
580
|
+
* @param {boolean} options.includeDeleted - Include soft-deleted files (default: false)
|
|
581
|
+
* @param {string} options.uploadType - Filter by upload type: 'upload' or 'url'
|
|
582
|
+
* @param {string} options.sortBy - Sort column: 'created_at', 'size', 'filename' (default: 'created_at')
|
|
583
|
+
* @param {string} options.sortOrder - Sort order: 'ASC' or 'DESC' (default: 'DESC')
|
|
584
|
+
* @returns {Promise<Object>} Paginated list of files
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* const result = await cdn.listFiles({ page: 1, limit: 50 });
|
|
588
|
+
*
|
|
589
|
+
* // Returns:
|
|
590
|
+
* // {
|
|
591
|
+
* // success: true,
|
|
592
|
+
* // data: {
|
|
593
|
+
* // files: [
|
|
594
|
+
* // {
|
|
595
|
+
* // id: 123,
|
|
596
|
+
* // filename: 'image.jpg',
|
|
597
|
+
* // cdn_url: 'https://cdn.oblien.com/...',
|
|
598
|
+
* // size: 102400,
|
|
599
|
+
* // mime_type: 'image/jpeg',
|
|
600
|
+
* // variants: [...],
|
|
601
|
+
* // upload_type: 'upload',
|
|
602
|
+
* // created_at: '2024-01-01T00:00:00.000Z'
|
|
603
|
+
* // }
|
|
604
|
+
* // ],
|
|
605
|
+
* // pagination: {
|
|
606
|
+
* // page: 1,
|
|
607
|
+
* // limit: 50,
|
|
608
|
+
* // total: 150,
|
|
609
|
+
* // totalPages: 3,
|
|
610
|
+
* // hasMore: true
|
|
611
|
+
* // }
|
|
612
|
+
* // }
|
|
613
|
+
* // }
|
|
614
|
+
*/
|
|
615
|
+
async listFiles(options = {}) {
|
|
616
|
+
const {
|
|
617
|
+
page = 1,
|
|
618
|
+
limit = 50,
|
|
619
|
+
includeDeleted = false,
|
|
620
|
+
uploadType = null,
|
|
621
|
+
sortBy = 'created_at',
|
|
622
|
+
sortOrder = 'DESC'
|
|
623
|
+
} = options;
|
|
624
|
+
|
|
625
|
+
const queryParams = new URLSearchParams({
|
|
626
|
+
page: page.toString(),
|
|
627
|
+
limit: limit.toString(),
|
|
628
|
+
include_deleted: includeDeleted.toString(),
|
|
629
|
+
sort_by: sortBy,
|
|
630
|
+
sort_order: sortOrder
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
if (uploadType) {
|
|
634
|
+
queryParams.append('upload_type', uploadType);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return this.client.get(`cdn/files?${queryParams.toString()}`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Get user storage statistics
|
|
642
|
+
* Uses session authentication (requires logged in user)
|
|
643
|
+
*
|
|
644
|
+
* @returns {Promise<Object>} Storage statistics
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* const stats = await cdn.getStats();
|
|
648
|
+
*
|
|
649
|
+
* // Returns:
|
|
650
|
+
* // {
|
|
651
|
+
* // success: true,
|
|
652
|
+
* // data: {
|
|
653
|
+
* // totalFiles: 150,
|
|
654
|
+
* // totalSize: 157286400,
|
|
655
|
+
* // activeFiles: 145,
|
|
656
|
+
* // activeSize: 152166400,
|
|
657
|
+
* // uploadedFiles: 100,
|
|
658
|
+
* // urlFiles: 50
|
|
659
|
+
* // }
|
|
660
|
+
* // }
|
|
661
|
+
*/
|
|
662
|
+
async getStats() {
|
|
663
|
+
return this.client.get('cdn/stats');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Get file by ID
|
|
668
|
+
* Uses session authentication (requires logged in user)
|
|
669
|
+
*
|
|
670
|
+
* @param {number|string} fileId - File ID
|
|
671
|
+
* @returns {Promise<Object>} File details
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* const file = await cdn.getFile(123);
|
|
675
|
+
*
|
|
676
|
+
* // Returns:
|
|
677
|
+
* // {
|
|
678
|
+
* // success: true,
|
|
679
|
+
* // data: {
|
|
680
|
+
* // id: 123,
|
|
681
|
+
* // filename: 'image.jpg',
|
|
682
|
+
* // cdn_url: 'https://cdn.oblien.com/...',
|
|
683
|
+
* // size: 102400,
|
|
684
|
+
* // mime_type: 'image/jpeg',
|
|
685
|
+
* // variants: [...],
|
|
686
|
+
* // is_deleted: false,
|
|
687
|
+
* // created_at: '2024-01-01T00:00:00.000Z'
|
|
688
|
+
* // }
|
|
689
|
+
* // }
|
|
690
|
+
*/
|
|
691
|
+
async getFile(fileId) {
|
|
692
|
+
if (!fileId) {
|
|
693
|
+
throw new Error('File ID is required');
|
|
694
|
+
}
|
|
695
|
+
return this.client.get(`cdn/files/${fileId}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Delete file (soft delete)
|
|
700
|
+
* Uses session authentication (requires logged in user)
|
|
701
|
+
*
|
|
702
|
+
* @param {number|string} fileId - File ID to delete
|
|
703
|
+
* @returns {Promise<Object>} Delete result
|
|
704
|
+
*
|
|
705
|
+
* @example
|
|
706
|
+
* const result = await cdn.deleteFile(123);
|
|
707
|
+
*
|
|
708
|
+
* // Returns:
|
|
709
|
+
* // {
|
|
710
|
+
* // success: true,
|
|
711
|
+
* // message: 'File marked as deleted'
|
|
712
|
+
* // }
|
|
713
|
+
*/
|
|
714
|
+
async deleteFile(fileId) {
|
|
715
|
+
if (!fileId) {
|
|
716
|
+
throw new Error('File ID is required');
|
|
717
|
+
}
|
|
718
|
+
return this.client.delete(`cdn/files/${fileId}`);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Restore a soft-deleted file
|
|
723
|
+
* Uses session authentication (requires logged in user)
|
|
724
|
+
*
|
|
725
|
+
* @param {number|string} fileId - File ID to restore
|
|
726
|
+
* @returns {Promise<Object>} Restore result
|
|
727
|
+
*
|
|
728
|
+
* @example
|
|
729
|
+
* const result = await cdn.restoreFile(123);
|
|
730
|
+
*
|
|
731
|
+
* // Returns:
|
|
732
|
+
* // {
|
|
733
|
+
* // success: true,
|
|
734
|
+
* // message: 'File restored successfully'
|
|
735
|
+
* // }
|
|
736
|
+
*/
|
|
737
|
+
async restoreFile(fileId) {
|
|
738
|
+
if (!fileId) {
|
|
739
|
+
throw new Error('File ID is required');
|
|
740
|
+
}
|
|
741
|
+
return this.client.post(`cdn/files/${fileId}/restore`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|