@proveanything/smartlinks 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -363,21 +363,36 @@ await asset.remove({
363
363
 
364
364
  ```json
365
365
  {
366
- "name": "Screenshot 2025-09-15 at 15.21.14",
366
+ "id": "aBcDeFgHiJkLmNoPqRsT",
367
+ "collectionId": "acme-demo",
368
+ "site": "acme-demo",
369
+ "productId": null,
370
+ "proofId": null,
371
+ "app": null,
372
+ "name": "product-hero-shot",
373
+ "cleanName": "product-hero-shot",
367
374
  "assetType": "Image",
375
+ "fileType": "png",
368
376
  "type": "png",
369
- "collectionId": "ChaseAtlantic",
370
- "url": "https://cdn.smartlinks.app/sites%2FChaseAtlantic%2Fimages%2F2025%2F9%2FScreenshot%202025-09-15%20at%2015%2C21%2C14-1757946214537.png",
371
- "createdAt": "2005-10-10T23:15:03",
372
- "hash": "fb98140a6b41ee69b824f29cc8b6795444246f871e4ab2379528b34a4d16284e",
373
- "thumbnails": {
374
- "x100": "https://cdn.smartlinks.app/..._100x100.png",
375
- "x200": "https://cdn.smartlinks.app/..._200x200.png",
376
- "x512": "https://cdn.smartlinks.app/..._512x512.png"
377
- },
378
- "id": "7k1cGErrlmQ94J8yDlVj",
379
- "site": "ChaseAtlantic",
380
- "cleanName": "Screenshot 2025-09-15 at 15.21"
377
+ "url": "https://cdn.smartlinks.app/sites%2Facme-demo%2Fimages%2F2025%2F1%2Fproduct-hero-shot-1234567890123.png",
378
+ "thumbnail": "https://cdn.smartlinks.app/sites%2Facme-demo%2Fimages%2F2025%2F1%2Fproduct-hero-shot-1234567890123_thumb.webp",
379
+ "mimeType": "image/png",
380
+ "contentType": "image/png",
381
+ "size": 204800,
382
+ "width": 1440,
383
+ "height": 900,
384
+ "hash": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
385
+ "labels": [],
386
+ "metadata": {},
387
+ "versions": [],
388
+ "uploadedBy": "uid_mock0001",
389
+ "uploaderContactId": null,
390
+ "uploadTokenId": null,
391
+ "uploaderIp": null,
392
+ "status": "active",
393
+ "createdAt": "2025-01-15T10:00:00Z",
394
+ "updatedAt": "2025-01-15T10:00:00Z",
395
+ "deletedAt": null
381
396
  }
382
397
  ```
383
398
 
@@ -1,4 +1,4 @@
1
- import { Asset, AssetResponse, UploadAssetOptions, UploadFromUrlOptions, ListAssetsOptions, GetAssetOptions, RemoveAssetOptions } from "../types/asset";
1
+ import { Asset, AssetResponse, UploadAssetOptions, UploadFromUrlOptions, ListAssetsOptions, GetAssetOptions, RemoveAssetOptions, AdminListAssetsOptions, AdminListAssetsResponse, UpdateAssetOptions, ReplaceAssetFileOptions, DeleteAssetOptions, BulkDeleteAssetsOptions, RequestUploadTokenOptions, UploadTokenResponse, PublicTokenUploadOptions } from "../types/asset";
2
2
  export declare namespace asset {
3
3
  /**
4
4
  * Error type for asset uploads
@@ -73,4 +73,63 @@ export declare namespace asset {
73
73
  * Remove an asset by id within a scope (admin)
74
74
  */
75
75
  function remove(options: RemoveAssetOptions): Promise<void>;
76
+ /**
77
+ * List assets for a collection with full filtering options.
78
+ */
79
+ function listAdmin(options: AdminListAssetsOptions): Promise<AdminListAssetsResponse>;
80
+ /**
81
+ * Get a single asset by ID (admin).
82
+ */
83
+ function getAdmin(collectionId: string, assetId: string): Promise<Asset>;
84
+ /**
85
+ * Update asset metadata (admin). Use `replaceFile` to swap the file.
86
+ */
87
+ function updateAdmin(options: UpdateAssetOptions): Promise<Asset>;
88
+ /**
89
+ * Replace the file of an existing asset. The previous file URL is snapshotted
90
+ * into `versions[]` on the asset.
91
+ */
92
+ function replaceFile(options: ReplaceAssetFileOptions): Promise<Asset>;
93
+ /**
94
+ * Soft-delete an asset. Schedules CDN purge after `graceDays` (default 30).
95
+ * Recoverable via `restoreAdmin` until purge runs.
96
+ */
97
+ function deleteAdmin(options: DeleteAssetOptions): Promise<{
98
+ deleted: true;
99
+ }>;
100
+ /**
101
+ * Restore a soft-deleted asset (clears `deletedAt`).
102
+ */
103
+ function restoreAdmin(collectionId: string, assetId: string): Promise<Asset>;
104
+ /**
105
+ * Soft-delete multiple assets in one request.
106
+ */
107
+ function bulkDelete(options: BulkDeleteAssetsOptions): Promise<{
108
+ deleted: number;
109
+ }>;
110
+ /**
111
+ * Request a single-use upload token for a public (unauthenticated) upload.
112
+ * The token encodes the upload policy (allowed types, max size, review requirement).
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const { tokenId, policy } = await asset.requestUploadToken({
117
+ * collectionId: 'my-collection',
118
+ * appId: 'user-gallery',
119
+ * contactId: contact.id,
120
+ * })
121
+ * const uploaded = await asset.publicUploadWithToken({
122
+ * collectionId: 'my-collection',
123
+ * tokenId,
124
+ * file: selectedFile,
125
+ * })
126
+ * ```
127
+ */
128
+ function requestUploadToken(options: RequestUploadTokenOptions): Promise<UploadTokenResponse>;
129
+ /**
130
+ * Upload a file using a single-use upload token (no admin auth required).
131
+ * Assets are created with `status: 'pending_review'` when the token policy
132
+ * has `reviewRequired: true`.
133
+ */
134
+ function publicUploadWithToken(options: PublicTokenUploadOptions): Promise<Asset>;
76
135
  }
package/dist/api/asset.js CHANGED
@@ -1,4 +1,15 @@
1
- import { request, post, del, getApiHeaders, isProxyEnabled, proxyUploadFormData } from "../http";
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { request, post, put, del, getApiHeaders, isProxyEnabled, proxyUploadFormData } from "../http";
2
13
  export var asset;
3
14
  (function (asset) {
4
15
  /**
@@ -76,7 +87,7 @@ export var asset;
76
87
  const code = mapStatusToUploadErrorCode(status, errBody === null || errBody === void 0 ? void 0 : errBody.code);
77
88
  reject(new AssetUploadError((errBody === null || errBody === void 0 ? void 0 : errBody.message) || `Upload failed (${status})`, code, errBody));
78
89
  }
79
- catch (_a) {
90
+ catch (_b) {
80
91
  const code = mapStatusToUploadErrorCode(status);
81
92
  reject(new AssetUploadError(`Asset upload failed with status ${status}`, code));
82
93
  }
@@ -277,4 +288,236 @@ export var asset;
277
288
  return del(path);
278
289
  }
279
290
  asset.remove = remove;
291
+ // ---------------------------------------------------------------------------
292
+ // Admin asset management — flat collection-scoped endpoints
293
+ // Base: /api/admin/collection/:collectionId/assets
294
+ // ---------------------------------------------------------------------------
295
+ /**
296
+ * List assets for a collection with full filtering options.
297
+ */
298
+ async function listAdmin(options) {
299
+ const params = new URLSearchParams();
300
+ if (options.productId)
301
+ params.set('productId', options.productId);
302
+ if (options.proofId)
303
+ params.set('proofId', options.proofId);
304
+ if (options.appId)
305
+ params.set('appId', options.appId);
306
+ if (options.assetType)
307
+ params.set('assetType', options.assetType);
308
+ if (options.labels)
309
+ params.set('labels', options.labels);
310
+ if (options.sort)
311
+ params.set('sort', options.sort);
312
+ if (options.order)
313
+ params.set('order', options.order);
314
+ if (typeof options.limit === 'number')
315
+ params.set('limit', String(options.limit));
316
+ if (typeof options.offset === 'number')
317
+ params.set('offset', String(options.offset));
318
+ const qs = params.toString();
319
+ const path = `/admin/collection/${encodeURIComponent(options.collectionId)}/assets${qs ? `?${qs}` : ''}`;
320
+ return request(path);
321
+ }
322
+ asset.listAdmin = listAdmin;
323
+ /**
324
+ * Get a single asset by ID (admin).
325
+ */
326
+ async function getAdmin(collectionId, assetId) {
327
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/assets/${encodeURIComponent(assetId)}`;
328
+ return request(path);
329
+ }
330
+ asset.getAdmin = getAdmin;
331
+ /**
332
+ * Update asset metadata (admin). Use `replaceFile` to swap the file.
333
+ */
334
+ async function updateAdmin(options) {
335
+ const path = `/admin/collection/${encodeURIComponent(options.collectionId)}/assets/${encodeURIComponent(options.assetId)}`;
336
+ const { collectionId: _c, assetId: _a } = options, body = __rest(options, ["collectionId", "assetId"]);
337
+ return put(path, body);
338
+ }
339
+ asset.updateAdmin = updateAdmin;
340
+ /**
341
+ * Replace the file of an existing asset. The previous file URL is snapshotted
342
+ * into `versions[]` on the asset.
343
+ */
344
+ async function replaceFile(options) {
345
+ const path = `/admin/collection/${encodeURIComponent(options.collectionId)}/assets/${encodeURIComponent(options.assetId)}/replace`;
346
+ const formData = new FormData();
347
+ formData.append('file', options.file);
348
+ if (options.onProgress && typeof window !== 'undefined' && !isProxyEnabled()) {
349
+ const url = (typeof window !== 'undefined' && window.SMARTLINKS_API_BASEURL)
350
+ ? window.SMARTLINKS_API_BASEURL + path
351
+ : path;
352
+ const headers = getApiHeaders ? getApiHeaders() : {};
353
+ return new Promise((resolve, reject) => {
354
+ const xhr = new XMLHttpRequest();
355
+ xhr.open('POST', url);
356
+ for (const [key, value] of Object.entries(headers))
357
+ xhr.setRequestHeader(key, value);
358
+ xhr.upload.onprogress = (event) => {
359
+ if (options.onProgress && event.lengthComputable) {
360
+ options.onProgress(Math.round((event.loaded / event.total) * 100));
361
+ }
362
+ };
363
+ xhr.onload = () => {
364
+ if (xhr.status >= 200 && xhr.status < 300) {
365
+ try {
366
+ resolve(JSON.parse(xhr.responseText));
367
+ }
368
+ catch (_b) {
369
+ reject(new AssetUploadError('Failed to parse server response', 'UNKNOWN'));
370
+ }
371
+ }
372
+ else {
373
+ try {
374
+ const e = JSON.parse(xhr.responseText);
375
+ reject(new AssetUploadError((e === null || e === void 0 ? void 0 : e.message) || `Replace failed (${xhr.status})`, mapStatusToUploadErrorCode(xhr.status, e === null || e === void 0 ? void 0 : e.code), e));
376
+ }
377
+ catch (_d) {
378
+ reject(new AssetUploadError(`Replace failed with status ${xhr.status}`, mapStatusToUploadErrorCode(xhr.status)));
379
+ }
380
+ }
381
+ };
382
+ xhr.onerror = () => reject(new AssetUploadError('Network error during file replace', 'NETWORK_ERROR'));
383
+ xhr.send(formData);
384
+ });
385
+ }
386
+ return post(path, formData);
387
+ }
388
+ asset.replaceFile = replaceFile;
389
+ /**
390
+ * Soft-delete an asset. Schedules CDN purge after `graceDays` (default 30).
391
+ * Recoverable via `restoreAdmin` until purge runs.
392
+ */
393
+ async function deleteAdmin(options) {
394
+ const params = new URLSearchParams();
395
+ if (typeof options.graceDays === 'number')
396
+ params.set('graceDays', String(options.graceDays));
397
+ const qs = params.toString();
398
+ const path = `/admin/collection/${encodeURIComponent(options.collectionId)}/assets/${encodeURIComponent(options.assetId)}${qs ? `?${qs}` : ''}`;
399
+ return del(path);
400
+ }
401
+ asset.deleteAdmin = deleteAdmin;
402
+ /**
403
+ * Restore a soft-deleted asset (clears `deletedAt`).
404
+ */
405
+ async function restoreAdmin(collectionId, assetId) {
406
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/assets/${encodeURIComponent(assetId)}/restore`;
407
+ return post(path, {});
408
+ }
409
+ asset.restoreAdmin = restoreAdmin;
410
+ /**
411
+ * Soft-delete multiple assets in one request.
412
+ */
413
+ async function bulkDelete(options) {
414
+ const path = `/admin/collection/${encodeURIComponent(options.collectionId)}/assets/bulk-delete`;
415
+ const body = { assetIds: options.assetIds };
416
+ if (typeof options.graceDays === 'number')
417
+ body.graceDays = options.graceDays;
418
+ return post(path, body);
419
+ }
420
+ asset.bulkDelete = bulkDelete;
421
+ // ---------------------------------------------------------------------------
422
+ // Public (token-based) uploads
423
+ // ---------------------------------------------------------------------------
424
+ /**
425
+ * Request a single-use upload token for a public (unauthenticated) upload.
426
+ * The token encodes the upload policy (allowed types, max size, review requirement).
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const { tokenId, policy } = await asset.requestUploadToken({
431
+ * collectionId: 'my-collection',
432
+ * appId: 'user-gallery',
433
+ * contactId: contact.id,
434
+ * })
435
+ * const uploaded = await asset.publicUploadWithToken({
436
+ * collectionId: 'my-collection',
437
+ * tokenId,
438
+ * file: selectedFile,
439
+ * })
440
+ * ```
441
+ */
442
+ async function requestUploadToken(options) {
443
+ const path = `/public/collection/${encodeURIComponent(options.collectionId)}/asset/token`;
444
+ const body = { appId: options.appId };
445
+ if (options.contactId)
446
+ body.contactId = options.contactId;
447
+ if (options.productId)
448
+ body.productId = options.productId;
449
+ if (options.proofId)
450
+ body.proofId = options.proofId;
451
+ return post(path, body);
452
+ }
453
+ asset.requestUploadToken = requestUploadToken;
454
+ /**
455
+ * Upload a file using a single-use upload token (no admin auth required).
456
+ * Assets are created with `status: 'pending_review'` when the token policy
457
+ * has `reviewRequired: true`.
458
+ */
459
+ async function publicUploadWithToken(options) {
460
+ const path = `/public/collection/${encodeURIComponent(options.collectionId)}/asset`;
461
+ const formData = new FormData();
462
+ formData.append('file', options.file);
463
+ if (options.name)
464
+ formData.append('name', options.name);
465
+ if (options.metadata)
466
+ formData.append('metadata', JSON.stringify(options.metadata));
467
+ if (options.onProgress && typeof window !== 'undefined' && !isProxyEnabled()) {
468
+ const baseUrl = (typeof window !== 'undefined' && window.SMARTLINKS_API_BASEURL)
469
+ ? window.SMARTLINKS_API_BASEURL + path
470
+ : path;
471
+ const headers = Object.assign(Object.assign({}, getApiHeaders()), { 'X-Upload-Token': options.tokenId });
472
+ return new Promise((resolve, reject) => {
473
+ const xhr = new XMLHttpRequest();
474
+ xhr.open('POST', baseUrl);
475
+ for (const [key, value] of Object.entries(headers))
476
+ xhr.setRequestHeader(key, value);
477
+ xhr.upload.onprogress = (event) => {
478
+ if (options.onProgress && event.lengthComputable) {
479
+ options.onProgress(Math.round((event.loaded / event.total) * 100));
480
+ }
481
+ };
482
+ xhr.onload = () => {
483
+ if (xhr.status >= 200 && xhr.status < 300) {
484
+ try {
485
+ resolve(JSON.parse(xhr.responseText));
486
+ }
487
+ catch (_b) {
488
+ reject(new AssetUploadError('Failed to parse server response', 'UNKNOWN'));
489
+ }
490
+ }
491
+ else {
492
+ try {
493
+ const e = JSON.parse(xhr.responseText);
494
+ reject(new AssetUploadError((e === null || e === void 0 ? void 0 : e.message) || `Upload failed (${xhr.status})`, mapStatusToUploadErrorCode(xhr.status, e === null || e === void 0 ? void 0 : e.code), e));
495
+ }
496
+ catch (_d) {
497
+ reject(new AssetUploadError(`Upload failed with status ${xhr.status}`, mapStatusToUploadErrorCode(xhr.status)));
498
+ }
499
+ }
500
+ };
501
+ xhr.onerror = () => reject(new AssetUploadError('Network error during public upload', 'NETWORK_ERROR'));
502
+ xhr.send(formData);
503
+ });
504
+ }
505
+ // Pass the token as a header via a custom fetch; post() doesn't accept extra headers,
506
+ // so we build the request manually using the same base URL resolution.
507
+ const baseUrl = (typeof window !== 'undefined' && window.SMARTLINKS_API_BASEURL)
508
+ ? window.SMARTLINKS_API_BASEURL + path
509
+ : path;
510
+ const headers = Object.assign(Object.assign({}, getApiHeaders()), { 'X-Upload-Token': options.tokenId });
511
+ const response = await fetch(baseUrl, { method: 'POST', headers, body: formData });
512
+ if (!response.ok) {
513
+ let errBody;
514
+ try {
515
+ errBody = await response.json();
516
+ }
517
+ catch ( /* ignore */_b) { /* ignore */ }
518
+ throw new AssetUploadError((errBody === null || errBody === void 0 ? void 0 : errBody.message) || `Public upload failed (${response.status})`, mapStatusToUploadErrorCode(response.status, errBody === null || errBody === void 0 ? void 0 : errBody.code), errBody);
519
+ }
520
+ return response.json();
521
+ }
522
+ asset.publicUploadWithToken = publicUploadWithToken;
280
523
  })(asset || (asset = {}));