@proveanything/smartlinks 1.15.6 → 1.15.10
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/docs/API_SUMMARY.md +46 -4
- package/dist/docs/appConfig.md +33 -54
- package/dist/docs/containers.md +2 -0
- package/dist/docs/item-context.md +184 -0
- package/dist/docs/utils.md +35 -2
- package/dist/docs/widgets.md +9 -0
- package/dist/openapi.yaml +62 -8
- package/dist/types/appConfiguration.d.ts +0 -1
- package/dist/types/collection.d.ts +0 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/itemContext.d.ts +30 -0
- package/dist/types/itemContext.js +2 -0
- package/dist/types/nfc.d.ts +38 -0
- package/dist/types/widgets.d.ts +15 -0
- package/dist/utils/conditions.d.ts +33 -3
- package/dist/utils/conditions.js +34 -1
- package/docs/API_SUMMARY.md +46 -4
- package/docs/appConfig.md +33 -54
- package/docs/containers.md +2 -0
- package/docs/item-context.md +184 -0
- package/docs/utils.md +35 -2
- package/docs/widgets.md +9 -0
- package/openapi.yaml +62 -8
- package/package.json +1 -1
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.10 | Generated: 2026-07-19T13:35:07.171Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -1447,7 +1447,6 @@ interface AppConfigSettings {
|
|
|
1447
1447
|
addOns?: string[]
|
|
1448
1448
|
requestedBasePlanId?: string
|
|
1449
1449
|
itemRecordMode?: 'registered' | 'owned' | null
|
|
1450
|
-
virtualItemsEnabled?: boolean
|
|
1451
1450
|
system?: SystemBlock
|
|
1452
1451
|
}
|
|
1453
1452
|
```
|
|
@@ -3698,8 +3697,6 @@ interface Collection {
|
|
|
3698
3697
|
secondaryColor?: string
|
|
3699
3698
|
portalUrl?: string // URL for the collection's portal (if applicable)
|
|
3700
3699
|
allowAutoGenerateClaims?: boolean
|
|
3701
|
-
variants: boolean // does this collection support variants?
|
|
3702
|
-
batches: boolean // does this collection support batches?
|
|
3703
3700
|
defaultAuthKitId: string // default auth kit for this collection, used for auth
|
|
3704
3701
|
}
|
|
3705
3702
|
```
|
|
@@ -5881,6 +5878,23 @@ interface InteractionEventContext {
|
|
|
5881
5878
|
|
|
5882
5879
|
**EffectConfig** = ``
|
|
5883
5880
|
|
|
5881
|
+
### itemContext
|
|
5882
|
+
|
|
5883
|
+
**ItemContext** (interface)
|
|
5884
|
+
```typescript
|
|
5885
|
+
interface ItemContext {
|
|
5886
|
+
isAuthentic: boolean
|
|
5887
|
+
status: ItemContextStatus
|
|
5888
|
+
source: 'nfc' | 'serial'
|
|
5889
|
+
errorMessage?: string
|
|
5890
|
+
isRescan?: boolean
|
|
5891
|
+
tag?: TagContext
|
|
5892
|
+
checkedAt: number
|
|
5893
|
+
}
|
|
5894
|
+
```
|
|
5895
|
+
|
|
5896
|
+
**ItemContextStatus** = ``
|
|
5897
|
+
|
|
5884
5898
|
### jobs
|
|
5885
5899
|
|
|
5886
5900
|
**Job** (interface)
|
|
@@ -6411,6 +6425,24 @@ interface NfcClaimTagRequest {
|
|
|
6411
6425
|
}
|
|
6412
6426
|
```
|
|
6413
6427
|
|
|
6428
|
+
**TagContext** (interface)
|
|
6429
|
+
```typescript
|
|
6430
|
+
interface TagContext {
|
|
6431
|
+
status: TagStatus
|
|
6432
|
+
tagId?: string
|
|
6433
|
+
claimSetId?: string
|
|
6434
|
+
codeId?: string
|
|
6435
|
+
count?: number
|
|
6436
|
+
previousCount?: number
|
|
6437
|
+
data?: Record<string, unknown>
|
|
6438
|
+
source: 'nfc-validate' | 'nfc-lookup' | 'tag-index' | 'none'
|
|
6439
|
+
validatedAt: number
|
|
6440
|
+
errorMessage?: string
|
|
6441
|
+
}
|
|
6442
|
+
```
|
|
6443
|
+
|
|
6444
|
+
**TagStatus** = `'valid' | 'rescan' | 'invalid' | 'error'`
|
|
6445
|
+
|
|
6414
6446
|
### order
|
|
6415
6447
|
|
|
6416
6448
|
**OrderItem** (interface)
|
|
@@ -7695,6 +7727,15 @@ interface SmartLinksWidgetProps {
|
|
|
7695
7727
|
* accepted for backward compatibility.
|
|
7696
7728
|
onNavigate?: (request: NavigationRequest | string) => void
|
|
7697
7729
|
publicPortalUrl?: string
|
|
7730
|
+
* Authenticity context for the specific item (proof) the URL points at,
|
|
7731
|
+
* resolved via an NFC tap or a serial proof URL. `undefined` for
|
|
7732
|
+
* collection- and product-only URLs, where there's no item to verify.
|
|
7733
|
+
* See docs/item-context.md.
|
|
7734
|
+
itemContext?: ItemContext
|
|
7735
|
+
* @deprecated Use `itemContext.tag` instead. Kept for one release for
|
|
7736
|
+
* backward compatibility with scanner-aware apps that read raw NFC/SUN
|
|
7737
|
+
* data directly. See docs/item-context.md.
|
|
7738
|
+
tag?: TagContext
|
|
7698
7739
|
size?: 'compact' | 'standard' | 'large'
|
|
7699
7740
|
lang?: string
|
|
7700
7741
|
translations?: Record<string, string>
|
|
@@ -7864,6 +7905,7 @@ interface ConditionParams {
|
|
|
7864
7905
|
product?: ProductInfo
|
|
7865
7906
|
proof?: ProofInfo
|
|
7866
7907
|
collection?: CollectionInfo
|
|
7908
|
+
itemContext?: ItemContext
|
|
7867
7909
|
stats?: StatsInfo
|
|
7868
7910
|
fetchCondition?: (collectionId: string, conditionId: string) => Promise<ConditionSet | null>
|
|
7869
7911
|
getLocation?: () => Promise<{ latitude: number; longitude: number }>
|
package/dist/docs/appConfig.md
CHANGED
|
@@ -36,7 +36,6 @@ export interface AppConfigSettings {
|
|
|
36
36
|
|
|
37
37
|
/** How the account handles individual physical items. */
|
|
38
38
|
itemRecordMode?: 'registered' | 'owned' | null;
|
|
39
|
-
virtualItemsEnabled?: boolean;
|
|
40
39
|
|
|
41
40
|
// ── SYSTEM-OWNED, PUBLIC (reconcile writes; client read-only) ─────
|
|
42
41
|
/** Resolved entitlement truth. Read this to gate features. */
|
|
@@ -245,6 +244,25 @@ Always resolve flags through `appConfiguration.isFeatureEnabled(collectionId, fl
|
|
|
245
244
|
if you already have a `system` block from elsewhere — never read
|
|
246
245
|
`cfg.system.features[flag]` directly.
|
|
247
246
|
|
|
247
|
+
#### 4.4.1 Common feature flags
|
|
248
|
+
|
|
249
|
+
`features` is an open map — any microapp can define its own key — but
|
|
250
|
+
these are the major, cross-cutting flags most integrations will care
|
|
251
|
+
about:
|
|
252
|
+
|
|
253
|
+
| Flag | Gates |
|
|
254
|
+
| -------------- | ---------------------------------------------------------------------- |
|
|
255
|
+
| `analytics` | Analytics surfaces (dashboards, reporting, event exploration). |
|
|
256
|
+
| `crm` | Customer interaction / CRM — contact records, broadcasts, segments. Sub-features hang off the `crm.*` dotted prefix, e.g. `crm.broadcasts`, `crm.segments`. |
|
|
257
|
+
| `hub` | The Hub module. |
|
|
258
|
+
| `portal` | QR code scan portals. |
|
|
259
|
+
| `virtualItems` | Virtual items — algorithmic per-item IDs with no persistent row (battery serials, scan-to-collect points, bulk QR sheets). Replaces the old root-level `virtualItemsEnabled` boolean, which has been removed (§8) — resolve this like any other feature flag, not as a separate config key. Independent of `itemRecordMode`. |
|
|
260
|
+
| `batches` | Batch support. Used to be the `Collection.batches` boolean; that field has been removed — this flag is now the only source of truth. |
|
|
261
|
+
| `variants` | Variant support. Used to be the `Collection.variants` boolean; that field has been removed — this flag is now the only source of truth. |
|
|
262
|
+
|
|
263
|
+
Same resolution rule as any flag (§4.4) — don't read these off
|
|
264
|
+
`cfg.system.features` directly, always go through `isFeatureEnabled()`.
|
|
265
|
+
|
|
248
266
|
### 4.5 `meters`
|
|
249
267
|
|
|
250
268
|
Map keyed by add-on key. Apps reporting usage should call the
|
|
@@ -407,18 +425,14 @@ with a stale local copy.
|
|
|
407
425
|
Clients MUST NOT write directly to `system` or `systemPrivate`.
|
|
408
426
|
`entitlements-reconcile` merges user-owned fields through untouched.
|
|
409
427
|
|
|
410
|
-
## 8. Item handling (`itemRecordMode
|
|
428
|
+
## 8. Item handling (`itemRecordMode`)
|
|
411
429
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
them.
|
|
430
|
+
A root-level, user-owned key describing how the account treats
|
|
431
|
+
individual physical items. Reconcile never touches it.
|
|
415
432
|
|
|
416
|
-
|
|
433
|
+
Two axes (do NOT conflate):
|
|
417
434
|
|
|
418
|
-
1. **
|
|
419
|
-
IDs, no per-item row. Battery serials, scan-to-collect points,
|
|
420
|
-
bulk QR sheets. Can be on with `itemRecordMode` off.
|
|
421
|
-
2. **Item records** — `itemRecordMode`. Materialise a persistent row
|
|
435
|
+
1. **Item records** — `itemRecordMode`. Materialise a persistent row
|
|
422
436
|
per physical item. `null` / missing = **disabled** (no separate
|
|
423
437
|
`"none"` enum — reads collapse to `mode === "off"` via the
|
|
424
438
|
`useItemRecordMode()` hook).
|
|
@@ -426,7 +440,15 @@ Three axes (do NOT conflate):
|
|
|
426
440
|
provenance without a customer relationship.
|
|
427
441
|
- `"owned"` — tag row + owner + interaction history. Requires
|
|
428
442
|
`basePlanId >= "engage"`. Enables claim / transfer / CRM.
|
|
429
|
-
|
|
443
|
+
2. **CRM** — downstream consequence of `owned`; not a peer flag.
|
|
444
|
+
|
|
445
|
+
**Virtual items are not part of this key.** Algorithmic IDs with no
|
|
446
|
+
per-item row (battery serials, scan-to-collect points, bulk QR sheets)
|
|
447
|
+
used to be a separate root-level `virtualItemsEnabled: boolean` — that
|
|
448
|
+
field has been removed. Virtual items are now gated by the `virtualItems`
|
|
449
|
+
feature flag under `system.features` (§4.4.1), same as every other
|
|
450
|
+
feature — resolve it with `isFeatureEnabled(collectionId, 'virtualItems')`.
|
|
451
|
+
It's independent of `itemRecordMode` and can be on with item records off.
|
|
430
452
|
|
|
431
453
|
**Billing:** the usage ledger stamps `tier` at event time from
|
|
432
454
|
`itemRecordMode`. Writes with `mode === null` MUST be rejected. Mode
|
|
@@ -447,46 +469,3 @@ CRM visibility), not just a capability flag.
|
|
|
447
469
|
features yet.
|
|
448
470
|
- `systemOverrides` (old root-level key) → `systemPrivate.overrides`.
|
|
449
471
|
One-off backfill moves the block and re-runs reconcile.
|
|
450
|
-
|
|
451
|
-
## 10. Change log
|
|
452
|
-
|
|
453
|
-
- **2026-07-17**: Initial contract published; `appConfig.system` becomes
|
|
454
|
-
canonical entitlements source of truth. `entitlements` group deprecated.
|
|
455
|
-
- **2026-07-17**: Added `systemOverrides` for additive comps; reconcile
|
|
456
|
-
records `system.appliedOverrides`.
|
|
457
|
-
- **2026-07-18**: Renamed `systemOverrides` → `systemPrivate.overrides`
|
|
458
|
-
and formalised the three-tier access model (user / system /
|
|
459
|
-
systemPrivate). API strips `systemPrivate` for non-admin readers.
|
|
460
|
-
Added `system.accountType` derived from
|
|
461
|
-
`systemPrivate.overrides.accountType`. Added top-of-doc TypeScript
|
|
462
|
-
definition.
|
|
463
|
-
- **2026-07-18**: Added SDK helpers `appConfiguration.getAppConfig()`
|
|
464
|
-
(typed + cached), `isFeatureEnabled()` (async, cache-backed feature
|
|
465
|
-
gate), and `isFeatureEnabledSync()` (cache-only, for callers that can't
|
|
466
|
-
await). Renamed the exported TypeScript type to `AppConfigSettings`
|
|
467
|
-
to avoid a name collision with the existing per-module `AppConfig`
|
|
468
|
-
type in `src/types/collection.ts`.
|
|
469
|
-
- **2026-07-18**: `/public/collection/:id/app/config` (`collection.getAppsConfig()`)
|
|
470
|
-
now returns the `appConfig` entitlements data (`system`, `addOns`,
|
|
471
|
-
`requestedBasePlanId`, `itemRecordMode`, `virtualItemsEnabled`) merged
|
|
472
|
-
alongside its existing app-catalog `apps[]`. `appConfiguration.getAppConfig()`
|
|
473
|
-
was repointed at this endpoint (dropping its `admin` option — this endpoint
|
|
474
|
-
is public-only); the settings-group endpoint (`appConfiguration.getConfig({ appId: 'appConfig' })`)
|
|
475
|
-
is still the only way to reach `systemPrivate`. See §3.1 for why the two
|
|
476
|
-
endpoints' `apps[]` fields are shaped differently and shouldn't be conflated.
|
|
477
|
-
- **2026-07-18**: `system.features` changed from "only truthy flags appear"
|
|
478
|
-
to explicit `Record<string, boolean>` overrides. Added the `accountType`
|
|
479
|
-
default rule (§4.4): enterprise accounts default every flag to on unless
|
|
480
|
-
explicitly `false`; standard accounts default off unless explicitly `true`.
|
|
481
|
-
Added `appConfiguration.resolveFeature(system, flag)` — the pure resolver
|
|
482
|
-
behind `isFeatureEnabled()`/`isFeatureEnabledSync()`, exposed only for the
|
|
483
|
-
rare case of already holding a `system` block from somewhere other than
|
|
484
|
-
`getAppConfig()`. There is no separate admin feature-check path — `system`
|
|
485
|
-
is public data on every read, so admin surfaces should call
|
|
486
|
-
`isFeatureEnabled()` like everything else. Reading `cfg.system.features[flag]`
|
|
487
|
-
directly is no longer correct for enterprise accounts — always resolve
|
|
488
|
-
through one of these three.
|
|
489
|
-
- **2026-07-19**: Corrected flag naming convention (§4.4): flags are
|
|
490
|
-
camelCase (e.g. `customDomain`), not snake_case, and may use a dotted
|
|
491
|
-
grouping prefix for a topic/module (e.g. `crm.broadcasts`). Updated all
|
|
492
|
-
examples in this doc accordingly.
|
package/dist/docs/containers.md
CHANGED
|
@@ -231,6 +231,8 @@ All standard widget props apply:
|
|
|
231
231
|
| `productId` | string | ❌ | Product context |
|
|
232
232
|
| `proofId` | string | ❌ | Proof context |
|
|
233
233
|
| `user` | object | ❌ | Current user info |
|
|
234
|
+
| `itemContext` | `ItemContext` | ❌ | Authenticity context for the specific item (proof) the URL points at; `undefined` for collection/product-only URLs — see [item-context.md](item-context.md) |
|
|
235
|
+
| `tag` | `TagContext` | ❌ | **Deprecated** — use `itemContext.tag` instead. Kept for one release. |
|
|
234
236
|
| `onNavigate` | function | ❌ | Navigation callback (accepts `NavigationRequest` or legacy string) |
|
|
235
237
|
| `size` | string | ❌ | `"compact"`, `"standard"`, or `"large"` |
|
|
236
238
|
| `lang` | string | ❌ | Language code (e.g., `"en"`) |
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Item Context (container prop)
|
|
2
|
+
|
|
3
|
+
> **Copy this file into `node_modules/@proveanything/smartlinks/docs/item-context.md`** in the published SDK package.
|
|
4
|
+
|
|
5
|
+
When the URL points at a specific item — either a **serial proof URL** or an
|
|
6
|
+
**NFC tap** — the portal derives an `ItemContext` describing what it found
|
|
7
|
+
and hands it to the container as the **`itemContext`** prop.
|
|
8
|
+
|
|
9
|
+
For collection- and product-only URLs there is no item to describe, so
|
|
10
|
+
`itemContext` is `undefined`. Sub-apps that don't care about authenticity
|
|
11
|
+
can simply ignore the prop.
|
|
12
|
+
|
|
13
|
+
The prop is named `itemContext` (not `item`) to make clear it's *context
|
|
14
|
+
about the item*, not the item record itself. The underlying record is
|
|
15
|
+
passed separately via `proof`, `product` and `collection` props.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
export default function MyContainer({ itemContext, collection, product, proof }) {
|
|
19
|
+
if (!itemContext) {
|
|
20
|
+
// Collection or product view — nothing to verify.
|
|
21
|
+
return <BrowseView collection={collection} product={product} />;
|
|
22
|
+
}
|
|
23
|
+
if (itemContext.isAuthentic) {
|
|
24
|
+
return <RealContent proof={proof} />;
|
|
25
|
+
}
|
|
26
|
+
if (itemContext.status === 'not-found' || itemContext.status === 'invalid') {
|
|
27
|
+
return <NotGenuine message={itemContext.errorMessage} />;
|
|
28
|
+
}
|
|
29
|
+
// status === 'error' — transport failure
|
|
30
|
+
return <TransientError message={itemContext.errorMessage} />;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Shape
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
interface ItemContext {
|
|
40
|
+
/** True only for `valid` and `rescan`. Read this for the simple case. */
|
|
41
|
+
isAuthentic: boolean;
|
|
42
|
+
|
|
43
|
+
status:
|
|
44
|
+
| 'valid' // genuine
|
|
45
|
+
| 'rescan' // genuine, but this exact NFC tap has been seen before
|
|
46
|
+
| 'invalid' // SDK rejected the NFC payload (bad crypto)
|
|
47
|
+
| 'not-found' // identifier well-formed but 404
|
|
48
|
+
| 'error'; // transport / network failure — cannot tell
|
|
49
|
+
|
|
50
|
+
source: 'nfc' | 'serial';
|
|
51
|
+
|
|
52
|
+
/** Set for `invalid` / `not-found` / `error`. Safe to display. */
|
|
53
|
+
errorMessage?: string;
|
|
54
|
+
|
|
55
|
+
/** True when SDK reports this exact NFC tap was seen before. */
|
|
56
|
+
isRescan?: boolean;
|
|
57
|
+
|
|
58
|
+
/** Full NFC payload (SUN counter, tagId, claimSetId, codeId, mirror…). Present only when source === 'nfc'. */
|
|
59
|
+
tag?: TagContext;
|
|
60
|
+
|
|
61
|
+
/** Epoch ms when the check was derived. */
|
|
62
|
+
checkedAt: number;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Also exported from `@proveanything/smartlinks` as `ItemContext` /
|
|
67
|
+
`ItemContextStatus` (`src/types/itemContext.ts`). The nested `TagContext`
|
|
68
|
+
shape is unchanged (`src/types/nfc.ts`).
|
|
69
|
+
|
|
70
|
+
## When `itemContext` is present
|
|
71
|
+
|
|
72
|
+
| URL points at | itemContext |
|
|
73
|
+
| ---------------------------------------------- | -------------------- |
|
|
74
|
+
| Collection root | `undefined` |
|
|
75
|
+
| Product page (no proof) | `undefined` |
|
|
76
|
+
| Serial proof URL (any outcome) | present, source `serial` |
|
|
77
|
+
| NFC tap (any outcome) | present, source `nfc` |
|
|
78
|
+
|
|
79
|
+
## Status matrix (when present)
|
|
80
|
+
|
|
81
|
+
| Situation | source | status | isAuthentic |
|
|
82
|
+
| ----------------------------------------------------- | -------- | ------------- | :---------: |
|
|
83
|
+
| Fresh NFC tap, SUN counter validated | `nfc` | `valid` | ✅ |
|
|
84
|
+
| Same NFC tap replayed / refresh | `nfc` | `rescan` | ✅ |
|
|
85
|
+
| NFC payload rejected (bad crypto / bad params) | `nfc` | `invalid` | ❌ |
|
|
86
|
+
| Serial proof URL, proof fetched OK | `serial` | `valid` | ✅ |
|
|
87
|
+
| Serial proof URL, proof returned 404 | `serial` | `not-found` | ❌ |
|
|
88
|
+
| Network / 5xx while resolving proof or NFC | (either) | `error` | ❌ |
|
|
89
|
+
|
|
90
|
+
`isAuthentic === (status === 'valid' || status === 'rescan')`.
|
|
91
|
+
|
|
92
|
+
## How it's delivered
|
|
93
|
+
|
|
94
|
+
**Direct-mounted containers** (default public mode):
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
type Props = {
|
|
98
|
+
collection: Collection;
|
|
99
|
+
product?: Product;
|
|
100
|
+
proof?: Proof;
|
|
101
|
+
itemContext?: ItemContext; // ← only when URL targets a specific item
|
|
102
|
+
// …plus the normal navigation / theme / auth props
|
|
103
|
+
};
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
NFC-aware apps that need the raw SUN / tag payload read `itemContext.tag`.
|
|
107
|
+
|
|
108
|
+
**Iframe containers**: the portal serialises `ItemContext` to JSON, base64-
|
|
109
|
+
encodes it (UTF-8 safe) and appends it as an `itemContext` query param on
|
|
110
|
+
the iframe URL — omitted entirely when there is no item context:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
function readItemContext(): ItemContext | undefined {
|
|
114
|
+
const raw = new URLSearchParams(location.search).get('itemContext');
|
|
115
|
+
if (!raw) return undefined;
|
|
116
|
+
try { return JSON.parse(decodeURIComponent(escape(atob(raw)))); }
|
|
117
|
+
catch { return undefined; }
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Guarantees
|
|
122
|
+
|
|
123
|
+
- `itemContext` is present on every container mount whose URL targeted an
|
|
124
|
+
item (proof or NFC tap). Absent otherwise.
|
|
125
|
+
- `SL.nfc.validate` is called at most once per `(claimSetId, codeId)` per
|
|
126
|
+
browser session — SUN counters are never double-consumed on refresh.
|
|
127
|
+
- SSR uses `SL.nfc.lookupTag` (non-consuming) only.
|
|
128
|
+
- `isAuthentic === true` implies the SDK either validated an NFC payload
|
|
129
|
+
or returned a full proof document. It is never inferred from URL shape.
|
|
130
|
+
- The portal will **not** render its full-page error surface for a 404 on
|
|
131
|
+
a proof — that surfaces as `status: 'not-found'` on `itemContext`, and
|
|
132
|
+
the container is still mounted so it can present the message its own way.
|
|
133
|
+
|
|
134
|
+
## What containers should do
|
|
135
|
+
|
|
136
|
+
| Status | Recommended UI |
|
|
137
|
+
| ------------- | ------------------------------------------------------------------ |
|
|
138
|
+
| `valid` | Full experience. Show ownership, actions, etc. |
|
|
139
|
+
| `rescan` | Same as `valid`, optionally with a subtle "seen before" hint. |
|
|
140
|
+
| `invalid` | Clear "this doesn't look genuine" message. Do NOT reveal data. |
|
|
141
|
+
| `not-found` | "We don't recognise this item." Offer a link home. |
|
|
142
|
+
| `error` | Neutral "couldn't verify right now." Offer retry. |
|
|
143
|
+
| _absent_ | Standard browse UI. Don't mention authenticity at all. |
|
|
144
|
+
|
|
145
|
+
## Gating UX with conditions
|
|
146
|
+
|
|
147
|
+
Sub-apps should standardize on passing `itemContext` into `validateCondition()`
|
|
148
|
+
and gating with `itemStatus` — the same condition type already used for
|
|
149
|
+
claim/virtual checks, extended with authenticity `statusType`s that read
|
|
150
|
+
`itemContext` instead of `proof`:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { validateCondition } from '@proveanything/smartlinks';
|
|
154
|
+
|
|
155
|
+
const showFakeWarning = await validateCondition({
|
|
156
|
+
condition: {
|
|
157
|
+
conditions: [{ type: 'itemStatus', statusType: 'invalidProof' }],
|
|
158
|
+
},
|
|
159
|
+
itemContext, // the prop delivered to the container
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- `isAuthentic` — passes when `itemContext.isAuthentic` is true (`valid`/`rescan`)
|
|
164
|
+
- `notAuthentic` — passes when an `itemContext` was resolved but isn't authentic (`invalid`/`not-found`/`error`)
|
|
165
|
+
- `invalidProof` — passes specifically when resolution was attempted and came back `invalid` or `not-found` — the "someone scanned a fake tag / typed a bad serial" case, as distinct from `noProof` ("nothing was on the URL to check at all")
|
|
166
|
+
- `isFirstScan` — the single "this is good, show the full experience" check: authentic AND this is the first time it's been seen (`status === 'valid'`)
|
|
167
|
+
- `isRescan` — authentic but a duplicate/replayed tap (`status === 'rescan'`) — use this to suppress "first scan" celebration UX without treating the tag as fake; `isAuthentic` alone is true for both `isFirstScan` and `isRescan` cases
|
|
168
|
+
|
|
169
|
+
## Deprecated
|
|
170
|
+
|
|
171
|
+
The old top-level `tag` prop (`TagContext`) is still forwarded for one
|
|
172
|
+
release for backward compatibility with scanner-aware apps that read raw
|
|
173
|
+
SUN data. New code should read `itemContext.tag` and treat `itemContext`
|
|
174
|
+
as the source of truth.
|
|
175
|
+
|
|
176
|
+
## Note on terminology
|
|
177
|
+
|
|
178
|
+
Today the SDK still uses `proof` for a specific item record (a serialised
|
|
179
|
+
instance of a product). "Item record" is under consideration as a clearer
|
|
180
|
+
name — no API changes are planned imminently, but a future SDK version may
|
|
181
|
+
support both `proof`- and `item record`-shaped names for the same
|
|
182
|
+
functions/types side by side. `itemContext` is deliberately future-proof:
|
|
183
|
+
it describes context about whatever the URL points at, regardless of what
|
|
184
|
+
we ultimately call the record itself.
|
package/dist/docs/utils.md
CHANGED
|
@@ -485,9 +485,12 @@ await utils.validateCondition({
|
|
|
485
485
|
```
|
|
486
486
|
|
|
487
487
|
#### Item Status Conditions
|
|
488
|
-
Check proof/item status
|
|
488
|
+
Check proof/item status — claim/ownership state, and authenticity of the
|
|
489
|
+
item (proof) the URL points at (via NFC tap or serial proof URL — see
|
|
490
|
+
[item-context.md](item-context.md)):
|
|
489
491
|
|
|
490
492
|
```typescript
|
|
493
|
+
// Claim/ownership — unchanged, reads `proof`
|
|
491
494
|
await utils.validateCondition({
|
|
492
495
|
condition: {
|
|
493
496
|
type: 'and',
|
|
@@ -498,9 +501,38 @@ await utils.validateCondition({
|
|
|
498
501
|
},
|
|
499
502
|
proof: { claimable: true }
|
|
500
503
|
})
|
|
504
|
+
|
|
505
|
+
// Authenticity — reads `itemContext`
|
|
506
|
+
await utils.validateCondition({
|
|
507
|
+
condition: {
|
|
508
|
+
type: 'and',
|
|
509
|
+
conditions: [{
|
|
510
|
+
type: 'itemStatus',
|
|
511
|
+
statusType: 'invalidProof'
|
|
512
|
+
}]
|
|
513
|
+
},
|
|
514
|
+
itemContext: { isAuthentic: false, status: 'invalid', source: 'nfc', checkedAt: Date.now() }
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
// Fresh scan vs. duplicate/replayed scan — both authentic, different UX
|
|
518
|
+
await utils.validateCondition({
|
|
519
|
+
condition: {
|
|
520
|
+
type: 'and',
|
|
521
|
+
conditions: [{
|
|
522
|
+
type: 'itemStatus',
|
|
523
|
+
statusType: 'isRescan'
|
|
524
|
+
}]
|
|
525
|
+
},
|
|
526
|
+
itemContext: { isAuthentic: true, status: 'rescan', source: 'nfc', checkedAt: Date.now() }
|
|
527
|
+
})
|
|
501
528
|
```
|
|
502
529
|
|
|
503
|
-
Status types:
|
|
530
|
+
Status types:
|
|
531
|
+
- Claim/ownership (reads `proof`): `'isClaimable'`, `'notClaimable'`, `'isVirtual'`, `'notVirtual'`
|
|
532
|
+
- Presence (reads `proof`): `'hasProof'`, `'noProof'` — `noProof` only means *nothing was attempted*, not that an attempt failed
|
|
533
|
+
- Authenticity (reads `itemContext`): `'isAuthentic'`, `'notAuthentic'`, `'invalidProof'`, `'isFirstScan'`, `'isRescan'`
|
|
534
|
+
- `invalidProof` is specifically "an identifier was passed and resolution came back `invalid`/`not-found`", distinct from `noProof`'s "nothing on the URL to check at all"
|
|
535
|
+
- `isAuthentic` is true for both a fresh tap and a rescan. Use `isFirstScan` (authentic AND `status === 'valid'`) as the single "this is good, show the full experience" check, and `isRescan` (authentic but `status === 'rescan'`) to suppress "first scan" celebration UX without treating the tag as fake
|
|
504
536
|
|
|
505
537
|
#### Version Conditions
|
|
506
538
|
For A/B testing or versioned content:
|
|
@@ -720,6 +752,7 @@ See [examples/utils-demo.ts](../examples/utils-demo.ts) for comprehensive exampl
|
|
|
720
752
|
- **[API Summary](API_SUMMARY.md)** - Complete SDK reference with all namespaces and functions
|
|
721
753
|
- **[QR Codes](API_SUMMARY.md#qr)** - QR code lookup functions that work with generated paths
|
|
722
754
|
- **[NFC](API_SUMMARY.md#nfc)** - NFC tag claiming and validation
|
|
755
|
+
- **[Item Context](item-context.md)** - `itemContext` prop delivered to containers describing the item (proof) the URL points at and whether it's authentic
|
|
723
756
|
- **[Collections](API_SUMMARY.md#collection)** - Collection management functions
|
|
724
757
|
- **[Products](API_SUMMARY.md#product)** - Product CRUD operations
|
|
725
758
|
- **[Batches](API_SUMMARY.md#batch)** - Batch management
|
package/dist/docs/widgets.md
CHANGED
|
@@ -159,6 +159,13 @@ interface SmartLinksWidgetProps {
|
|
|
159
159
|
// Base URL to the full public portal for deep linking
|
|
160
160
|
publicPortalUrl?: string;
|
|
161
161
|
|
|
162
|
+
// Authenticity context for the specific item (proof) the URL points at.
|
|
163
|
+
// undefined for collection- and product-only URLs.
|
|
164
|
+
itemContext?: ItemContext;
|
|
165
|
+
|
|
166
|
+
// @deprecated Use itemContext.tag instead — kept for one release
|
|
167
|
+
tag?: TagContext;
|
|
168
|
+
|
|
162
169
|
// Size hint for responsive rendering
|
|
163
170
|
size?: 'compact' | 'standard' | 'large';
|
|
164
171
|
|
|
@@ -180,6 +187,8 @@ interface SmartLinksWidgetProps {
|
|
|
180
187
|
| `SL` | `typeof SL` | Pre-initialized SmartLinks SDK |
|
|
181
188
|
| `onNavigate` | `function?` | Callback to navigate within parent app (accepts `NavigationRequest` or legacy string) |
|
|
182
189
|
| `publicPortalUrl` | `string?` | Base URL to full portal for deep links |
|
|
190
|
+
| `itemContext` | `ItemContext?` | Authenticity context for the specific item (proof) the URL points at; `undefined` for collection/product-only URLs — see [item-context.md](item-context.md) |
|
|
191
|
+
| `tag` | `TagContext?` | **Deprecated** — use `itemContext.tag` instead. Kept for one release. |
|
|
183
192
|
| `size` | `string?` | Size hint: 'compact', 'standard', or 'large' |
|
|
184
193
|
| `lang` | `string?` | Language code (e.g., 'en', 'de', 'fr') |
|
|
185
194
|
| `translations` | `object?` | Translation overrides |
|
package/dist/openapi.yaml
CHANGED
|
@@ -15719,8 +15719,6 @@ components:
|
|
|
15719
15719
|
enum:
|
|
15720
15720
|
- registered
|
|
15721
15721
|
- owned
|
|
15722
|
-
virtualItemsEnabled:
|
|
15723
|
-
type: boolean
|
|
15724
15722
|
system:
|
|
15725
15723
|
$ref: "#/components/schemas/SystemBlock"
|
|
15726
15724
|
required:
|
|
@@ -19309,10 +19307,6 @@ components:
|
|
|
19309
19307
|
type: string
|
|
19310
19308
|
allowAutoGenerateClaims:
|
|
19311
19309
|
type: boolean
|
|
19312
|
-
variants:
|
|
19313
|
-
type: boolean
|
|
19314
|
-
batches:
|
|
19315
|
-
type: boolean
|
|
19316
19310
|
defaultAuthKitId:
|
|
19317
19311
|
type: string
|
|
19318
19312
|
required:
|
|
@@ -19328,8 +19322,6 @@ components:
|
|
|
19328
19322
|
- supported
|
|
19329
19323
|
- roles
|
|
19330
19324
|
- shortId
|
|
19331
|
-
- variants
|
|
19332
|
-
- batches
|
|
19333
19325
|
- defaultAuthKitId
|
|
19334
19326
|
HubAvailabilityResponse:
|
|
19335
19327
|
type: object
|
|
@@ -22211,6 +22203,31 @@ components:
|
|
|
22211
22203
|
- metadata
|
|
22212
22204
|
- broadcastId
|
|
22213
22205
|
- journeyId
|
|
22206
|
+
ItemContext:
|
|
22207
|
+
type: object
|
|
22208
|
+
properties:
|
|
22209
|
+
isAuthentic:
|
|
22210
|
+
type: boolean
|
|
22211
|
+
status:
|
|
22212
|
+
$ref: "#/components/schemas/ItemContextStatus"
|
|
22213
|
+
source:
|
|
22214
|
+
type: string
|
|
22215
|
+
enum:
|
|
22216
|
+
- nfc
|
|
22217
|
+
- serial
|
|
22218
|
+
errorMessage:
|
|
22219
|
+
type: string
|
|
22220
|
+
isRescan:
|
|
22221
|
+
type: boolean
|
|
22222
|
+
tag:
|
|
22223
|
+
$ref: "#/components/schemas/TagContext"
|
|
22224
|
+
checkedAt:
|
|
22225
|
+
type: number
|
|
22226
|
+
required:
|
|
22227
|
+
- isAuthentic
|
|
22228
|
+
- status
|
|
22229
|
+
- source
|
|
22230
|
+
- checkedAt
|
|
22214
22231
|
Job:
|
|
22215
22232
|
type: object
|
|
22216
22233
|
properties:
|
|
@@ -23042,6 +23059,39 @@ components:
|
|
|
23042
23059
|
- claimSetId
|
|
23043
23060
|
- codeId
|
|
23044
23061
|
- data
|
|
23062
|
+
TagContext:
|
|
23063
|
+
type: object
|
|
23064
|
+
properties:
|
|
23065
|
+
status:
|
|
23066
|
+
$ref: "#/components/schemas/TagStatus"
|
|
23067
|
+
tagId:
|
|
23068
|
+
type: string
|
|
23069
|
+
claimSetId:
|
|
23070
|
+
type: string
|
|
23071
|
+
codeId:
|
|
23072
|
+
type: string
|
|
23073
|
+
count:
|
|
23074
|
+
type: number
|
|
23075
|
+
previousCount:
|
|
23076
|
+
type: number
|
|
23077
|
+
data:
|
|
23078
|
+
type: object
|
|
23079
|
+
additionalProperties: true
|
|
23080
|
+
source:
|
|
23081
|
+
type: string
|
|
23082
|
+
enum:
|
|
23083
|
+
- nfc-validate
|
|
23084
|
+
- nfc-lookup
|
|
23085
|
+
- tag-index
|
|
23086
|
+
- none
|
|
23087
|
+
validatedAt:
|
|
23088
|
+
type: number
|
|
23089
|
+
errorMessage:
|
|
23090
|
+
type: string
|
|
23091
|
+
required:
|
|
23092
|
+
- status
|
|
23093
|
+
- source
|
|
23094
|
+
- validatedAt
|
|
23045
23095
|
OrderItem:
|
|
23046
23096
|
type: object
|
|
23047
23097
|
properties:
|
|
@@ -25219,6 +25269,10 @@ components:
|
|
|
25219
25269
|
additionalProperties: true
|
|
25220
25270
|
publicPortalUrl:
|
|
25221
25271
|
type: string
|
|
25272
|
+
itemContext:
|
|
25273
|
+
$ref: "#/components/schemas/ItemContext"
|
|
25274
|
+
tag:
|
|
25275
|
+
$ref: "#/components/schemas/TagContext"
|
|
25222
25276
|
size:
|
|
25223
25277
|
type: string
|
|
25224
25278
|
enum:
|
|
@@ -112,7 +112,6 @@ export interface AppConfigSettings {
|
|
|
112
112
|
requestedBasePlanId?: string;
|
|
113
113
|
/** How the account handles individual physical items. */
|
|
114
114
|
itemRecordMode?: 'registered' | 'owned' | null;
|
|
115
|
-
virtualItemsEnabled?: boolean;
|
|
116
115
|
/** Resolved entitlement truth. Read this to gate features. */
|
|
117
116
|
system?: SystemBlock;
|
|
118
117
|
}
|
|
@@ -57,8 +57,6 @@ export interface Collection {
|
|
|
57
57
|
portalUrl?: string;
|
|
58
58
|
/** Allow users to claim products without providing a proof ID (auto-generates serial on-demand) */
|
|
59
59
|
allowAutoGenerateClaims?: boolean;
|
|
60
|
-
variants: boolean;
|
|
61
|
-
batches: boolean;
|
|
62
60
|
defaultAuthKitId: string;
|
|
63
61
|
}
|
|
64
62
|
export type CollectionResponse = Collection;
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TagContext } from './nfc';
|
|
2
|
+
/**
|
|
3
|
+
* Authenticity status for a specific item — a proof, resolved via an NFC
|
|
4
|
+
* tap or a serial proof URL. Collection- and product-only URLs have no
|
|
5
|
+
* item to describe, so there is no "nothing checkable" status here: absence
|
|
6
|
+
* of an `ItemContext` altogether is how that case is represented (see the
|
|
7
|
+
* `itemContext` prop on `SmartLinksWidgetProps`).
|
|
8
|
+
*/
|
|
9
|
+
export type ItemContextStatus = 'valid' | 'rescan' | 'invalid' | 'not-found' | 'error';
|
|
10
|
+
/**
|
|
11
|
+
* Describes a specific item the current URL points at and whether it's
|
|
12
|
+
* authentic — delivered to containers as the `itemContext` prop.
|
|
13
|
+
* Supersedes the NFC-only `TagContext`: still available, nested at
|
|
14
|
+
* `itemContext.tag`, and for one release also as a deprecated top-level
|
|
15
|
+
* `tag` prop. See docs/item-context.md.
|
|
16
|
+
*/
|
|
17
|
+
export interface ItemContext {
|
|
18
|
+
/** True only for `valid` and `rescan`. Read this for the simple case. */
|
|
19
|
+
isAuthentic: boolean;
|
|
20
|
+
status: ItemContextStatus;
|
|
21
|
+
source: 'nfc' | 'serial';
|
|
22
|
+
/** Set for `invalid` / `not-found` / `error`. Safe to display. */
|
|
23
|
+
errorMessage?: string;
|
|
24
|
+
/** True when the SDK reports this exact NFC tap was seen before. */
|
|
25
|
+
isRescan?: boolean;
|
|
26
|
+
/** Full NFC payload (SUN counter, tagId, claimSetId, codeId, mirror…). Present only when `source === 'nfc'`. */
|
|
27
|
+
tag?: TagContext;
|
|
28
|
+
/** Epoch ms when the check was derived. */
|
|
29
|
+
checkedAt: number;
|
|
30
|
+
}
|