@proveanything/smartlinks 1.3.25 → 1.3.27

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