@proveanything/smartlinks 1.3.44 → 1.3.46

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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.3.44 | Generated: 2026-02-19T08:06:35.560Z
3
+ Version: 1.3.46 | Generated: 2026-02-19T21:09:09.402Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -87,7 +87,10 @@ Return whether proxy mode is currently enabled.
87
87
  extraHeaders?: Record<string, string>
88
88
  iframeAutoResize?: boolean // default true when in iframe
89
89
  logger?: Logger // optional console-like or function to enable verbose logging
90
- }) → `void`
90
+ /**
91
+ * When true, bypasses the idempotency guard and forces a full re-initialization.
92
+ * Use only when you intentionally need to reset all SDK state (e.g. in tests or
93
+ * when switching accounts) → `void`
91
94
  Call this once (e.g. at app startup) to configure baseURL/auth.
92
95
 
93
96
  **setNgrokSkipBrowserWarning**(flag: boolean) → `void`
@@ -102,6 +105,9 @@ Allows setting the bearerToken at runtime (e.g. after login/logout).
102
105
  **getBaseURL**() → `string | null`
103
106
  Get the currently configured API base URL. Returns null if initializeApi() has not been called yet.
104
107
 
108
+ **isInitialized**() → `boolean`
109
+ Returns true if initializeApi() has been called at least once. Useful for guards in widgets or shared modules that want to skip initialization when another module has already done it. ```ts if (!isInitialized()) { initializeApi({ baseURL: 'https://smartlinks.app/api/v1' }) } ```
110
+
105
111
  **proxyUploadFormData**(path: string,
106
112
  formData: FormData,
107
113
  onProgress?: (percent: number) → `void`
@@ -1504,6 +1510,8 @@ interface Collection {
1504
1510
  redirectUrl?: string // Whether the collection has a custom domain
1505
1511
  shortId: string, // The shortId of this collection
1506
1512
  dark?: boolean // if dark mode is enabled for this collection
1513
+ primaryColor?: string
1514
+ secondaryColor?: string
1507
1515
  portalUrl?: string // URL for the collection's portal (if applicable)
1508
1516
  allowAutoGenerateClaims?: boolean
1509
1517
  defaultAuthKitId: string // default auth kit for this collection, used for auth
@@ -41,23 +41,43 @@ A **Collection** represents a top-level business, brand, or organization. All pr
41
41
  | `collection.id` | string | Unique identifier |
42
42
  | `collection.title` | string | Display title of the collection |
43
43
  | `collection.description` | string | Description text |
44
- | `collection.slug` | string | URL-friendly identifier |
45
- | `collection.logoUrl` | string | URL to the collection's logo image |
46
- | `collection.websiteUrl` | string | Primary website URL |
47
- | `collection.metadata` | object | Custom key-value metadata |
48
- | `collection.createdAt` | datetime | When the collection was created |
49
- | `collection.updatedAt` | datetime | When the collection was last updated |
44
+ | `collection.shortId` | string | Short identifier for the collection |
45
+ | `collection.logoImage.url` | string | URL to the collection's logo image |
46
+ | `collection.logoImage.thumbnails.x100` | string | 100px thumbnail |
47
+ | `collection.logoImage.thumbnails.x200` | string | 200px thumbnail |
48
+ | `collection.logoImage.thumbnails.x512` | string | 512px thumbnail |
49
+ | `collection.headerImage.url` | string | URL to collection header/hero image |
50
+ | `collection.headerImage.thumbnails.*` | string | Header image thumbnails (x100, x200, x512) |
51
+ | `collection.loaderImage.url` | string | URL to collection loader image |
52
+ | `collection.primaryColor` | string | Primary theme color (hex code) |
53
+ | `collection.secondaryColor` | string | Secondary theme color (hex code) |
54
+ | `collection.dark` | boolean | Whether dark mode is enabled |
55
+ | `collection.portalUrl` | string | URL for the collection's portal |
56
+ | `collection.redirectUrl` | string | Custom domain redirect URL |
57
+ | `collection.roles` | object | User roles mapping (userId → role) |
58
+ | `collection.groupTags` | array | Array of group tag names |
59
+ | `collection.languages` | array | Array of supported language objects |
60
+ | `collection.defaultAuthKitId` | string | Default auth kit ID |
61
+ | `collection.allowAutoGenerateClaims` | boolean | Allow claiming without proof ID |
50
62
 
51
63
  #### Example Usage
52
64
 
53
65
  ```liquid
54
- Welcome to {{ collection.name }}!
66
+ Welcome to {{ collection.title }}!
55
67
 
56
- {% if collection.websiteUrl %}
57
- Visit us at {{ collection.websiteUrl }}
68
+ {% if collection.portalUrl %}
69
+ Visit our portal at {{ collection.portalUrl }}
58
70
  {% endif %}
59
71
 
60
- <img src="{{ collection.logoUrl }}" alt="{{ collection.name }} logo" />
72
+ {% if collection.logoImage %}
73
+ <img src="{{ collection.logoImage.url }}" alt="{{ collection.title }} logo" />
74
+ <!-- Or use a thumbnail: -->
75
+ <img src="{{ collection.logoImage.thumbnails.x200 }}" alt="{{ collection.title }} logo" />
76
+ {% endif %}
77
+
78
+ {% if collection.dark %}
79
+ <!-- Dark mode is enabled -->
80
+ {% endif %}
61
81
  ```
62
82
 
63
83
  ---
@@ -70,31 +90,44 @@ A **Product** represents a type or definition of a physical or digital item. Pro
70
90
  |-------|------|-------------|
71
91
  | `product.id` | string | Unique identifier |
72
92
  | `product.name` | string | Product name |
93
+ | `product.collectionId` | string | ID of the parent collection |
73
94
  | `product.description` | string | Product description |
74
- | `product.sku` | string | Stock keeping unit |
75
- | `product.slug` | string | URL-friendly identifier |
76
- | `product.imageUrl` | string | Primary product image URL |
77
- | `product.images` | array | Array of image URLs |
78
- | `product.category` | string | Product category |
79
- | `product.tags` | array | Array of tag strings |
80
- | `product.metadata` | object | Custom key-value metadata |
81
- | `product.createdAt` | datetime | When the product was created |
82
- | `product.updatedAt` | datetime | When the product was last updated |
95
+ | `product.gtin` | string | Global Trade Item Number |
96
+ | `product.type` | string | Product type from standard types |
97
+ | `product.heroImage.url` | string | Primary product image URL |
98
+ | `product.heroImage.thumbnails.x100` | string | 100px thumbnail |
99
+ | `product.heroImage.thumbnails.x200` | string | 200px thumbnail |
100
+ | `product.heroImage.thumbnails.x512` | string | 512px thumbnail |
101
+ | `product.tags` | object | Tag map with boolean values |
102
+ | `product.data` | object | Flexible key-value data map |
103
+ | `product.admin` | object | Admin-only configuration |
104
+ | `product.admin.allowAutoGenerateClaims` | boolean | Allow claiming without proof ID |
105
+ | `product.admin.lastSerialId` | number | Last generated serial ID |
83
106
 
84
107
  #### Example Usage
85
108
 
86
109
  ```liquid
87
- Your {{ product.name }} (SKU: {{ product.sku }})
110
+ Your {{ product.name }}
88
111
 
89
112
  {{ product.description }}
90
113
 
91
- {% if product.tags.size > 0 %}
92
- Tags: {{ product.tags | join: ", " }}
114
+ {% if product.gtin %}
115
+ GTIN: {{ product.gtin }}
93
116
  {% endif %}
94
117
 
95
- {% for image in product.images %}
96
- <img src="{{ image }}" alt="{{ product.name }}" />
97
- {% endfor %}
118
+ {% if product.heroImage %}
119
+ <img src="{{ product.heroImage.url }}" alt="{{ product.name }}" />
120
+ <!-- Or use a thumbnail: -->
121
+ <img src="{{ product.heroImage.thumbnails.x512 }}" alt="{{ product.name }}" />
122
+ {% endif %}
123
+
124
+ {% if product.tags.premium %}
125
+ 🌟 Premium Product
126
+ {% endif %}
127
+
128
+ {% if product.data.warranty_years %}
129
+ Warranty: {{ product.data.warranty_years }} years
130
+ {% endif %}
98
131
  ```
99
132
 
100
133
  ---
@@ -106,34 +139,50 @@ A **Proof** is a specific instance of a product—think of it as a unique digita
106
139
  | Field | Type | Description |
107
140
  |-------|------|-------------|
108
141
  | `proof.id` | string | Unique identifier |
109
- | `proof.serialNumber` | string | Human-readable serial number |
110
- | `proof.claimed` | boolean | Whether the proof has been claimed |
111
- | `proof.claimedAt` | datetime | When the proof was claimed |
112
- | `proof.claimedBy` | string | User ID of the claimer |
113
- | `proof.status` | string | Current status (e.g., "active", "transferred") |
114
- | `proof.nfcTagId` | string | Associated NFC tag ID (if applicable) |
115
- | `proof.qrCode` | string | QR code identifier |
116
- | `proof.shortCode` | string | Short code for easy lookup |
117
- | `proof.metadata` | object | Custom key-value metadata |
142
+ | `proof.collectionId` | string | ID of the parent collection |
143
+ | `proof.productId` | string | ID of the associated product |
144
+ | `proof.tokenId` | string | Unique token identifier |
145
+ | `proof.userId` | string | User ID of the owner |
146
+ | `proof.claimable` | boolean | Whether the proof can be claimed |
147
+ | `proof.virtual` | boolean | Whether this is a virtual proof |
148
+ | `proof.values` | object | Arbitrary key-value pairs for proof data |
118
149
  | `proof.createdAt` | datetime | When the proof was created |
119
- | `proof.updatedAt` | datetime | When the proof was last updated |
150
+
151
+ **Note**: Proof `values` object can contain any custom fields. Common examples:
152
+ - `proof.values.serialNumber` - Serial number
153
+ - `proof.values.claimedAt` - Claim timestamp
154
+ - `proof.values.status` - Current status
155
+ - `proof.values.warrantyExpiry` - Warranty expiration
120
156
 
121
157
  #### Example Usage
122
158
 
123
159
  ```liquid
124
160
  Proof of Authenticity
125
161
 
126
- Serial Number: {{ proof.serialNumber }}
127
- Status: {{ proof.status }}
162
+ {% if proof.values.serialNumber %}
163
+ Serial Number: {{ proof.values.serialNumber }}
164
+ {% endif %}
128
165
 
129
- {% if proof.claimed %}
130
- Claimed on: {{ proof.claimedAt | date: "%B %d, %Y at %H:%M" }}
166
+ {% if proof.values.status %}
167
+ Status: {{ proof.values.status }}
168
+ {% endif %}
169
+
170
+ {% if proof.claimable %}
171
+ This item is available to claim.
131
172
  {% else %}
132
- This item has not been claimed yet.
173
+ This item has been claimed.
133
174
  {% endif %}
134
175
 
135
- {% if proof.metadata.warrantyExpiry %}
136
- Warranty expires: {{ proof.metadata.warrantyExpiry | date: "%B %d, %Y" }}
176
+ {% if proof.virtual %}
177
+ 🌐 Digital Product
178
+ {% endif %}
179
+
180
+ {% if proof.values.claimedAt %}
181
+ Claimed on: {{ proof.values.claimedAt | date: "%B %d, %Y at %H:%M" }}
182
+ {% endif %}
183
+
184
+ {% if proof.values.warrantyExpiry %}
185
+ Warranty expires: {{ proof.values.warrantyExpiry | date: "%B %d, %Y" }}
137
186
  {% endif %}
138
187
  ```
139
188
 
@@ -145,25 +194,32 @@ A **Contact** represents a customer or user in the system. Contacts are associat
145
194
 
146
195
  | Field | Type | Description |
147
196
  |-------|------|-------------|
148
- | `contact.id` | string | Unique identifier |
149
- | `contact.email` | string | Email address |
150
- | `contact.name` | string | Full name |
197
+ | `contact.contactId` | string | Unique identifier |
198
+ | `contact.orgId` | string | Organization/collection ID |
199
+ | `contact.userId` | string | Linked user ID (if authenticated) |
200
+ | `contact.email` | string | Primary email address |
201
+ | `contact.phone` | string | Primary phone number |
202
+ | `contact.emails` | array | Array of all email addresses |
203
+ | `contact.phones` | array | Array of all phone numbers |
151
204
  | `contact.firstName` | string | First name |
152
205
  | `contact.lastName` | string | Last name |
153
- | `contact.phone` | string | Phone number |
206
+ | `contact.displayName` | string | Display name |
207
+ | `contact.company` | string | Company name |
208
+ | `contact.avatarUrl` | string | Profile picture URL |
154
209
  | `contact.locale` | string | Preferred language/locale (e.g., "en", "de") |
155
210
  | `contact.timezone` | string | Preferred timezone |
156
- | `contact.avatarUrl` | string | Profile picture URL |
157
- | `contact.metadata` | object | Custom key-value metadata |
158
211
  | `contact.tags` | array | Array of tag strings for segmentation |
212
+ | `contact.source` | string | How the contact was created |
213
+ | `contact.notes` | string | Admin notes |
214
+ | `contact.externalIds` | object | External system IDs |
215
+ | `contact.customFields` | object | Custom key-value data |
159
216
  | `contact.createdAt` | datetime | When the contact was created |
160
217
  | `contact.updatedAt` | datetime | When the contact was last updated |
161
- | `contact.lastSeenAt` | datetime | Last activity timestamp |
162
218
 
163
219
  #### Example Usage
164
220
 
165
221
  ```liquid
166
- Hi {{ contact.firstName | default: contact.name | default: "there" }},
222
+ Hi {{ contact.firstName | default: contact.displayName | default: "there" }},
167
223
 
168
224
  {% if contact.locale == "de" %}
169
225
  Willkommen!
@@ -176,6 +232,14 @@ Welcome!
176
232
  {% if contact.phone %}
177
233
  We'll send updates to {{ contact.phone }}.
178
234
  {% endif %}
235
+
236
+ {% if contact.company %}
237
+ Company: {{ contact.company }}
238
+ {% endif %}
239
+
240
+ {% if contact.customFields.vip %}
241
+ 🌟 VIP Customer
242
+ {% endif %}
179
243
  ```
180
244
 
181
245
  ---
@@ -186,20 +250,18 @@ A **User** represents an authenticated account in the system. This is typically
186
250
 
187
251
  | Field | Type | Description |
188
252
  |-------|------|-------------|
189
- | `user.id` | string | Unique identifier |
253
+ | `user.uid` | string | Unique identifier |
190
254
  | `user.email` | string | Email address |
191
- | `user.name` | string | Display name |
192
- | `user.admin` | boolean | Whether user has admin privileges |
193
- | `user.avatarUrl` | string | Profile picture URL |
194
- | `user.createdAt` | datetime | Account creation date |
255
+ | `user.displayName` | string | Display name |
256
+ | `user.accountData` | object | Account-specific data and settings |
195
257
 
196
258
  #### Example Usage
197
259
 
198
260
  ```liquid
199
- Logged in as: {{ user.name }} ({{ user.email }})
261
+ Logged in as: {{ user.displayName }} ({{ user.email }})
200
262
 
201
- {% if user.admin %}
202
- 🔐 You have administrator access.
263
+ {% if user.accountData.preferences.notifications %}
264
+ Notifications are enabled.
203
265
  {% endif %}
204
266
  ```
205
267
 
@@ -212,26 +274,33 @@ An **Attestation** is flexible data attached to a specific proof. It's used to s
212
274
  | Field | Type | Description |
213
275
  |-------|------|-------------|
214
276
  | `attestation.id` | string | Unique identifier |
215
- | `attestation.type` | string | Attestation type (app-defined) |
216
- | `attestation.data` | object | The attestation payload (varies by type) |
217
- | `attestation.createdBy` | string | User ID who created it |
277
+ | `attestation.public` | object | Public attestation data (varies by type) |
278
+ | `attestation.private` | object | Private attestation data (varies by type) |
279
+ | `attestation.proof` | object | Associated proof reference/data |
218
280
  | `attestation.createdAt` | datetime | When the attestation was created |
219
281
  | `attestation.updatedAt` | datetime | When the attestation was last updated |
220
282
 
283
+ **Note**: The `public` and `private` objects contain custom fields based on your use case.
284
+
221
285
  #### Example Usage
222
286
 
223
287
  ```liquid
224
- {% if attestation.type == "warranty_registration" %}
288
+ {% if attestation.public.type == "warranty_registration" %}
225
289
  Warranty Registration Details:
226
290
  - Registered: {{ attestation.createdAt | date: "%B %d, %Y" }}
227
- - Purchase Date: {{ attestation.data.purchaseDate }}
228
- - Store: {{ attestation.data.storeName }}
291
+ - Purchase Date: {{ attestation.public.purchaseDate }}
292
+ - Store: {{ attestation.public.storeName }}
293
+ {% endif %}
294
+
295
+ {% if attestation.public.type == "tasting_note" %}
296
+ 🍷 Tasting Note:
297
+ "{{ attestation.public.notes }}"
298
+ Rating: {{ attestation.public.rating }}/5
229
299
  {% endif %}
230
300
 
231
- {% if attestation.type == "tasting_note" %}
232
- 🍷 Tasting Note by {{ attestation.data.author }}:
233
- "{{ attestation.data.notes }}"
234
- Rating: {{ attestation.data.rating }}/5
301
+ {% if attestation.private.internalNotes %}
302
+ <!-- Private data only visible to admins -->
303
+ Notes: {{ attestation.private.internalNotes }}
235
304
  {% endif %}
236
305
  ```
237
306
 
@@ -357,37 +426,37 @@ Loop variables:
357
426
  ```liquid
358
427
  Subject: Your {{ product.name }} has been registered!
359
428
 
360
- Hi {{ contact.firstName | default: "there" }},
429
+ Hi {{ contact.firstName | default: contact.displayName | default: "there" }},
361
430
 
362
- Great news! Your {{ product.name }} (Serial: {{ proof.serialNumber }})
431
+ Great news! Your {{ product.name }}{% if proof.values.serialNumber %} (Serial: {{ proof.values.serialNumber }}){% endif %}
363
432
  has been successfully registered to your account.
364
433
 
365
- {% if product.metadata.warrantyYears %}
366
- Your warranty is valid for {{ product.metadata.warrantyYears }} years
434
+ {% if product.data.warranty_years %}
435
+ Your warranty is valid for {{ product.data.warranty_years }} years
367
436
  from the date of purchase.
368
437
  {% endif %}
369
438
 
370
- If you have any questions, please contact {{ collection.name }} support.
439
+ If you have any questions, please contact {{ collection.title }} support.
371
440
 
372
441
  Best regards,
373
- The {{ collection.name }} Team
442
+ The {{ collection.title }} Team
374
443
  ```
375
444
 
376
445
  ### Notification Messages
377
446
 
378
447
  ```liquid
379
448
  🎉 {{ contact.firstName }}, your {{ product.name }} is now verified!
380
- Proof ID: {{ proof.shortCode }}
449
+ {% if proof.values.shortCode %}Proof ID: {{ proof.values.shortCode }}{% endif %}
381
450
  ```
382
451
 
383
452
  ### Dynamic Content Blocks
384
453
 
385
454
  ```liquid
386
- {% if proof.metadata.tier == "gold" %}
455
+ {% if proof.values.tier == "gold" %}
387
456
  <div class="gold-benefits">
388
457
  As a Gold member, you get exclusive access to...
389
458
  </div>
390
- {% elsif proof.metadata.tier == "silver" %}
459
+ {% elsif proof.values.tier == "silver" %}
391
460
  <div class="silver-benefits">
392
461
  Your Silver membership includes...
393
462
  </div>
@@ -413,18 +482,19 @@ Proof ID: {{ proof.shortCode }}
413
482
 
414
483
  ## Accessing Nested Data
415
484
 
416
- Use dot notation to access nested fields in metadata or data objects:
485
+ Use dot notation to access nested fields in data objects:
417
486
 
418
487
  ```liquid
419
- {{ product.metadata.manufacturer }}
420
- {{ attestation.data.warranty.expiryDate }}
421
- {{ collection.metadata.social.twitter }}
488
+ {{ product.data.manufacturer }}
489
+ {{ attestation.public.warranty.expiryDate }}
490
+ {{ contact.customFields.vip_level }}
491
+ {{ proof.values.serialNumber }}
422
492
  ```
423
493
 
424
494
  For dynamic keys, you may need to use bracket notation (if supported):
425
495
 
426
496
  ```liquid
427
- {{ product.metadata["custom-field"] }}
497
+ {{ product.data["custom-field"] }}
428
498
  ```
429
499
 
430
500
  ---
@@ -433,29 +503,29 @@ For dynamic keys, you may need to use bracket notation (if supported):
433
503
 
434
504
  1. **Always use `default` filter** for optional fields to avoid blank output:
435
505
  ```liquid
436
- {{ contact.name | default: "Valued Customer" }}
506
+ {{ contact.displayName | default: contact.firstName | default: "Valued Customer" }}
437
507
  ```
438
508
 
439
509
  2. **Escape user-generated content** when outputting as HTML:
440
510
  ```liquid
441
- {{ attestation.data.userNotes | escape }}
511
+ {{ attestation.public.userNotes | escape }}
442
512
  ```
443
513
 
444
514
  3. **Check for existence** before accessing nested data:
445
515
  ```liquid
446
- {% if proof.metadata.warranty %}
447
- Warranty: {{ proof.metadata.warranty.type }}
516
+ {% if proof.values.warranty %}
517
+ Warranty: {{ proof.values.warranty.type }}
448
518
  {% endif %}
449
519
  ```
450
520
 
451
521
  4. **Use meaningful fallbacks** for a better user experience:
452
522
  ```liquid
453
- Hi {{ contact.firstName | default: contact.name | default: "there" }},
523
+ Hi {{ contact.firstName | default: contact.displayName | default: "there" }},
454
524
  ```
455
525
 
456
526
  5. **Format dates appropriately** for the user's locale:
457
527
  ```liquid
458
- {{ proof.claimedAt | date: "%d %B %Y" }}
528
+ {{ proof.createdAt | date: "%d %B %Y" }}
459
529
  ```
460
530
 
461
531
  ---
package/dist/http.d.ts CHANGED
@@ -16,6 +16,13 @@ export declare function initializeApi(options: {
16
16
  extraHeaders?: Record<string, string>;
17
17
  iframeAutoResize?: boolean;
18
18
  logger?: Logger;
19
+ /**
20
+ * When true, bypasses the idempotency guard and forces a full re-initialization.
21
+ * Use only when you intentionally need to reset all SDK state (e.g. in tests or
22
+ * when switching accounts). In normal application code, prefer letting the guard
23
+ * protect runtime state such as login tokens.
24
+ */
25
+ force?: boolean;
19
26
  }): void;
