@proveanything/smartlinks 1.3.26 → 1.3.28

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.
@@ -0,0 +1,675 @@
1
+ # Utility Functions
2
+
3
+ The smartlinks SDK includes utilities for common tasks like building portal URLs and validating conditional rendering logic.
4
+
5
+ > **See also:** [API Summary](API_SUMMARY.md) for the complete SDK reference.
6
+
7
+ ## Installation
8
+
9
+ ```typescript
10
+ import { utils } from '@proveanything/smartlinks'
11
+ ```
12
+
13
+ ## Path Builder Utility
14
+
15
+ Builds full portal URLs by default. Pass in objects (collection, product, batch, etc.) and the function extracts what it needs. Uses `collection.portalUrl` as the domain, or defaults to `https://zt.smartlinks.io`.
16
+
17
+ ### Basic Usage
18
+
19
+ ```typescript
20
+ import { utils } from '@proveanything/smartlinks'
21
+
22
+ // Returns full URL by default (uses collection.portalUrl)
23
+ const url = utils.buildPortalPath({
24
+ collection: myCollection, // uses collection.portalUrl or default domain
25
+ product: myProduct // ownGtin is read from product, not overridden
26
+ })
27
+ // Returns: https://portal.smartlinks.io/c/abc123/prod123
28
+
29
+ // Return just the path (no domain)
30
+ const path = utils.buildPortalPath({
31
+ collection: { shortId: 'abc123' },
32
+ productId: 'prod1',
33
+ pathOnly: true // Set to true for path only
34
+ })
35
+ // Returns: /c/abc123/prod1
36
+
37
+ // Or just IDs if you don't have full objects
38
+ const url2 = utils.buildPortalPath({
39
+ collection: { shortId: 'abc123' },
40
+ productId: 'prod1'
41
+ })
42
+ // Returns: https://zt.smartlinks.io/c/abc123/prod1 (default domain)
43
+ ```
44
+
45
+ ## Path Builder Function
46
+
47
+ ### `buildPortalPath(params)`
48
+
49
+ Builds a full portal URL based on the provided parameters. Returns full URL by default using `collection.portalUrl` or `https://zt.smartlinks.io`.
50
+
51
+ **Parameters:**
52
+
53
+ - `collection` (required) - Collection object or `{ shortId: string, portalUrl?: string }`
54
+ Extracts: `shortId` and optional `portalUrl` for base domain
55
+
56
+ - `product` (optional) - Full product object
57
+ Extracts: `id`, `gtin`, and `ownGtin` (ownGtin is a critical product setting - read only, never overridden)
58
+
59
+ - `productId` (optional) - Just a product ID string
60
+ Use this if you don't have the full product object
61
+
62
+ - `batch` (optional) - Batch object
63
+ Extracts: `id` and `expiryDate`
64
+
65
+ - `batchId` (optional) - Just a batch ID string
66
+ Use this if you don't have the full batch object (no expiry date)
67
+
68
+ - `variant` (optional) - Variant object OR just a variant ID string
69
+ - If object: extracts `id`
70
+ - If string: uses as variant ID
71
+
72
+ - `proof` (optional) - Proof object OR just a proof ID string
73
+ - If object: extracts `id`
74
+ - If string: uses as proof ID
75
+
76
+ - `queryParams` (optional) - Additional query parameters object
77
+
78
+ - `pathOnly` (optional, default: `false`) - Return only the path without domain
79
+ Set to `true` to get `/c/abc/prod` instead of `https://domain.com/c/abc/prod`
80
+
81
+ **Path Formats:**
82
+
83
+ - Basic product: `/c/{shortId}/{productId}`
84
+ - With proof: `/c/{shortId}/{productId}/{proofId}`
85
+ - GTIN (own): `/01/{gtin}` (when product.ownGtin is true)
86
+ - GTIN (not own): `/gc/{shortId}/01/{gtin}` (when product.ownGtin is false)
87
+ - With batch: adds `/10/{batchId}` and optionally `?17={expiryDate}`
88
+ - With variant: adds `/22/{variantId}`
89
+
90
+ ## Examples
91
+
92
+ ### Full URL (Default Behavior)
93
+
94
+ ```typescript
95
+ // Returns full URL using collection.portalUrl
96
+ utils.buildPortalPath({
97
+ collection: myCollection, // uses collection.portalUrl
98
+ product: myProduct
99
+ })
100
+ // Returns: https://portal.smartlinks.io/c/abc123/prod123
101
+
102
+ // Without portalUrl, uses default domain
103
+ utils.buildPortalPath({
104
+ collection: { shortId: 'abc123' },
105
+ productId: 'prod1'
106
+ })
107
+ // Returns: https://zt.smartlinks.io/c/abc123/prod1
108
+
109
+ // With proof object
110
+ utils.buildPortalPath({
111
+ collection: myCollection,
112
+ product: myProduct,
113
+ proof: myProof // extracts id
114
+ })
115
+ // Returns: https://portal.smartlinks.io/c/abc123/prod123/proof789
116
+ ```
117
+
118
+ ### Path Only (No Domain)
119
+
120
+ ```typescript
121
+ // Set pathOnly: true to get just the path
122
+ utils.buildPortalPath({
123
+ collection: myCollection,
124
+ product: myProduct,
125
+ pathOnly: true
126
+ })
127
+ // Returns: /c/abc123/prod123
128
+
129
+ // Useful when you need to build your own full URL
130
+ const path = utils.buildPortalPath({
131
+ collection: { shortId: 'abc123' },
132
+ productId: 'prod1',
133
+ pathOnly: true
134
+ })
135
+ const customUrl = `https://mycustomdomain.com${path}`
136
+ // Result: https://mycustomdomain.com/c/abc123/prod1
137
+ ```
138
+
139
+ ### GTIN Paths
140
+
141
+ ```typescript
142
+ // Product owns GTIN (ownGtin read from product.ownGtin)
143
+ utils.buildPortalPath({
144
+ collection: myCollection,
145
+ product: myProduct // if myProduct.ownGtin === true
146
+ })
147
+ // Returns: https://portal.smartlinks.io/01/1234567890123
148
+
149
+ // Product doesn't own GTIN (shared/master GTIN)
150
+ utils.buildPortalPath({
151
+ collection: myCollection,
152
+ product: myProduct // if myProduct.ownGtin === false
153
+ })
154
+ // Returns: https://portal.smartlinks.io/gc/abc123/01/1234567890123
155
+ ```
156
+
157
+ ### With Batch Object or ID
158
+
159
+ ```typescript
160
+ // Batch object includes expiry date
161
+ utils.buildPortalPath({
162
+ collection: myCollection,
163
+ product: myProduct,
164
+ batch: myBatch // extracts id and expiryDate
165
+ })
166
+ // Returns: https://portal.smartlinks.io/01/1234567890123/10/batch456?17=260630
167
+
168
+ // Just batch ID (no expiry)
169
+ utils.buildPortalPath({
170
+ collection: myCollection,
171
+ product: myProduct,
172
+ batchId: 'batch456' // just string, no expiry date
173
+ })
174
+ // Returns: https://portal.smartlinks.io/01/1234567890123/10/batch456
175
+ ```
176
+
177
+ ### With Variant
178
+
179
+ ```typescript
180
+ utils.buildPortalPath({
181
+ collection: myCollection,
182
+ product: myProduct,
183
+ batch: myBatch,
184
+ variant: myVariant // extracts id
185
+ })
186
+ // Returns: https://portal.smartlinks.io/01/1234567890123/10/batch456/22/var1?17=260630
187
+
188
+ // Or just variant ID
189
+ utils.buildPortalPath({
190
+ collection: myCollection,
191
+ product: myProduct,
192
+ variant: 'var1'
193
+ })
194
+ // Returns: https://portal.smartlinks.io/01/1234567890123/22/var1
195
+ ```
196
+
197
+ ### With Query Parameters
198
+
199
+ ```typescript
200
+ utils.buildPortalPath({
201
+ collection: myCollection,
202
+ product: myProduct,
203
+ queryParams: {
204
+ utm_source: 'email',
205
+ utm_campaign: 'launch',
206
+ lang: 'fr'
207
+ }
208
+ })
209
+ // Returns: https://portal.smartlinks.io/c/abc123/prod123?utm_source=email&utm_campaign=launch&lang=fr
210
+ ```
211
+
212
+ ## Use Cases
213
+
214
+ ### QR Code Generation
215
+
216
+ ```typescript
217
+ const qrUrl = utils.buildPortalPath({
218
+ collection: myCollection,
219
+ product: myProduct, // ownGtin read from product
220
+ batch: currentBatch, // includes expiry date
221
+ queryParams: { source: 'qr' }
222
+ })
223
+ // Use qrUrl to generate QR code
224
+ ```
225
+
226
+ ### Email Campaign Links
227
+
228
+ ```typescript
229
+ const emailLink = utils.buildPortalPath({
230
+ collection: myCollection,
231
+ product: featuredProduct,
232
+ queryParams: {
233
+ utm_source: 'newsletter',
234
+ utm_medium: 'email',
235
+ utm_campaign: 'product-launch'
236
+ }
237
+ })
238
+ ```
239
+
240
+ ### NFC Tag Programming
241
+
242
+ ```typescript
243
+ const nfcUrl = utils.buildPortalPath({
244
+ collection: myCollection,
245
+ product: myProduct,
246
+ queryParams: { nfc: '1' }
247
+ })
248
+ // Program NFC tag with nfcUrl
249
+ ```
250
+
251
+ ### Dynamic Navigation
252
+
253
+ ```typescript
254
+ function getProductUrl(product: Product, collection: Collection) {
255
+ return utils.buildPortalPath({
256
+ collection,
257
+ product, // ownGtin automatically read from product
258
+ batch: product.currentBatch
259
+ })
260
+ }
261
+ ```
262
+
263
+ ## TypeScript Support
264
+
265
+ The utility function is fully typed:
266
+
267
+ ```typescript
268
+ import type { PortalPathParams } from '@evrythng/smartlinks'
269
+ import type { Product, Collection } from '@evrythng/smartlinks'
270
+
271
+ const params: PortalPathParams = {
272
+ collection: myCollection,
273
+ product: myProduct
274
+ }
275
+
276
+ const path = utils.buildPortalPath(params)
277
+ ```
278
+
279
+ ## Condition Validation Utility
280
+
281
+ The `validateCondition` function helps determine if content should be shown or hidden based on various criteria like geography, device type, user status, dates, and more.
282
+
283
+ ### Basic Usage
284
+
285
+ ```typescript
286
+ import { utils } from '@proveanything/smartlinks'
287
+
288
+ // Check if user is in EU
289
+ const canShow = await utils.validateCondition({
290
+ condition: {
291
+ type: 'and',
292
+ conditions: [{
293
+ type: 'country',
294
+ useRegions: true,
295
+ regions: ['eu'],
296
+ contains: true
297
+ }]
298
+ },
299
+ user: {
300
+ valid: true,
301
+ location: { country: 'DE' }
302
+ }
303
+ })
304
+
305
+ // Multiple conditions with AND logic
306
+ const showFeature = await utils.validateCondition({
307
+ condition: {
308
+ type: 'and',
309
+ conditions: [
310
+ { type: 'user', userType: 'valid' },
311
+ { type: 'device', displays: ['mobile'], contains: true },
312
+ { type: 'date', dateTest: 'after', afterDate: '2026-01-01' }
313
+ ]
314
+ },
315
+ user: { valid: true },
316
+ stats: { mobile: true }
317
+ })
318
+ ```
319
+
320
+ ### Supported Condition Types
321
+
322
+ #### Country-Based Conditions
323
+ Filter by country codes or predefined regions (EU, EEA, UK, North America, Asia Pacific):
324
+
325
+ ```typescript
326
+ await utils.validateCondition({
327
+ condition: {
328
+ type: 'and',
329
+ conditions: [{
330
+ type: 'country',
331
+ useRegions: true,
332
+ regions: ['eu', 'uk'],
333
+ contains: true // true = show IN these regions, false = hide IN these regions
334
+ }]
335
+ },
336
+ user: { valid: true, location: { country: 'FR' } }
337
+ })
338
+
339
+ // Or specific countries
340
+ await utils.validateCondition({
341
+ condition: {
342
+ type: 'and',
343
+ conditions: [{
344
+ type: 'country',
345
+ countries: ['US', 'CA', 'MX'],
346
+ contains: true
347
+ }]
348
+ },
349
+ user: { valid: true, location: { country: 'US' } }
350
+ })
351
+ ```
352
+
353
+ #### Device & Platform Conditions
354
+ Target specific devices or platforms:
355
+
356
+ ```typescript
357
+ await utils.validateCondition({
358
+ condition: {
359
+ type: 'and',
360
+ conditions: [{
361
+ type: 'device',
362
+ displays: ['ios', 'android', 'mobile'],
363
+ contains: true
364
+ }]
365
+ },
366
+ stats: {
367
+ platform: { ios: true },
368
+ mobile: true
369
+ }
370
+ })
371
+ ```
372
+
373
+ Supported displays: `'android'`, `'ios'`, `'win'`, `'mac'`, `'desktop'`, `'mobile'`
374
+
375
+ #### User Status Conditions
376
+ Check authentication and permissions:
377
+
378
+ ```typescript
379
+ // Logged in users only
380
+ await utils.validateCondition({
381
+ condition: {
382
+ type: 'and',
383
+ conditions: [{ type: 'user', userType: 'valid' }]
384
+ },
385
+ user: { valid: true, uid: 'user123' }
386
+ })
387
+
388
+ // Collection admins only
389
+ await utils.validateCondition({
390
+ condition: {
391
+ type: 'and',
392
+ conditions: [{ type: 'user', userType: 'admin' }]
393
+ },
394
+ user: { valid: true, uid: 'user123' },
395
+ collection: { id: 'col1', roles: { 'user123': 'admin' } }
396
+ })
397
+
398
+ // Proof owners only
399
+ await utils.validateCondition({
400
+ condition: {
401
+ type: 'and',
402
+ conditions: [{ type: 'user', userType: 'owner' }]
403
+ },
404
+ user: { valid: true, uid: 'user123' },
405
+ proof: { userId: 'user123' }
406
+ })
407
+ ```
408
+
409
+ User types: `'valid'`, `'invalid'`, `'owner'`, `'admin'`, `'group'`
410
+
411
+ #### Date & Time Conditions
412
+ Show/hide content based on dates:
413
+
414
+ ```typescript
415
+ // Show after specific date
416
+ await utils.validateCondition({
417
+ condition: {
418
+ type: 'and',
419
+ conditions: [{
420
+ type: 'date',
421
+ dateTest: 'after',
422
+ afterDate: '2026-06-01'
423
+ }]
424
+ }
425
+ })
426
+
427
+ // Show during date range
428
+ await utils.validateCondition({
429
+ condition: {
430
+ type: 'and',
431
+ conditions: [{
432
+ type: 'date',
433
+ dateTest: 'between',
434
+ rangeDate: ['2026-01-01', '2026-12-31']
435
+ }]
436
+ }
437
+ })
438
+ ```
439
+
440
+ Date tests: `'before'`, `'after'`, `'between'`
441
+
442
+ #### Product & Tag Conditions
443
+ Filter by specific products or product tags:
444
+
445
+ ```typescript
446
+ // Specific products
447
+ await utils.validateCondition({
448
+ condition: {
449
+ type: 'and',
450
+ conditions: [{
451
+ type: 'product',
452
+ productIds: ['prod1', 'prod2'],
453
+ contains: true
454
+ }]
455
+ },
456
+ product: { id: 'prod1' }
457
+ })
458
+
459
+ // Products with specific tags
460
+ await utils.validateCondition({
461
+ condition: {
462
+ type: 'and',
463
+ conditions: [{
464
+ type: 'tag',
465
+ tags: ['premium', 'featured'],
466
+ contains: true
467
+ }]
468
+ },
469
+ product: { id: 'prod1', tags: { premium: true } }
470
+ })
471
+ ```
472
+
473
+ #### Item Status Conditions
474
+ Check proof/item status:
475
+
476
+ ```typescript
477
+ await utils.validateCondition({
478
+ condition: {
479
+ type: 'and',
480
+ conditions: [{
481
+ type: 'itemStatus',
482
+ statusType: 'isClaimable'
483
+ }]
484
+ },
485
+ proof: { claimable: true }
486
+ })
487
+ ```
488
+
489
+ Status types: `'isClaimable'`, `'notClaimable'`, `'noProof'`, `'hasProof'`, `'isVirtual'`, `'notVirtual'`
490
+
491
+ #### Version Conditions
492
+ For A/B testing or versioned content:
493
+
494
+ ```typescript
495
+ await utils.validateCondition({
496
+ condition: {
497
+ type: 'and',
498
+ conditions: [{
499
+ type: 'version',
500
+ versions: ['v2', 'v3'],
501
+ contains: true
502
+ }]
503
+ },
504
+ stats: { version: 'v2' }
505
+ })
506
+ ```
507
+
508
+ #### Value Comparison Conditions
509
+ Compare custom field values:
510
+
511
+ ```typescript
512
+ await utils.validateCondition({
513
+ condition: {
514
+ type: 'and',
515
+ conditions: [{
516
+ type: 'value',
517
+ field: 'product.inventory.quantity', // dot notation
518
+ fieldType: 'integer',
519
+ validationType: 'greater',
520
+ value: 10
521
+ }]
522
+ },
523
+ product: { inventory: { quantity: 25 } }
524
+ })
525
+ ```
526
+
527
+ Validation types: `'equal'`, `'not'`, `'greater'`, `'less'`
528
+ Field types: `'string'`, `'boolean'`, `'integer'`, `'number'`
529
+
530
+ #### Geofence Conditions
531
+ Location-based restrictions using bounding boxes:
532
+
533
+ ```typescript
534
+ await utils.validateCondition({
535
+ condition: {
536
+ type: 'and',
537
+ conditions: [{
538
+ type: 'geofence',
539
+ top: 50.0,
540
+ bottom: 40.0,
541
+ left: -10.0,
542
+ right: 5.0,
543
+ contains: true // true = inside box, false = outside box
544
+ }]
545
+ },
546
+ user: {
547
+ valid: true,
548
+ location: { latitude: 45.0, longitude: 0.0 }
549
+ }
550
+ })
551
+ ```
552
+
553
+ #### Nested Conditions
554
+ Reference other condition sets for reusability:
555
+
556
+ ```typescript
557
+ await utils.validateCondition({
558
+ condition: {
559
+ type: 'and',
560
+ conditions: [{
561
+ type: 'condition',
562
+ conditionId: 'mobile-users-condition',
563
+ passes: true // true = must pass, false = must fail
564
+ }]
565
+ },
566
+ conditionId: 'mobile-users-condition',
567
+ fetchCondition: async (collectionId, conditionId) => {
568
+ // Your logic to fetch condition by ID
569
+ return { type: 'and', conditions: [...] }
570
+ },
571
+ collection: { id: 'col1' }
572
+ })
573
+ ```
574
+
575
+ ### Combining Conditions (AND/OR Logic)
576
+
577
+ ```typescript
578
+ // AND logic (all must pass)
579
+ await utils.validateCondition({
580
+ condition: {
581
+ type: 'and',
582
+ conditions: [
583
+ { type: 'user', userType: 'valid' },
584
+ { type: 'device', displays: ['mobile'], contains: true },
585
+ { type: 'country', useRegions: true, regions: ['eu'], contains: true }
586
+ ]
587
+ },
588
+ user: { valid: true, location: { country: 'FR' } },
589
+ stats: { mobile: true }
590
+ })
591
+
592
+ // OR logic (any can pass)
593
+ await utils.validateCondition({
594
+ condition: {
595
+ type: 'or',
596
+ conditions: [
597
+ { type: 'user', userType: 'admin' },
598
+ { type: 'product', productIds: ['featured1'], contains: true }
599
+ ]
600
+ },
601
+ user: { valid: false },
602
+ product: { id: 'featured1' }
603
+ })
604
+ ```
605
+
606
+ ### Common Use Cases
607
+
608
+ #### Page Rendering Control
609
+ ```typescript
610
+ const showPremiumContent = await utils.validateCondition({
611
+ condition: {
612
+ type: 'and',
613
+ conditions: [
614
+ { type: 'user', userType: 'valid' },
615
+ { type: 'tag', tags: ['premium'], contains: true }
616
+ ]
617
+ },
618
+ user: { valid: true },
619
+ product: { id: 'prod1', tags: { premium: true } }
620
+ })
621
+
622
+ if (showPremiumContent) {
623
+ // Render premium content
624
+ }
625
+ ```
626
+
627
+ #### Regional Feature Rollout
628
+ ```typescript
629
+ const showNewFeature = await utils.validateCondition({
630
+ condition: {
631
+ type: 'and',
632
+ conditions: [
633
+ { type: 'country', useRegions: true, regions: ['northamerica'], contains: true },
634
+ { type: 'date', dateTest: 'after', afterDate: '2026-03-01' }
635
+ ]
636
+ },
637
+ user: { valid: true, location: { country: 'US' } }
638
+ })
639
+ ```
640
+
641
+ #### Mobile-Only Features
642
+ ```typescript
643
+ const showMobileFeature = await utils.validateCondition({
644
+ condition: {
645
+ type: 'and',
646
+ conditions: [
647
+ { type: 'device', displays: ['mobile'], contains: true }
648
+ ]
649
+ },
650
+ stats: { mobile: true, platform: { ios: true } }
651
+ })
652
+ ```
653
+
654
+ ### Available Regions
655
+
656
+ Predefined regions for country conditions:
657
+ - **eu** - European Union (27 member states)
658
+ - **eea** - European Economic Area (EU + EFTA countries)
659
+ - **uk** - United Kingdom
660
+ - **northamerica** - US, CA, MX
661
+ - **asiapacific** - AU, NZ, JP, KR, SG, HK, TW, TH, MY, PH, ID, VN, IN
662
+
663
+ ## More Examples
664
+
665
+ See [examples/utils-demo.ts](../examples/utils-demo.ts) for comprehensive examples.
666
+
667
+ ## Related Documentation
668
+
669
+ - **[API Summary](API_SUMMARY.md)** - Complete SDK reference with all namespaces and functions
670
+ - **[QR Codes](API_SUMMARY.md#qr)** - QR code lookup functions that work with generated paths
671
+ - **[NFC](API_SUMMARY.md#nfc)** - NFC tag claiming and validation
672
+ - **[Collections](API_SUMMARY.md#collection)** - Collection management functions
673
+ - **[Products](API_SUMMARY.md#product)** - Product CRUD operations
674
+ - **[Batches](API_SUMMARY.md#batch)** - Batch management
675
+ - **[Proofs](API_SUMMARY.md#proof)** - Proof creation and claiming
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ export * from "./types";
4
4
  export { iframe } from "./iframe";
5
5
  export * as cache from './cache';
6
6
  export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
7
+ export * as utils from './utils';
8
+ export type { PortalPathParams, ConditionParams, ConditionSet, Condition, UserInfo, ProductInfo, ProofInfo, CollectionInfo, } from './utils';
7
9
  export type { LoginResponse, VerifyTokenResponse, AccountInfoResponse, } from "./api/auth";
8
10
  export type { UserAccountRegistrationRequest, } from "./types/auth";
9
11
  export type { CommunicationEvent, CommsQueryByUser, CommsRecipientIdsQuery, CommsRecipientsWithoutActionQuery, CommsRecipientsWithActionQuery, RecipientId, RecipientWithOutcome, LogCommunicationEventBody, LogBulkCommunicationEventsBody, AppendResult, AppendBulkResult, CommsSettings, TopicConfig, CommsSettingsGetResponse, CommsSettingsPatchBody, CommsPublicTopicsResponse, UnsubscribeQuery, UnsubscribeResponse, CommsConsentUpsertRequest, CommsPreferencesUpsertRequest, CommsSubscribeRequest, CommsSubscribeResponse, CommsSubscriptionCheckQuery, CommsSubscriptionCheckResponse, CommsListMethodsQuery, CommsListMethodsResponse, RegisterEmailMethodRequest, RegisterSmsMethodRequest, RegisterMethodResponse, SubscriptionsResolveRequest, SubscriptionsResolveResponse, } from "./types/comms";
package/dist/index.js CHANGED
@@ -9,3 +9,5 @@ import * as cache_1 from './cache';
9
9
  export { cache_1 as cache };
10
10
  // IframeResponder (also exported via iframe namespace)
11
11
  export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
12
+ import * as utils_1 from './utils';
13
+ export { utils_1 as utils };