@proveanything/smartlinks 1.3.35 → 1.3.38
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/dist/api/appConfiguration.d.ts +279 -2
- package/dist/api/appConfiguration.js +295 -23
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/proof.d.ts +19 -2
- package/dist/api/proof.js +23 -2
- package/dist/docs/API_SUMMARY.md +58 -4
- package/dist/docs/app-data-storage.md +223 -0
- package/dist/docs/liquid-templates.md +1 -1
- package/dist/docs/proof-claiming-methods.md +869 -0
- package/dist/http.js +37 -1
- package/dist/iframeResponder.js +51 -8
- package/dist/types/collection.d.ts +2 -0
- package/dist/types/product.d.ts +8 -0
- package/docs/API_SUMMARY.md +58 -4
- package/docs/app-data-storage.md +223 -0
- package/docs/liquid-templates.md +1 -1
- package/docs/proof-claiming-methods.md +869 -0
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -173,6 +173,13 @@ export function initializeApi(options) {
|
|
|
173
173
|
apiKey = options.apiKey;
|
|
174
174
|
bearerToken = options.bearerToken;
|
|
175
175
|
proxyMode = !!options.proxyMode;
|
|
176
|
+
console.log('[SmartLinks] initializeApi called', {
|
|
177
|
+
baseURL,
|
|
178
|
+
proxyMode,
|
|
179
|
+
hasApiKey: !!apiKey,
|
|
180
|
+
hasBearerToken: !!bearerToken,
|
|
181
|
+
isIframe: typeof window !== 'undefined' && window.parent !== window
|
|
182
|
+
});
|
|
176
183
|
// Auto-enable ngrok skip header if domain contains .ngrok.io and user did not explicitly set the flag.
|
|
177
184
|
// Infer ngrok usage from common domains (.ngrok.io or .ngrok-free.dev)
|
|
178
185
|
const inferredNgrok = /(\.ngrok\.io|\.ngrok-free\.dev)(\b|\/)/i.test(baseURL);
|
|
@@ -226,19 +233,33 @@ function ensureProxyListener() {
|
|
|
226
233
|
return;
|
|
227
234
|
window.addEventListener("message", (event) => {
|
|
228
235
|
const msg = event.data;
|
|
236
|
+
// Log all messages to help debug
|
|
237
|
+
if (msg && msg._smartlinksProxyResponse) {
|
|
238
|
+
console.log('[SmartLinks:Child] 📨 Received proxy response from parent', {
|
|
239
|
+
id: msg.id,
|
|
240
|
+
hasError: !!msg.error,
|
|
241
|
+
hasData: !!msg.data,
|
|
242
|
+
origin: event.origin
|
|
243
|
+
});
|
|
244
|
+
}
|
|
229
245
|
if (!msg || !msg._smartlinksProxyResponse || !msg.id)
|
|
230
246
|
return;
|
|
231
247
|
logDebug('[smartlinks] proxy:response', { id: msg.id, ok: !msg.error, keys: Object.keys(msg) });
|
|
232
248
|
const pending = proxyPending[msg.id];
|
|
233
249
|
if (pending) {
|
|
234
250
|
if (msg.error) {
|
|
251
|
+
console.error('[SmartLinks:Child] ❌ Proxy request failed', { id: msg.id, error: msg.error });
|
|
235
252
|
pending.reject(new Error(msg.error));
|
|
236
253
|
}
|
|
237
254
|
else {
|
|
255
|
+
console.log('[SmartLinks:Child] ✅ Proxy request succeeded', { id: msg.id });
|
|
238
256
|
pending.resolve(msg.data);
|
|
239
257
|
}
|
|
240
258
|
delete proxyPending[msg.id];
|
|
241
259
|
}
|
|
260
|
+
else {
|
|
261
|
+
console.warn('[SmartLinks:Child] ⚠️ Received response for unknown request', { id: msg.id });
|
|
262
|
+
}
|
|
242
263
|
});
|
|
243
264
|
window._smartlinksProxyListener = true;
|
|
244
265
|
}
|
|
@@ -266,10 +287,25 @@ async function proxyRequest(method, path, body, headers, options) {
|
|
|
266
287
|
headers,
|
|
267
288
|
options,
|
|
268
289
|
};
|
|
290
|
+
console.log('[SmartLinks:Child] 🚀 Sending proxy request to parent', {
|
|
291
|
+
id,
|
|
292
|
+
method,
|
|
293
|
+
path,
|
|
294
|
+
hasBody: !!body,
|
|
295
|
+
bodyType: body ? body.constructor.name : 'none',
|
|
296
|
+
headerCount: headers ? Object.keys(headers).length : 0
|
|
297
|
+
});
|
|
269
298
|
logDebug('[smartlinks] proxy:postMessage', { id, method, path, headers: headers ? redactHeaders(headers) : undefined, hasBody: !!body });
|
|
270
299
|
return new Promise((resolve, reject) => {
|
|
271
300
|
proxyPending[id] = { resolve, reject };
|
|
272
|
-
|
|
301
|
+
try {
|
|
302
|
+
window.parent.postMessage(msg, "*");
|
|
303
|
+
console.log('[SmartLinks:Child] ✅ postMessage sent successfully', { id });
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error('[SmartLinks:Child] ❌ postMessage failed', { id, error });
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
273
309
|
// Optionally: add a timeout here to reject if no response
|
|
274
310
|
});
|
|
275
311
|
}
|
package/dist/iframeResponder.js
CHANGED
|
@@ -41,14 +41,14 @@ export class IframeResponder {
|
|
|
41
41
|
this.resizeHandler = null;
|
|
42
42
|
this.appUrl = null;
|
|
43
43
|
this.resolveReady = null;
|
|
44
|
-
console.log('[IframeResponder] Constructor called', {
|
|
44
|
+
console.log('[IframeResponder:Parent] 🏗️ Constructor called', {
|
|
45
45
|
collectionId: options.collectionId,
|
|
46
46
|
appId: options.appId,
|
|
47
47
|
productId: options.productId,
|
|
48
48
|
hasCache: !!options.cache,
|
|
49
49
|
hasCachedApps: !!((_a = options.cache) === null || _a === void 0 ? void 0 : _a.apps),
|
|
50
50
|
});
|
|
51
|
-
console.log('[IframeResponder] SDK version check:', {
|
|
51
|
+
console.log('[IframeResponder:Parent] SDK version check:', {
|
|
52
52
|
hasCache: typeof cache !== 'undefined',
|
|
53
53
|
hasCacheGetOrFetch: typeof (cache === null || cache === void 0 ? void 0 : cache.getOrFetch) === 'function',
|
|
54
54
|
});
|
|
@@ -78,19 +78,20 @@ export class IframeResponder {
|
|
|
78
78
|
* Returns the src URL to set on the iframe.
|
|
79
79
|
*/
|
|
80
80
|
async attach(iframe) {
|
|
81
|
-
console.log('[IframeResponder] attach() called, waiting for ready...');
|
|
81
|
+
console.log('[IframeResponder:Parent] 🔗 attach() called, waiting for ready...');
|
|
82
82
|
await this.ready;
|
|
83
|
-
console.log('[IframeResponder] Ready resolved, appUrl:', this.appUrl);
|
|
83
|
+
console.log('[IframeResponder:Parent] ✅ Ready resolved, appUrl:', this.appUrl);
|
|
84
84
|
this.iframe = iframe;
|
|
85
85
|
// Set up message listener
|
|
86
86
|
this.messageHandler = this.handleMessage.bind(this);
|
|
87
87
|
window.addEventListener('message', this.messageHandler);
|
|
88
|
+
console.log('[IframeResponder:Parent] 👂 Message listener attached to window');
|
|
88
89
|
// Set up resize listener for viewport-based calculations
|
|
89
90
|
this.resizeHandler = this.calculateViewportHeight.bind(this);
|
|
90
91
|
window.addEventListener('resize', this.resizeHandler);
|
|
91
92
|
window.addEventListener('orientationchange', this.resizeHandler);
|
|
92
93
|
const src = this.buildIframeSrc();
|
|
93
|
-
console.log('[IframeResponder] Built iframe src:', src);
|
|
94
|
+
console.log('[IframeResponder:Parent] 🎯 Built iframe src:', src);
|
|
94
95
|
return src;
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
@@ -272,33 +273,65 @@ export class IframeResponder {
|
|
|
272
273
|
// Message Handling
|
|
273
274
|
// ===========================================================================
|
|
274
275
|
async handleMessage(event) {
|
|
276
|
+
var _a, _b;
|
|
277
|
+
// Log all received messages for debugging
|
|
278
|
+
const data = event.data;
|
|
279
|
+
if (data && typeof data === 'object') {
|
|
280
|
+
console.log('[IframeResponder:Parent] 📨 Received message from iframe', {
|
|
281
|
+
type: data.type || (data._smartlinksProxyRequest ? 'proxy-request' : 'unknown'),
|
|
282
|
+
hasId: !!data.id,
|
|
283
|
+
isProxyRequest: !!data._smartlinksProxyRequest,
|
|
284
|
+
isCustomProxyRequest: !!data._smartlinksCustomProxyRequest,
|
|
285
|
+
isStandardMessage: !!data._smartlinksIframeMessage,
|
|
286
|
+
isRouteChange: data.type === 'smartlinks-route-change',
|
|
287
|
+
origin: event.origin,
|
|
288
|
+
source: event.source === ((_a = this.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow) ? 'our-iframe' : 'other'
|
|
289
|
+
});
|
|
290
|
+
}
|
|
275
291
|
// Validate source is our iframe
|
|
276
292
|
if (!this.iframe || event.source !== this.iframe.contentWindow) {
|
|
293
|
+
console.log('[IframeResponder:Parent] ⚠️ Ignoring message - not from our iframe', {
|
|
294
|
+
hasIframe: !!this.iframe,
|
|
295
|
+
isCorrectSource: event.source === ((_b = this.iframe) === null || _b === void 0 ? void 0 : _b.contentWindow)
|
|
296
|
+
});
|
|
277
297
|
return;
|
|
278
298
|
}
|
|
279
|
-
|
|
280
|
-
|
|
299
|
+
if (!data || typeof data !== 'object') {
|
|
300
|
+
console.log('[IframeResponder:Parent] ⚠️ Ignoring message - invalid data');
|
|
281
301
|
return;
|
|
302
|
+
}
|
|
282
303
|
// Route changes (deep linking)
|
|
283
304
|
if (data.type === 'smartlinks-route-change') {
|
|
305
|
+
console.log('[IframeResponder:Parent] 🔀 Handling route change');
|
|
284
306
|
this.handleRouteChange(data);
|
|
285
307
|
return;
|
|
286
308
|
}
|
|
287
309
|
// Standardized iframe messages
|
|
288
310
|
if (data._smartlinksIframeMessage) {
|
|
311
|
+
console.log('[IframeResponder:Parent] 📋 Handling standard message');
|
|
289
312
|
await this.handleStandardMessage(data, event);
|
|
290
313
|
return;
|
|
291
314
|
}
|
|
292
315
|
// File upload proxy
|
|
293
316
|
if (data._smartlinksProxyUpload) {
|
|
317
|
+
console.log('[IframeResponder:Parent] 📤 Handling upload proxy');
|
|
294
318
|
await this.handleUpload(data, event);
|
|
295
319
|
return;
|
|
296
320
|
}
|
|
297
321
|
// API proxy requests
|
|
298
|
-
if (data._smartlinksProxyRequest) {
|
|
322
|
+
if (data._smartlinksProxyRequest || data._smartlinksCustomProxyRequest) {
|
|
323
|
+
console.log('[IframeResponder:Parent] 🌐 Handling API proxy request', {
|
|
324
|
+
id: data.id,
|
|
325
|
+
method: data.method,
|
|
326
|
+
path: data.path,
|
|
327
|
+
isCustom: !!data._smartlinksCustomProxyRequest
|
|
328
|
+
});
|
|
299
329
|
await this.handleProxyRequest(data, event);
|
|
300
330
|
return;
|
|
301
331
|
}
|
|
332
|
+
console.log('[IframeResponder:Parent] ⚠️ Unhandled message type', {
|
|
333
|
+
keys: Object.keys(data)
|
|
334
|
+
});
|
|
302
335
|
}
|
|
303
336
|
// ===========================================================================
|
|
304
337
|
// Route Changes (Deep Linking)
|
|
@@ -404,12 +437,17 @@ export class IframeResponder {
|
|
|
404
437
|
// ===========================================================================
|
|
405
438
|
async handleProxyRequest(data, event) {
|
|
406
439
|
var _a, _b, _c;
|
|
440
|
+
console.log('[IframeResponder:Parent] 🔧 handleProxyRequest called', {
|
|
441
|
+
id: data.id,
|
|
442
|
+
hasCustomFlag: '_smartlinksCustomProxyRequest' in data
|
|
443
|
+
});
|
|
407
444
|
const response = {
|
|
408
445
|
_smartlinksProxyResponse: true,
|
|
409
446
|
id: data.id,
|
|
410
447
|
};
|
|
411
448
|
// Handle custom proxy requests (redirects, etc.)
|
|
412
449
|
if ('_smartlinksCustomProxyRequest' in data && data._smartlinksCustomProxyRequest) {
|
|
450
|
+
console.log('[IframeResponder:Parent] 🔄 Handling custom proxy request', { request: data.request });
|
|
413
451
|
if (data.request === 'REDIRECT') {
|
|
414
452
|
const url = (_a = data.params) === null || _a === void 0 ? void 0 : _a.url;
|
|
415
453
|
if (url) {
|
|
@@ -421,6 +459,11 @@ export class IframeResponder {
|
|
|
421
459
|
}
|
|
422
460
|
}
|
|
423
461
|
const proxyData = data;
|
|
462
|
+
console.log('[IframeResponder:Parent] 🌐 Processing API proxy request', {
|
|
463
|
+
id: proxyData.id,
|
|
464
|
+
method: proxyData.method,
|
|
465
|
+
path: proxyData.path
|
|
466
|
+
});
|
|
424
467
|
try {
|
|
425
468
|
const path = proxyData.path.startsWith('/') ? proxyData.path.slice(1) : proxyData.path;
|
|
426
469
|
// Check for cached data matches on GET requests
|
|
@@ -63,6 +63,8 @@ export interface Collection {
|
|
|
63
63
|
/** if dark mode is enabled for this collection */
|
|
64
64
|
dark?: boolean;
|
|
65
65
|
portalUrl?: string;
|
|
66
|
+
/** Allow users to claim products without providing a proof ID (auto-generates serial on-demand) */
|
|
67
|
+
allowAutoGenerateClaims?: boolean;
|
|
66
68
|
}
|
|
67
69
|
export type CollectionResponse = Collection;
|
|
68
70
|
export type CollectionCreateRequest = Omit<Collection, 'id' | 'shortId'>;
|
package/dist/types/product.d.ts
CHANGED
|
@@ -38,6 +38,14 @@ export interface Product {
|
|
|
38
38
|
data: {
|
|
39
39
|
[key: string]: any;
|
|
40
40
|
};
|
|
41
|
+
/** Admin-only configuration */
|
|
42
|
+
admin?: {
|
|
43
|
+
/** Allow users to claim this product without providing a proof ID (overrides collection setting) */
|
|
44
|
+
allowAutoGenerateClaims?: boolean;
|
|
45
|
+
/** Last generated serial ID for auto-claim functionality */
|
|
46
|
+
lastSerialId?: number;
|
|
47
|
+
[key: string]: any;
|
|
48
|
+
};
|
|
41
49
|
}
|
|
42
50
|
export type ProductResponse = Product;
|
|
43
51
|
export type ProductCreateRequest = Omit<Product, 'id' | 'collectionId'> & {
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.38 | Generated: 2026-02-18T19:45:24.113Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -16,6 +16,8 @@ For detailed guides on specific features:
|
|
|
16
16
|
- **[Liquid Templates](liquid-templates.md)** - Dynamic templating for content generation
|
|
17
17
|
- **[Theme System](theme.system.md)** - Theme configuration and customization
|
|
18
18
|
- **[Theme Defaults](theme-defaults.md)** - Default theme values and presets
|
|
19
|
+
- **[Proof Claiming Methods](proof-claiming-methods.md)** - All methods for claiming/registering product ownership (NFC tags, serial numbers, auto-generated claims)
|
|
20
|
+
- **[App Data Storage](app-data-storage.md)** - User-specific and collection-scoped app data storage
|
|
19
21
|
|
|
20
22
|
## API Namespaces
|
|
21
23
|
|
|
@@ -1503,6 +1505,7 @@ interface Collection {
|
|
|
1503
1505
|
shortId: string, // The shortId of this collection
|
|
1504
1506
|
dark?: boolean // if dark mode is enabled for this collection
|
|
1505
1507
|
portalUrl?: string // URL for the collection's portal (if applicable)
|
|
1508
|
+
allowAutoGenerateClaims?: boolean
|
|
1506
1509
|
}
|
|
1507
1510
|
```
|
|
1508
1511
|
|
|
@@ -3381,6 +3384,11 @@ interface Product {
|
|
|
3381
3384
|
data: {
|
|
3382
3385
|
[key: string]: any
|
|
3383
3386
|
} // Flexible key/value data map
|
|
3387
|
+
admin?: {
|
|
3388
|
+
allowAutoGenerateClaims?: boolean
|
|
3389
|
+
lastSerialId?: number
|
|
3390
|
+
[key: string]: any
|
|
3391
|
+
}
|
|
3384
3392
|
}
|
|
3385
3393
|
```
|
|
3386
3394
|
|
|
@@ -3776,16 +3784,27 @@ interface TemplateRenderSourceResponse {
|
|
|
3776
3784
|
**AppConfigOptions** (type)
|
|
3777
3785
|
```typescript
|
|
3778
3786
|
type AppConfigOptions = {
|
|
3787
|
+
/** The app ID */
|
|
3779
3788
|
appId: string
|
|
3789
|
+
|
|
3790
|
+
/** Collection ID (required for most operations) */
|
|
3780
3791
|
collectionId?: string
|
|
3792
|
+
/** Product ID (optional - for product-scoped config) */
|
|
3781
3793
|
productId?: string
|
|
3794
|
+
/** Variant ID (optional - for variant-scoped config) */
|
|
3782
3795
|
variantId?: string
|
|
3796
|
+
/** Batch ID (optional - for batch-scoped config) */
|
|
3783
3797
|
batchId?: string
|
|
3798
|
+
|
|
3799
|
+
/** Item ID - required for getDataItem/deleteDataItem */
|
|
3784
3800
|
itemId?: string
|
|
3785
|
-
|
|
3786
|
-
|
|
3801
|
+
|
|
3802
|
+
/** Use admin endpoints instead of public */
|
|
3787
3803
|
admin?: boolean
|
|
3804
|
+
|
|
3805
|
+
/** Configuration object for setConfig */
|
|
3788
3806
|
config?: any
|
|
3807
|
+
/** Data object for setDataItem */
|
|
3789
3808
|
data?: any
|
|
3790
3809
|
}
|
|
3791
3810
|
```
|
|
@@ -3941,18 +3960,25 @@ Post a chat message to the AI (admin or public)
|
|
|
3941
3960
|
### appConfiguration
|
|
3942
3961
|
|
|
3943
3962
|
**getConfig**(opts: AppConfigOptions) → `Promise<any>`
|
|
3963
|
+
Get app configuration for a collection/product scope. ```typescript const config = await appConfiguration.getConfig({ appId: 'warranty-portal', collectionId: 'my-collection' }); ```
|
|
3944
3964
|
|
|
3945
3965
|
**setConfig**(opts: AppConfigOptions) → `Promise<any>`
|
|
3966
|
+
Set app configuration for a collection/product scope. Requires admin authentication. ```typescript await appConfiguration.setConfig({ appId: 'warranty-portal', collectionId: 'my-collection', admin: true, config: { warrantyPeriod: 24, supportEmail: 'support@example.com' } }); ```
|
|
3946
3967
|
|
|
3947
3968
|
**deleteConfig**(opts: AppConfigOptions) → `Promise<void>`
|
|
3969
|
+
Delete app configuration for a collection/product scope. Requires admin authentication. ```typescript await appConfiguration.deleteConfig({ appId: 'warranty-portal', collectionId: 'my-collection', admin: true }); ```
|
|
3948
3970
|
|
|
3949
3971
|
**getData**(opts: AppConfigOptions) → `Promise<any[]>`
|
|
3972
|
+
Get all data items for an app within a scope. ```typescript const items = await appConfiguration.getData({ appId: 'product-docs', collectionId: 'my-collection', productId: 'product-123' }); ```
|
|
3950
3973
|
|
|
3951
3974
|
**getDataItem**(opts: AppConfigOptions) → `Promise<any>`
|
|
3975
|
+
Get a single data item by ID within a scope. ```typescript const item = await appConfiguration.getDataItem({ appId: 'product-docs', collectionId: 'my-collection', productId: 'product-123', itemId: 'manual-1' }); ```
|
|
3952
3976
|
|
|
3953
3977
|
**setDataItem**(opts: AppConfigOptions) → `Promise<any>`
|
|
3978
|
+
Set/create a data item within a scope. Requires admin authentication. ```typescript await appConfiguration.setDataItem({ appId: 'product-docs', collectionId: 'my-collection', productId: 'product-123', admin: true, data: { id: 'manual-1', title: 'User Manual', url: 'https://...' } }); ```
|
|
3954
3979
|
|
|
3955
3980
|
**deleteDataItem**(opts: AppConfigOptions) → `Promise<void>`
|
|
3981
|
+
Delete a data item by ID within a scope. Requires admin authentication. ```typescript await appConfiguration.deleteDataItem({ appId: 'product-docs', collectionId: 'my-collection', productId: 'product-123', admin: true, itemId: 'manual-1' }); ```
|
|
3956
3982
|
|
|
3957
3983
|
**getWidgets**(collectionId: string,
|
|
3958
3984
|
options?: GetCollectionWidgetsOptions) → `Promise<CollectionWidgetsResponse>`
|
|
@@ -4832,7 +4858,12 @@ Update a proof for a product (admin only). PUT /admin/collection/:collectionId/p
|
|
|
4832
4858
|
productId: string,
|
|
4833
4859
|
proofId: string,
|
|
4834
4860
|
values: ProofClaimRequest) → `Promise<ProofResponse>`
|
|
4835
|
-
Claim a proof for a product. PUT /public/collection/:collectionId/product/:productId/proof/:proofId
|
|
4861
|
+
Claim a proof for a product using a proof ID (serial number, NFC tag, etc.). PUT /public/collection/:collectionId/product/:productId/proof/:proofId/claim
|
|
4862
|
+
|
|
4863
|
+
**claimProduct**(collectionId: string,
|
|
4864
|
+
productId: string,
|
|
4865
|
+
values?: ProofClaimRequest) → `Promise<ProofResponse>`
|
|
4866
|
+
Claim a product without providing a proof ID. System auto-generates a unique serial number on-demand. Requires allowAutoGenerateClaims to be enabled on the collection or product. PUT /public/collection/:collectionId/product/:productId/proof/claim ```typescript const proof = await proof.claimProduct( 'beauty-brand', 'moisturizer-pro', { purchaseDate: '2026-02-17', store: 'Target' } ); console.log('Auto-generated ID:', proof.id); ```
|
|
4836
4867
|
|
|
4837
4868
|
**remove**(collectionId: string,
|
|
4838
4869
|
productId: string,
|
|
@@ -4978,6 +5009,29 @@ Backward-compat: Public batch lookup (GET) with collectionId parameter (ignored)
|
|
|
4978
5009
|
**renderSource**(collectionId: string,
|
|
4979
5010
|
body: TemplateRenderSourceRequest) → `Promise<TemplateRenderSourceResponse>`
|
|
4980
5011
|
|
|
5012
|
+
### userAppData
|
|
5013
|
+
|
|
5014
|
+
**getConfig**(appId: string) → `Promise<any>`
|
|
5015
|
+
Get user's config blob for an app. This is a single JSON object stored per user+app. ```typescript const config = await userAppData.getConfig('allergy-tracker'); // Returns: { allergies: ['peanuts'], notifications: true } ```
|
|
5016
|
+
|
|
5017
|
+
**setConfig**(appId: string, config: any) → `Promise<any>`
|
|
5018
|
+
Set user's config blob for an app. ```typescript await userAppData.setConfig('allergy-tracker', { allergies: ['peanuts', 'shellfish'], notifications: true }); ```
|
|
5019
|
+
|
|
5020
|
+
**deleteConfig**(appId: string) → `Promise<void>`
|
|
5021
|
+
Delete user's config blob for an app. ```typescript await userAppData.deleteConfig('allergy-tracker'); ```
|
|
5022
|
+
|
|
5023
|
+
**list**(appId: string) → `Promise<any[]>`
|
|
5024
|
+
List all user's data items for an app. Returns an array of objects, each with an `id` field. ```typescript const beds = await userAppData.list('garden-planner'); // Returns: [{ id: 'bed-1', name: 'Vegetables', ... }, { id: 'bed-2', ... }] ```
|
|
5025
|
+
|
|
5026
|
+
**get**(appId: string, itemId: string) → `Promise<any>`
|
|
5027
|
+
Get a specific user data item by ID. ```typescript const bed = await userAppData.get('garden-planner', 'bed-1'); // Returns: { id: 'bed-1', name: 'Vegetable Bed', plants: [...] } ```
|
|
5028
|
+
|
|
5029
|
+
**set**(appId: string, item: any) → `Promise<any>`
|
|
5030
|
+
Create or update a user data item. The item object must include an `id` field. ```typescript await userAppData.set('garden-planner', { id: 'bed-1', name: 'Vegetable Bed', plants: ['tomatoes', 'peppers'], location: { x: 10, y: 20 } }); ```
|
|
5031
|
+
|
|
5032
|
+
**remove**(appId: string, itemId: string) → `Promise<void>`
|
|
5033
|
+
Delete a user data item by ID. ```typescript await userAppData.remove('garden-planner', 'bed-1'); ```
|
|
5034
|
+
|
|
4981
5035
|
### variant
|
|
4982
5036
|
|
|
4983
5037
|
**get**(collectionId: string,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# App Data Storage Guide
|
|
2
|
+
|
|
3
|
+
The SmartLinks platform provides two distinct types of data storage for apps:
|
|
4
|
+
|
|
5
|
+
## 1. User-Specific Data (Global per User+App)
|
|
6
|
+
|
|
7
|
+
**Use the `userAppData` namespace for all user-specific data.**
|
|
8
|
+
|
|
9
|
+
User data is **shared across all collections** for a given user and app. This is perfect for storing user preferences, personal settings, and user-generated content that should persist regardless of which collection they're viewing.
|
|
10
|
+
|
|
11
|
+
### Use Cases
|
|
12
|
+
- User allergies in an allergy tracking app
|
|
13
|
+
- Garden bed layouts in a garden planning app
|
|
14
|
+
- User preferences and settings
|
|
15
|
+
- Shopping lists, wishlists, favorites
|
|
16
|
+
- Personal notes and annotations
|
|
17
|
+
|
|
18
|
+
### API Endpoints
|
|
19
|
+
```
|
|
20
|
+
GET /public/auth/app/:appId - Get user's single config blob
|
|
21
|
+
POST /public/auth/app/:appId - Set user's single config blob
|
|
22
|
+
DELETE /public/auth/app/:appId - Delete user's config blob
|
|
23
|
+
|
|
24
|
+
GET /public/auth/app/:appId/data - Get all user's data items
|
|
25
|
+
GET /public/auth/app/:appId/data/:itemId - Get a specific user data item
|
|
26
|
+
POST /public/auth/app/:appId/data - Create/update a user data item
|
|
27
|
+
DELETE /public/auth/app/:appId/data/:itemId - Delete a user data item
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### SDK Usage
|
|
31
|
+
|
|
32
|
+
#### Single Config Blob (Simple Key-Value)
|
|
33
|
+
```typescript
|
|
34
|
+
import { userAppData } from '@proveanything/smartlinks';
|
|
35
|
+
|
|
36
|
+
// Get user's config
|
|
37
|
+
const config = await userAppData.getConfig('allergy-tracker');
|
|
38
|
+
|
|
39
|
+
// Save user's config
|
|
40
|
+
await userAppData.setConfig('allergy-tracker', {
|
|
41
|
+
allergies: ['peanuts', 'shellfish'],
|
|
42
|
+
notifications: true
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Delete user's config
|
|
46
|
+
await userAppData.deleteConfig('allergy-tracker');
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Multiple Keyed Data Items (Recommended)
|
|
50
|
+
```typescript
|
|
51
|
+
// Get all user's garden beds
|
|
52
|
+
const beds = await userAppData.list('garden-planner');
|
|
53
|
+
// Returns: [{ id: 'bed-1', name: 'Vegetables', ... }, { id: 'bed-2', ... }]
|
|
54
|
+
|
|
55
|
+
// Get specific bed
|
|
56
|
+
const bed = await userAppData.get('garden-planner', 'bed-1');
|
|
57
|
+
|
|
58
|
+
// Save/update a bed
|
|
59
|
+
await userAppData.set('garden-planner', {
|
|
60
|
+
id: 'bed-1',
|
|
61
|
+
name: 'Vegetable Bed',
|
|
62
|
+
plants: ['tomatoes', 'peppers'],
|
|
63
|
+
location: { x: 10, y: 20 }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Delete a bed
|
|
67
|
+
await userAppData.remove('garden-planner', 'bed-1');
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Important Notes
|
|
71
|
+
- ✅ **Clean, simple API** - Just pass the `appId` (no collection/product scoping)
|
|
72
|
+
- ✅ User data requires authentication (Bearer token)
|
|
73
|
+
- ✅ Data is automatically scoped to the authenticated user
|
|
74
|
+
- ✅ Impossible to accidentally scope to collections
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 2. Collection/Product-Scoped Data (Admin Configuration)
|
|
79
|
+
|
|
80
|
+
**Use the `appConfiguration` namespace for collection/product-scoped data.**
|
|
81
|
+
|
|
82
|
+
This data is scoped to specific collections, products, variants, or batches. It's typically configured by collection admins/owners and applies to all users viewing that collection/product.
|
|
83
|
+
|
|
84
|
+
### Use Cases
|
|
85
|
+
- App-specific settings for a collection
|
|
86
|
+
- Product-level configuration
|
|
87
|
+
- Feature flags and toggles
|
|
88
|
+
- Theme and branding settings
|
|
89
|
+
- Public content that all users see
|
|
90
|
+
|
|
91
|
+
### API Endpoints
|
|
92
|
+
```
|
|
93
|
+
GET /public/collection/:collectionId/app/:appId
|
|
94
|
+
POST /admin/collection/:collectionId/app/:appId
|
|
95
|
+
DELETE /admin/collection/:collectionId/app/:appId
|
|
96
|
+
|
|
97
|
+
GET /public/collection/:collectionId/product/:productId/app/:appId/data
|
|
98
|
+
POST /admin/collection/:collectionId/product/:productId/app/:appId/data
|
|
99
|
+
...
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### SDK Usage
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { appConfiguration } from '@proveanything/smartlinks';
|
|
106
|
+
|
|
107
|
+
// Get collection-level app config
|
|
108
|
+
const collectionConfig = await appConfiguration.getConfig({
|
|
109
|
+
appId: 'warranty-portal',
|
|
110
|
+
collectionId: 'my-collection'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Set collection-level config (requires admin auth)
|
|
114
|
+
await appConfiguration.setConfig({
|
|
115
|
+
appId: 'warranty-portal',
|
|
116
|
+
collectionId: 'my-collection',
|
|
117
|
+
admin: true,
|
|
118
|
+
config: {
|
|
119
|
+
warrantyPeriod: 24,
|
|
120
|
+
supportEmail: 'support@example.com'
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Get product-level data items
|
|
125
|
+
const items = await appConfiguration.getData({
|
|
126
|
+
appId: 'product-docs',
|
|
127
|
+
collectionId: 'my-collection',
|
|
128
|
+
productId: 'product-123'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Set product-level data item (requires admin auth)
|
|
132
|
+
await appConfiguration.setDataItem({
|
|
133
|
+
appId: 'product-docs',
|
|
134
|
+
collectionId: 'my-collection',
|
|
135
|
+
productId: 'product-123',
|
|
136
|
+
admin: true,
|
|
137
|
+
data: {
|
|
138
|
+
id: 'manual-1',
|
|
139
|
+
title: 'User Manual',
|
|
140
|
+
url: 'https://...'
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Comparison Table
|
|
148
|
+
|
|
149
|
+
| Feature | User Data | Collection/Product Data |
|
|
150
|
+
|---------|-----------|------------------------|
|
|
151
|
+
| **Namespace** | `userAppData` | `appConfiguration` |
|
|
152
|
+
| **Scope** | User + App (global) | Collection/Product/Variant/Batch |
|
|
153
|
+
| **Set by** | Individual users | Collection admins/owners |
|
|
154
|
+
| **Shared across collections?** | ✅ Yes | ❌ No |
|
|
155
|
+
| **Requires auth?** | ✅ Yes (user token) | ✅ Yes (admin token for write) |
|
|
156
|
+
| **Function signature** | Simple: `set(appId, data)` | Options object: `setDataItem({ appId, collectionId, data })` |
|
|
157
|
+
| **Admin write required?** | ❌ No | ✅ Yes (for write operations) |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Migration from Old SDK
|
|
162
|
+
|
|
163
|
+
### Old SDK → New SDK
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// OLD: Get user config
|
|
167
|
+
RemoteApi.get({ path: `public/auth/app/${appId}` })
|
|
168
|
+
// NEW:
|
|
169
|
+
userAppData.getConfig(appId)
|
|
170
|
+
|
|
171
|
+
// OLD: Set user config
|
|
172
|
+
RemoteApi.post({ path: `public/auth/app/${appId}`, data })
|
|
173
|
+
// NEW:
|
|
174
|
+
userAppData.setConfig(appId, data)
|
|
175
|
+
|
|
176
|
+
// OLD: Get user data items
|
|
177
|
+
RemoteApi.get({ path: `public/auth/app/${appId}/data` })
|
|
178
|
+
// NEW:
|
|
179
|
+
userAppData.list(appId)
|
|
180
|
+
|
|
181
|
+
// OLD: Get user data item
|
|
182
|
+
RemoteApi.get({ path: `public/auth/app/${appId}/data/${itemId}` })
|
|
183
|
+
// NEW:
|
|
184
|
+
userAppData.get(appId, itemId)
|
|
185
|
+
|
|
186
|
+
// OLD: Set user data item
|
|
187
|
+
RemoteApi.post({ path: `public/auth/app/${appId}/data`, data: item })
|
|
188
|
+
// NEW:
|
|
189
|
+
userAppData.set(appId, item)
|
|
190
|
+
|
|
191
|
+
// OLD: Delete user data item
|
|
192
|
+
RemoteApi.delete({ path: `public/auth/app/${appId}/data/${itemId}` })
|
|
193
|
+
// NEW:
|
|
194
|
+
userAppData.remove(appId, itemId)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Complete API Reference
|
|
200
|
+
|
|
201
|
+
### `userAppData` (User-Specific Data)
|
|
202
|
+
|
|
203
|
+
| Function | Signature | Description |
|
|
204
|
+
|----------|-----------|-------------|
|
|
205
|
+
| `getConfig` | `(appId: string) => Promise<any>` | Get user's config blob |
|
|
206
|
+
| `setConfig` | `(appId: string, config: any) => Promise<any>` | Set user's config blob |
|
|
207
|
+
| `deleteConfig` | `(appId: string) => Promise<void>` | Delete user's config blob |
|
|
208
|
+
| `list` | `(appId: string) => Promise<any[]>` | List all user's data items |
|
|
209
|
+
| `get` | `(appId: string, itemId: string) => Promise<any>` | Get specific data item |
|
|
210
|
+
| `set` | `(appId: string, item: any) => Promise<any>` | Create/update data item |
|
|
211
|
+
| `remove` | `(appId: string, itemId: string) => Promise<void>` | Delete data item |
|
|
212
|
+
|
|
213
|
+
### `appConfiguration` (Collection/Product-Scoped Data)
|
|
214
|
+
|
|
215
|
+
| Function | Signature | Description |
|
|
216
|
+
|----------|-----------|-------------|
|
|
217
|
+
| `getConfig` | `(opts: AppConfigOptions) => Promise<any>` | Get config for scope |
|
|
218
|
+
| `setConfig` | `(opts: AppConfigOptions) => Promise<any>` | Set config for scope |
|
|
219
|
+
| `deleteConfig` | `(opts: AppConfigOptions) => Promise<void>` | Delete config for scope |
|
|
220
|
+
| `getData` | `(opts: AppConfigOptions) => Promise<any[]>` | List data items in scope |
|
|
221
|
+
| `getDataItem` | `(opts: AppConfigOptions) => Promise<any>` | Get specific data item |
|
|
222
|
+
| `setDataItem` | `(opts: AppConfigOptions) => Promise<any>` | Create/update data item |
|
|
223
|
+
| `deleteDataItem` | `(opts: AppConfigOptions) => Promise<void>` | Delete data item |
|
package/docs/liquid-templates.md
CHANGED
|
@@ -39,7 +39,7 @@ A **Collection** represents a top-level business, brand, or organization. All pr
|
|
|
39
39
|
| Field | Type | Description |
|
|
40
40
|
|-------|------|-------------|
|
|
41
41
|
| `collection.id` | string | Unique identifier |
|
|
42
|
-
| `collection.
|
|
42
|
+
| `collection.title` | string | Display title of the collection |
|
|
43
43
|
| `collection.description` | string | Description text |
|
|
44
44
|
| `collection.slug` | string | URL-friendly identifier |
|
|
45
45
|
| `collection.logoUrl` | string | URL to the collection's logo image |
|