20
27
  /** Enable/disable automatic "ngrok-skip-browser-warning" header. */
21
28
  export declare function setNgrokSkipBrowserWarning(flag: boolean): void;
@@ -30,6 +37,19 @@ export declare function setBearerToken(token: string | undefined): void;
30
37
  * Returns null if initializeApi() has not been called yet.
31
38
  */
32
39
  export declare function getBaseURL(): string | null;
40
+ /**
41
+ * Returns true if initializeApi() has been called at least once.
42
+ * Useful for guards in widgets or shared modules that want to skip
43
+ * initialization when another module has already done it.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * if (!isInitialized()) {
48
+ * initializeApi({ baseURL: 'https://smartlinks.app/api/v1' })
49
+ * }
50
+ * ```
51
+ */
52
+ export declare function isInitialized(): boolean;
33
53
  /**
34
54
  * Upload a FormData payload via proxy with progress events using chunked postMessage.
35
55
  * Parent is expected to implement the counterpart protocol.
package/dist/http.js CHANGED
@@ -20,6 +20,8 @@ let bearerToken = undefined;
20
20
  let proxyMode = false;
21
21
  let ngrokSkipBrowserWarning = false;
22
22
  let extraHeadersGlobal = {};
23
+ /** Whether initializeApi has been successfully called at least once. */
24
+ let initialized = false;
23
25
  let logger;
24
26
  function logDebug(...args) {
25
27
  if (!logger)
@@ -169,9 +171,32 @@ function normalizeErrorResponse(responseBody, statusCode) {
169
171
  import { iframe } from './iframe';
170
172
  export function initializeApi(options) {
171
173
  // Normalize baseURL by removing trailing slashes.
172
- baseURL = options.baseURL.replace(/\/+$/g, "");
174
+ const normalizedBaseURL = options.baseURL.replace(/\/+$/g, "");
175
+ // ------------------------------------------------------------------
176
+ // Firebase-style idempotency guard
177
+ // If we have already been initialized with the same baseURL and the
178
+ // caller is not forcing a reset, return immediately. This prevents
179
+ // any module – widget, component, or re-rendered page – from
180
+ // accidentally wiping runtime state such as a bearerToken that was
181
+ // set by auth.login() after the first initialization.
182
+ // ------------------------------------------------------------------
183
+ if (initialized && !options.force && baseURL === normalizedBaseURL) {
184
+ logDebug('[smartlinks] initializeApi: already initialized with this baseURL – skipping.', { baseURL });
185
+ return;
186
+ }
187
+ baseURL = normalizedBaseURL;
173
188
  apiKey = options.apiKey;
174
- bearerToken = options.bearerToken;
189
+ // Only overwrite bearerToken when the caller explicitly supplies one,
190
+ // OR when this is the very first initialization (start with a clean slate).
191
+ // Re-initialization calls that omit bearerToken must NOT clear a token that
192
+ // was acquired at runtime (e.g. from a successful auth.login()).
193
+ if (options.bearerToken !== undefined) {
194
+ bearerToken = options.bearerToken;
195
+ }
196
+ else if (!initialized) {
197
+ bearerToken = undefined;
198
+ }
199
+ // else: preserve the existing runtime bearerToken.
175
200
  proxyMode = !!options.proxyMode;
176
201
  // Auto-enable ngrok skip header if domain contains .ngrok.io and user did not explicitly set the flag.
177
202
  // Infer ngrok usage from common domains (.ngrok.io or .ngrok-free.dev)
@@ -185,6 +210,7 @@ export function initializeApi(options) {
185
210
  iframe.enableAutoIframeResize();
186
211
  }
187
212
  logger = options.logger;
213
+ initialized = true;
188
214
  logDebug('[smartlinks] initializeApi', {
189
215
  baseURL,
190
216
  proxyMode,
@@ -215,6 +241,21 @@ export function setBearerToken(token) {
215
241
  export function getBaseURL() {
216
242
  return baseURL;
217
243
  }
244
+ /**
245
+ * Returns true if initializeApi() has been called at least once.
246
+ * Useful for guards in widgets or shared modules that want to skip
247
+ * initialization when another module has already done it.
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * if (!isInitialized()) {
252
+ * initializeApi({ baseURL: 'https://smartlinks.app/api/v1' })
253
+ * }
254
+ * ```
255
+ */
256
+ export function isInitialized() {
257
+ return initialized;
258
+ }
218
259
  // Map of pending proxy requests: id -> {resolve, reject}
219
260
  const proxyPending = {};
220
261
  function generateProxyId() {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { initializeApi, request, sendCustomProxyMessage } from "./http";
1
+ export { initializeApi, isInitialized, request, sendCustomProxyMessage } from "./http";
2
2
  export * from "./api";
3
3
  export * from "./types";
4
4
  export { iframe } from "./iframe";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  // Top-level entrypoint of the npm package. Re-export initializeApi + all namespaces.
3
- export { initializeApi, request, sendCustomProxyMessage } from "./http";
3
+ export { initializeApi, isInitialized, request, sendCustomProxyMessage } from "./http";
4
4
  export * from "./api";
5
5
  export * from "./types";
6
6
  // Iframe namespace
@@ -62,6 +62,10 @@ export interface Collection {
62
62
  shortId: string;
63
63
  /** if dark mode is enabled for this collection */
64
64
  dark?: boolean;
65
+ /** Primary theme color */
66
+ primaryColor?: string;
67
+ /** Secondary theme color */
68
+ secondaryColor?: string;
65
69
  portalUrl?: string;
66
70
  /** Allow users to claim products without providing a proof ID (auto-generates serial on-demand) */
67
71
  allowAutoGenerateClaims?: boolean;
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.3.44 | Generated: 2026-02-19T08:06:35.560Z
3
+ Version: 1.3.46 | Generated: 2026-02-19T21:09:09.402Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -87,7 +87,10 @@ Return whether proxy mode is currently enabled.
87
87
  extraHeaders?: Record<string, string>
88
88
  iframeAutoResize?: boolean // default true when in iframe
89
89
  logger?: Logger // optional console-like or function to enable verbose logging
90
- }) → `void`
90
+ /**
91
+ * When true, bypasses the idempotency guard and forces a full re-initialization.
92
+ * Use only when you intentionally need to reset all SDK state (e.g. in tests or
93
+ * when switching accounts) → `void`
91
94
  Call this once (e.g. at app startup) to configure baseURL/auth.
92
95
 
93
96
  **setNgrokSkipBrowserWarning**(flag: boolean) → `void`
@@ -102,6 +105,9 @@ Allows setting the bearerToken at runtime (e.g. after login/logout).
102
105
  **getBaseURL**() → `string | null`
103
106
  Get the currently configured API base URL. Returns null if initializeApi() has not been called yet.
104
107
 
108
+ **isInitialized**() → `boolean`
109
+ Returns true if initializeApi() has been called at least once. Useful for guards in widgets or shared modules that want to skip initialization when another module has already done it. ```ts if (!isInitialized()) { initializeApi({ baseURL: 'https://smartlinks.app/api/v1' }) } ```
110
+
105
111
  **proxyUploadFormData**(path: string,
106
112
  formData: FormData,
107
113
  onProgress?: (percent: number) → `void`
@@ -1504,6 +1510,8 @@ interface Collection {
1504
1510
  redirectUrl?: string // Whether the collection has a custom domain
1505
1511
  shortId: string, // The shortId of this collection
1506
1512
  dark?: boolean // if dark mode is enabled for this collection
1513
+ primaryColor?: string
1514
+ secondaryColor?: string
1507
1515
  portalUrl?: string // URL for the collection's portal (if applicable)
1508
1516
  allowAutoGenerateClaims?: boolean
1509
1517
  defaultAuthKitId: string // default auth kit for this collection, used for auth
@@ -41,23 +41,43 @@ A **Collection** represents a top-level business, brand, or organization. All pr
41
41
  | `collection.id` | string | Unique identifier |
42
42
  | `collection.title` | string | Display title of the collection |
43
43
  | `collection.description` | string | Description text |
44
- | `collection.slug` | string | URL-friendly identifier |
45
- | `collection.logoUrl` | string | URL to the collection's logo image |
46
- | `collection.websiteUrl` | string | Primary website URL |
47
- | `collection.metadata` | object | Custom key-value metadata |
48
- | `collection.createdAt` | datetime | When the collection was created |
49
- | `collection.updatedAt` | datetime | When the collection was last updated |
44
+ | `collection.shortId` | string | Short identifier for the collection |
45
+ | `collection.logoImage.url` | string | URL to the collection's logo image |
46
+ | `collection.logoImage.thumbnails.x100` | string | 100px thumbnail |
47
+ | `collection.logoImage.thumbnails.x200` | string | 200px thumbnail |
48
+ | `collection.logoImage.thumbnails.x512` | string | 512px thumbnail |
49
+ | `collection.headerImage.url` | string | URL to collection header/hero image |
50
+ | `collection.headerImage.thumbnails.*` | string | Header image thumbnails (x100, x200, x512) |
51
+ | `collection.loaderImage.url` | string | URL to collection loader image |
52
+ | `collection.primaryColor` | string | Primary theme color (hex code) |
53
+ | `collection.secondaryColor` | string | Secondary theme color (hex code) |
54
+ | `collection.dark` | boolean | Whether dark mode is enabled |
55
+ | `collection.portalUrl` | string | URL for the collection's portal |
56
+ | `collection.redirectUrl` | string | Custom domain redirect URL |
57
+ | `collection.roles` | object | User roles mapping (userId → role) |
58
+ | `collection.groupTags` | array | Array of group tag names |
59
+ | `collection.languages` | array | Array of supported language objects |
60
+ | `collection.defaultAuthKitId` | string | Default auth kit ID |
61
+ | `collection.allowAutoGenerateClaims` | boolean | Allow claiming without proof ID |
50
62
 
51
63
  #### Example Usage
52
64
 
53
65
  ```liquid
54
- Welcome to {{ collection.name }}!
66
+ Welcome to {{ collection.title }}!
55
67
 
56
- {% if collection.websiteUrl %}
57
- Visit us at {{ collection.websiteUrl }}
68
+ {% if collection.portalUrl %}
69
+ Visit our portal at {{ collection.portalUrl }}
58
70
  {% endif %}
59
71
 
60
- <img src="{{ collection.logoUrl }}" alt="{{ collection.name }} logo" />
72
+ {% if collection.logoImage %}
73
+ <img src="{{ collection.logoImage.url }}" alt="{{ collection.title }} logo" />
74
+ <!-- Or use a thumbnail: -->
75
+ <img src="{{ collection.logoImage.thumbnails.x200 }}" alt="{{ collection.title }} logo" />
76
+ {% endif %}
77
+
78
+ {% if collection.dark %}
79
+ <!-- Dark mode is enabled -->
80
+ {% endif %}
61
81
  ```
62
82
 
63
83
  ---
@@ -70,31 +90,44 @@ A **Product** represents a type or definition of a physical or digital item. Pro
70
90
  |-------|------|-------------|
71
91
  | `product.id` | string | Unique identifier |
72
92
  | `product.name` | string | Product name |
93
+ | `product.collectionId` | string | ID of the parent collection |
73
94
  | `product.description` | string | Product description |
74
- | `product.sku` | string | Stock keeping unit |
75
- | `product.slug` | string | URL-friendly identifier |
76
- | `product.imageUrl` | string | Primary product image URL |
77
- | `product.images` | array | Array of image URLs |
78
- | `product.category` | string | Product category |
79
- | `product.tags` | array | Array of tag strings |
80
- | `product.metadata` | object | Custom key-value metadata |
81
- | `product.createdAt` | datetime | When the product was created |
82
- | `product.updatedAt` | datetime | When the product was last updated |
95
+ | `product.gtin` | string | Global Trade Item Number |
96
+ | `product.type` | string | Product type from standard types |
97
+ | `product.heroImage.url` | string | Primary product image URL |
98
+ | `product.heroImage.thumbnails.x100` | string | 100px thumbnail |
99
+ | `product.heroImage.thumbnails.x200` | string | 200px thumbnail |
100
+ | `product.heroImage.thumbnails.x512` | string | 512px thumbnail |
101
+ | `product.tags` | object | Tag map with boolean values |
102
+ | `product.data` | object | Flexible key-value data map |
103
+ | `product.admin` | object | Admin-only configuration |
104
+ | `product.admin.allowAutoGenerateClaims` | boolean | Allow claiming without proof ID |
105
+ | `product.admin.lastSerialId` | number | Last generated serial ID |
83
106
 
84
107
  #### Example Usage
85
108
 
86
109
  ```liquid
87
- Your {{ product.name }} (SKU: {{ product.sku }})
110
+ Your {{ product.name }}
88
111
 
89
112
  {{ product.description }}
90
113
 
91
- {% if product.tags.size > 0 %}
92
- Tags: {{ product.tags | join: ", " }}
114
+ {% if product.gtin %}
115
+ GTIN: {{ product.gtin }}
93
116
  {% endif %}
94
117
 
95
- {% for image in product.images %}
96
- <img src="{{ image }}" alt="{{ product.name }}" />
97
- {% endfor %}
118
+ {% if product.heroImage %}
119
+ <img src="{{ product.heroImage.url }}" alt="{{ product.name }}" />
120
+ <!-- Or use a thumbnail: -->
121
+ <img src="{{ product.heroImage.thumbnails.x512 }}" alt="{{ product.name }}" />
122
+ {% endif %}
123
+
124
+ {% if product.tags.premium %}
125
+ 🌟 Premium Product
126
+ {% endif %}
127
+
128
+ {% if product.data.warranty_years %}
129
+ Warranty: {{ product.data.warranty_years }} years
130
+ {% endif %}
98
131
  ```
99
132
 
100
133
  ---
@@ -106,34 +139,50 @@ A **Proof** is a specific instance of a product—think of it as a unique digita
106
139
  | Field | Type | Description |
107
140
  |-------|------|-------------|
108
141
  | `proof.id` | string | Unique identifier |
109
- | `proof.serialNumber` | string | Human-readable serial number |
110
- | `proof.claimed` | boolean | Whether the proof has been claimed |
111
- | `proof.claimedAt` | datetime | When the proof was claimed |
112
- | `proof.claimedBy` | string | User ID of the claimer |
113
- | `proof.status` | string | Current status (e.g., "active", "transferred") |
114
- | `proof.nfcTagId` | string | Associated NFC tag ID (if applicable) |
115
- | `proof.qrCode` | string | QR code identifier |
116
- | `proof.shortCode` | string | Short code for easy lookup |
117
- | `proof.metadata` | object | Custom key-value metadata |
142
+ | `proof.collectionId` | string | ID of the parent collection |
143
+ | `proof.productId` | string | ID of the associated product |
144
+ | `proof.tokenId` | string | Unique token identifier |
145
+ | `proof.userId` | string | User ID of the owner |
146
+ | `proof.claimable` | boolean | Whether the proof can be claimed |
147
+ | `proof.virtual` | boolean | Whether this is a virtual proof |
148
+ | `proof.values` | object | Arbitrary key-value pairs for proof data |
118
149
  | `proof.createdAt` | datetime | When the proof was created |
119
- | `proof.updatedAt` | datetime | When the proof was last updated |
150
+
151
+ **Note**: Proof `values` object can contain any custom fields. Common examples:
152
+ - `proof.values.serialNumber` - Serial number
153
+ - `proof.values.claimedAt` - Claim timestamp
154
+ - `proof.values.status` - Current status
155
+ - `proof.values.warrantyExpiry` - Warranty expiration
120
156
 
121
157
  #### Example Usage
122
158
 
123
159
  ```liquid
124
160
  Proof of Authenticity
125
161
 
126
- Serial Number: {{ proof.serialNumber }}
127
- Status: {{ proof.status }}
162
+ {% if proof.values.serialNumber %}
163
+ Serial Number: {{ proof.values.serialNumber }}
164
+ {% endif %}
128
165
 
129
- {% if proof.claimed %}
130
- Claimed on: {{ proof.claimedAt | date: "%B %d, %Y at %H:%M" }}
166
+ {% if proof.values.status %}
167
+ Status: {{ proof.values.status }}
168
+ {% endif %}
169
+
170
+ {% if proof.claimable %}
171
+ This item is available to claim.
131
172
  {% else %}
132
- This item has not been claimed yet.
173
+ This item has been claimed.
133
174
  {% endif %}
134
175
 
135
- {% if proof.metadata.warrantyExpiry %}
136
- Warranty expires: {{ proof.metadata.warrantyExpiry | date: "%B %d, %Y" }}
176
+ {% if proof.virtual %}
177
+ 🌐 Digital Product
178
+ {% endif %}
179
+
180
+ {% if proof.values.claimedAt %}
181
+ Claimed on: {{ proof.values.claimedAt | date: "%B %d, %Y at %H:%M" }}
182
+ {% endif %}
183
+
184
+ {% if proof.values.warrantyExpiry %}
185
+ Warranty expires: {{ proof.values.warrantyExpiry | date: "%B %d, %Y" }}
137
186
  {% endif %}
138
187
  ```
139
188
 
@@ -145,25 +194,32 @@ A **Contact** represents a customer or user in the system. Contacts are associat
145
194
 
146
195
  | Field | Type | Description |
147
196
  |-------|------|-------------|
148
- | `contact.id` | string | Unique identifier |
149
- | `contact.email` | string | Email address |
150
- | `contact.name` | string | Full name |
197
+ | `contact.contactId` | string | Unique identifier |
198
+ | `contact.orgId` | string | Organization/collection ID |
199
+ | `contact.userId` | string | Linked user ID (if authenticated) |
200
+ | `contact.email` | string | Primary email address |
201
+ | `contact.phone` | string | Primary phone number |
202
+ | `contact.emails` | array | Array of all email addresses |
203
+ | `contact.phones` | array | Array of all phone numbers |
151
204
  | `contact.firstName` | string | First name |
152
205
  | `contact.lastName` | string | Last name |
153
- | `contact.phone` | string | Phone number |
206
+ | `contact.displayName` | string | Display name |
207
+ | `contact.company` | string | Company name |
208
+ | `contact.avatarUrl` | string | Profile picture URL |
154
209
  | `contact.locale` | string | Preferred language/locale (e.g., "en", "de") |
155
210
  | `contact.timezone` | string | Preferred timezone |
156
- | `contact.avatarUrl` | string | Profile picture URL |
157
- | `contact.metadata` | object | Custom key-value metadata |
158
211
  | `contact.tags` | array | Array of tag strings for segmentation |
212
+ | `contact.source` | string | How the contact was created |
213
+ | `contact.notes` | string | Admin notes |
214
+ | `contact.externalIds` | object | External system IDs |
215
+ | `contact.customFields` | object | Custom key-value data |
159
216
  | `contact.createdAt` | datetime | When the contact was created |
160
217
  | `contact.updatedAt` | datetime | When the contact was last updated |
161
- | `contact.lastSeenAt` | datetime | Last activity timestamp |
162
218
 
163
219
  #### Example Usage
164
220
 
165
221
  ```liquid
166
- Hi {{ contact.firstName | default: contact.name | default: "there" }},
222
+ Hi {{ contact.firstName | default: contact.displayName | default: "there" }},
167
223
 
168
224
  {% if contact.locale == "de" %}
169
225
  Willkommen!
@@ -176,6 +232,14 @@ Welcome!
176
232
  {% if contact.phone %}
177
233
  We'll send updates to {{ contact.phone }}.
178
234
  {% endif %}
235
+
236
+ {% if contact.company %}
237
+ Company: {{ contact.company }}
238
+ {% endif %}
239
+
240
+ {% if contact.customFields.vip %}
241
+ 🌟 VIP Customer
242
+ {% endif %}
179
243
  ```
180
244
 
181
245
  ---
@@ -186,20 +250,18 @@ A **User** represents an authenticated account in the system. This is typically
186
250
 
187
251
  | Field | Type | Description |
188
252
  |-------|------|-------------|
189
- | `user.id` | string | Unique identifier |
253
+ | `user.uid` | string | Unique identifier |
190
254
  | `user.email` | string | Email address |
191
- | `user.name` | string | Display name |
192
- | `user.admin` | boolean | Whether user has admin privileges |
193
- | `user.avatarUrl` | string | Profile picture URL |
194
- | `user.createdAt` | datetime | Account creation date |
255
+ | `user.displayName` | string | Display name |
256
+ | `user.accountData` | object | Account-specific data and settings |
195
257
 
196
258
  #### Example Usage
197
259
 
198
260
  ```liquid
199
- Logged in as: {{ user.name }} ({{ user.email }})
261
+ Logged in as: {{ user.displayName }} ({{ user.email }})
200
262
 
201
- {% if user.admin %}
202
- 🔐 You have administrator access.
263
+ {% if user.accountData.preferences.notifications %}
264
+ Notifications are enabled.
203
265
  {% endif %}
204
266
  ```
205
267
 
@@ -212,26 +274,33 @@ An **Attestation** is flexible data attached to a specific proof. It's used to s
212
274
  | Field | Type | Description |
213
275
  |-------|------|-------------|
214
276
  | `attestation.id` | string | Unique identifier |
215
- | `attestation.type` | string | Attestation type (app-defined) |
216
- | `attestation.data` | object | The attestation payload (varies by type) |
217
- | `attestation.createdBy` | string | User ID who created it |
277
+ | `attestation.public` | object | Public attestation data (varies by type) |
278
+ | `attestation.private` | object | Private attestation data (varies by type) |
279
+ | `attestation.proof` | object | Associated proof reference/data |
218
280
  | `attestation.createdAt` | datetime | When the attestation was created |
219
281
  | `attestation.updatedAt` | datetime | When the attestation was last updated |
220
282
 
283
+ **Note**: The `public` and `private` objects contain custom fields based on your use case.
284
+
221
285
  #### Example Usage
222
286
 
223
287
  ```liquid
224
- {% if attestation.type == "warranty_registration" %}
288
+ {% if attestation.public.type == "warranty_registration" %}
225
289
  Warranty Registration Details:
226
290
  - Registered: {{ attestation.createdAt | date: "%B %d, %Y" }}
227
- - Purchase Date: {{ attestation.data.purchaseDate }}
228
- - Store: {{ attestation.data.storeName }}
291
+ - Purchase Date: {{ attestation.public.purchaseDate }}
292
+ - Store: {{ attestation.public.storeName }}
293
+ {% endif %}
294
+
295
+ {% if attestation.public.type == "tasting_note" %}
296
+ 🍷 Tasting Note:
297
+ "{{ attestation.public.notes }}"
298
+ Rating: {{ attestation.public.rating }}/5
229
299
  {% endif %}
230
300
 
231
- {% if attestation.type == "tasting_note" %}
232
- 🍷 Tasting Note by {{ attestation.data.author }}:
233
- "{{ attestation.data.notes }}"
234
- Rating: {{ attestation.data.rating }}/5
301
+ {% if attestation.private.internalNotes %}
302
+ <!-- Private data only visible to admins -->
303
+ Notes: {{ attestation.private.internalNotes }}
235
304
  {% endif %}
236
305
  ```
237
306
 
@@ -357,37 +426,37 @@ Loop variables:
357
426
  ```liquid
358
427
  Subject: Your {{ product.name }} has been registered!
359
428
 
360
- Hi {{ contact.firstName | default: "there" }},
429
+ Hi {{ contact.firstName | default: contact.displayName | default: "there" }},
361
430
 
362
- Great news! Your {{ product.name }} (Serial: {{ proof.serialNumber }})
431
+ Great news! Your {{ product.name }}{% if proof.values.serialNumber %} (Serial: {{ proof.values.serialNumber }}){% endif %}
363
432
  has been successfully registered to your account.
364
433
 
365
- {% if product.metadata.warrantyYears %}
366
- Your warranty is valid for {{ product.metadata.warrantyYears }} years
434
+ {% if product.data.warranty_years %}
435
+ Your warranty is valid for {{ product.data.warranty_years }} years
367
436
  from the date of purchase.
368
437
  {% endif %}
369
438
 
370
- If you have any questions, please contact {{ collection.name }} support.
439
+ If you have any questions, please contact {{ collection.title }} support.
371
440
 
372
441
  Best regards,
373
- The {{ collection.name }} Team
442
+ The {{ collection.title }} Team
374
443
  ```
375
444
 
376
445
  ### Notification Messages
377
446
 
378
447
  ```liquid
379
448
  🎉 {{ contact.firstName }}, your {{ product.name }} is now verified!
380
- Proof ID: {{ proof.shortCode }}
449
+ {% if proof.values.shortCode %}Proof ID: {{ proof.values.shortCode }}{% endif %}
381
450
  ```
382
451
 
383
452
  ### Dynamic Content Blocks
384
453
 
385
454
  ```liquid
386
- {% if proof.metadata.tier == "gold" %}
455
+ {% if proof.values.tier == "gold" %}
387
456
  <div class="gold-benefits">
388
457
  As a Gold member, you get exclusive access to...
389
458
  </div>
390
- {% elsif proof.metadata.tier == "silver" %}
459
+ {% elsif proof.values.tier == "silver" %}
391
460
  <div class="silver-benefits">
392
461
  Your Silver membership includes...
393
462
  </div>
@@ -413,18 +482,19 @@ Proof ID: {{ proof.shortCode }}
413
482
 
414
483
  ## Accessing Nested Data
415
484
 
416
- Use dot notation to access nested fields in metadata or data objects:
485
+ Use dot notation to access nested fields in data objects:
417
486
 
418
487
  ```liquid
419
- {{ product.metadata.manufacturer }}
420
- {{ attestation.data.warranty.expiryDate }}
421
- {{ collection.metadata.social.twitter }}
488
+ {{ product.data.manufacturer }}
489
+ {{ attestation.public.warranty.expiryDate }}
490
+ {{ contact.customFields.vip_level }}
491
+ {{ proof.values.serialNumber }}
422
492
  ```
423
493
 
424
494
  For dynamic keys, you may need to use bracket notation (if supported):
425
495
 
426
496
  ```liquid
427
- {{ product.metadata["custom-field"] }}
497
+ {{ product.data["custom-field"] }}
428
498
  ```
429
499
 
430
500
  ---
@@ -433,29 +503,29 @@ For dynamic keys, you may need to use bracket notation (if supported):
433
503
 
434
504
  1. **Always use `default` filter** for optional fields to avoid blank output:
435
505
  ```liquid
436
- {{ contact.name | default: "Valued Customer" }}
506
+ {{ contact.displayName | default: contact.firstName | default: "Valued Customer" }}
437
507
  ```
438
508
 
439
509
  2. **Escape user-generated content** when outputting as HTML:
440
510
  ```liquid
441
- {{ attestation.data.userNotes | escape }}
511
+ {{ attestation.public.userNotes | escape }}
442
512
  ```
443
513
 
444
514
  3. **Check for existence** before accessing nested data:
445
515
  ```liquid
446
- {% if proof.metadata.warranty %}
447
- Warranty: {{ proof.metadata.warranty.type }}
516
+ {% if proof.values.warranty %}
517
+ Warranty: {{ proof.values.warranty.type }}
448
518
  {% endif %}
449
519
  ```
450
520
 
451
521
  4. **Use meaningful fallbacks** for a better user experience:
452
522
  ```liquid
453
- Hi {{ contact.firstName | default: contact.name | default: "there" }},
523
+ Hi {{ contact.firstName | default: contact.displayName | default: "there" }},
454
524
  ```
455
525
 
456
526
  5. **Format dates appropriately** for the user's locale:
457
527
  ```liquid
458
- {{ proof.claimedAt | date: "%d %B %Y" }}
528
+ {{ proof.createdAt | date: "%d %B %Y" }}
459
529
  ```
460
530
 
461
531
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.3.44",
3
+ "version": "1.3.46",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",