@proveanything/smartlinks 1.15.21 → 1.15.23
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 +33 -1
- package/dist/docs/utils.md +58 -2
- package/dist/index.d.ts +1 -1
- package/dist/utils/paths.d.ts +94 -2
- package/dist/utils/paths.js +173 -29
- package/docs/API_SUMMARY.md +33 -1
- package/docs/utils.md +58 -2
- package/package.json +2 -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.23 | Generated: 2026-08-28T10:57:24.158Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -35,6 +35,7 @@ For detailed guides on specific features:
|
|
|
35
35
|
- **[Proof Claiming Methods](proof-claiming-methods.md)** - All methods for claiming/registering product ownership (NFC tags, serial numbers, auto-generated claims)
|
|
36
36
|
- **[Proof Share Grants](proof-share-grants.md)** - Delegated, scoped, revocable bearer access to a single proof (read/comment/verify-owner links)
|
|
37
37
|
- **[Proof Ownership Transfer](proof-ownership-transfer.md)** - Moving a proof to a new owner: directed transfer, open release, accept/cancel, and the state machine
|
|
38
|
+
- **[Item Context](item-context.md)** - The `itemContext` container prop derived from a serial-proof URL or NFC tap (what item the URL points at)
|
|
38
39
|
- **[Product Facets SDK](PRODUCT_FACETS_SDK.md)** - Admin and public product facet endpoints and TypeScript interfaces
|
|
39
40
|
- **[Attestations](attestations.md)** - Append-only fact log with cryptographic chain integrity, time-series analytics, and public/owner/admin visibility
|
|
40
41
|
- **[Auth Kit](auth-kit.md)** - End-user authentication flows (email/password, magic link, OTP, OAuth) for microapps
|
|
@@ -8266,6 +8267,37 @@ interface PortalPathParams {
|
|
|
8266
8267
|
proof?: Proof | string
|
|
8267
8268
|
queryParams?: Record<string, string>
|
|
8268
8269
|
pathOnly?: boolean
|
|
8270
|
+
* Override custom-domain detection. When the collection is served from its own
|
|
8271
|
+
* custom domain, a GS1 link resolves `/01/{gtin}` directly (the host identifies
|
|
8272
|
+
* the collection), so the `/gc/{shortId}` prefix is dropped. Left undefined, this
|
|
8273
|
+
* is auto-detected from `collection.redirectUrl` or a non-platform `portalUrl` host.
|
|
8274
|
+
customDomain?: boolean
|
|
8275
|
+
}
|
|
8276
|
+
```
|
|
8277
|
+
|
|
8278
|
+
**Gs1DigitalLinkParams** (interface)
|
|
8279
|
+
```typescript
|
|
8280
|
+
interface Gs1DigitalLinkParams {
|
|
8281
|
+
collection?: Collection | { shortId?: string; portalUrl?: string; redirectUrl?: string }
|
|
8282
|
+
domain?: string
|
|
8283
|
+
gtin?: string
|
|
8284
|
+
product?: Product
|
|
8285
|
+
ownGtin?: boolean
|
|
8286
|
+
cpv?: string | { id: string }
|
|
8287
|
+
variant?: string | { id: string }
|
|
8288
|
+
lot?: string | { id: string }
|
|
8289
|
+
batch?: BatchResponse | string
|
|
8290
|
+
serial?: string | { id?: string; serialNumber?: string }
|
|
8291
|
+
expiry?: string | Date
|
|
8292
|
+
* Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
|
|
8293
|
+
* `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
|
|
8294
|
+
* `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
|
|
8295
|
+
* the path in canonical order; everything else becomes a query-string data attribute.
|
|
8296
|
+
ais?: Record<string, string | number | Date>
|
|
8297
|
+
linkType?: string
|
|
8298
|
+
queryParams?: Record<string, string>
|
|
8299
|
+
customDomain?: boolean
|
|
8300
|
+
pathOnly?: boolean
|
|
8269
8301
|
}
|
|
8270
8302
|
```
|
|
8271
8303
|
|
package/dist/docs/utils.md
CHANGED
|
@@ -77,16 +77,26 @@ Builds a full portal URL based on the provided parameters. Returns full URL by d
|
|
|
77
77
|
|
|
78
78
|
- `pathOnly` (optional, default: `false`) - Return only the path without domain
|
|
79
79
|
Set to `true` to get `/c/abc/prod` instead of `https://domain.com/c/abc/prod`
|
|
80
|
+
- `customDomain` (optional) - Override custom-domain detection. When a collection is
|
|
81
|
+
served from its own custom domain, a GS1 link resolves `/01/{gtin}` directly (the host
|
|
82
|
+
identifies the collection), so the `/gc/{shortId}` prefix is dropped. Leave unset to
|
|
83
|
+
auto-detect from `collection.redirectUrl` or a non-platform `portalUrl` host.
|
|
80
84
|
|
|
81
85
|
**Path Formats:**
|
|
82
86
|
|
|
83
87
|
- Basic product: `/c/{shortId}/{productId}`
|
|
84
88
|
- With proof: `/c/{shortId}/{productId}/{proofId}`
|
|
85
|
-
- GTIN (own): `/01/{gtin}`
|
|
86
|
-
|
|
89
|
+
- GTIN (own, or on a custom domain): `/01/{gtin}` — when `product.ownGtin` is set, **or** the
|
|
90
|
+
collection is on its own custom domain (bare `/01/` resolves via the host)
|
|
91
|
+
- GTIN (not own, on the shared platform domain): `/gc/{shortId}/01/{gtin}`
|
|
87
92
|
- With batch: adds `/10/{batchId}` and optionally `?17={expiryDate}`
|
|
88
93
|
- With variant: adds `/22/{variantId}`
|
|
89
94
|
|
|
95
|
+
**Custom domain behaviour:** On `smartlinks.app`, a non-master GTIN needs the `/gc/{shortId}`
|
|
96
|
+
collection prefix so the server can scope it; a master GTIN (`ownGtin`) uses bare `/01/`.
|
|
97
|
+
On a collection's **own custom domain**, the host already identifies the collection, so the
|
|
98
|
+
generator emits the clean `https://customdomain/01/{gtin}` for any product — no `/gc/` prefix.
|
|
99
|
+
|
|
90
100
|
## Examples
|
|
91
101
|
|
|
92
102
|
### Full URL (Default Behavior)
|
|
@@ -209,6 +219,52 @@ utils.buildPortalPath({
|
|
|
209
219
|
// Returns: https://portal.smartlinks.io/c/abc123/prod123?utm_source=email&utm_campaign=launch&lang=fr
|
|
210
220
|
```
|
|
211
221
|
|
|
222
|
+
## GS1 Digital Links — `buildGs1DigitalLink(params)`
|
|
223
|
+
|
|
224
|
+
`buildPortalPath` covers portal routing; when you specifically want a **GS1 Digital Link**
|
|
225
|
+
with the full range of Application Identifiers (AIs), use `buildGs1DigitalLink`. It emits
|
|
226
|
+
path qualifiers in the **canonical GS1 order** — `/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}` —
|
|
227
|
+
routes data attributes (expiry, production date, weights, …) to the query string, and applies
|
|
228
|
+
the same custom-domain scoping (bare `/01/` on a custom domain or for a master GTIN, `/gc/{shortId}`
|
|
229
|
+
otherwise).
|
|
230
|
+
|
|
231
|
+
**Named inputs** for the common AIs, plus a generic **`ais` map** for anything else:
|
|
232
|
+
|
|
233
|
+
| Input | AI | Notes |
|
|
234
|
+
|-------|----|-------|
|
|
235
|
+
| `gtin` / `product.gtin` | 01 | Primary identifier (required) |
|
|
236
|
+
| `cpv` / `variant` | 22 | Consumer Product Variant (path) |
|
|
237
|
+
| `lot` / `batch` | 10 | Batch/lot (path); a batch object also supplies its expiry |
|
|
238
|
+
| `serial` | 21 | Specific item — a string, or an object (`serialNumber` ?? `id`, e.g. a proof / serial / virtual id) |
|
|
239
|
+
| `expiry` | 17 | Date → `YYMMDD` (query) |
|
|
240
|
+
| `ais: { [ai]: value }` | any | Date AIs (11/12/13/15/16/17) formatted `YYMMDD`; qualifier AIs (22/10/21) go in the path; the rest become query attributes |
|
|
241
|
+
| `linkType` | — | GS1 `linkType`, added as `?linkType=` |
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Variant + lot + serial + expiry, on a custom domain
|
|
245
|
+
utils.buildGs1DigitalLink({
|
|
246
|
+
collection, // portalUrl = https://acme.com
|
|
247
|
+
gtin: '05012345678900',
|
|
248
|
+
variant: 'red',
|
|
249
|
+
lot: 'LOT42',
|
|
250
|
+
serial: proof, // AI 21 from proof.serialNumber ?? proof.id
|
|
251
|
+
expiry: '2026-06-30',
|
|
252
|
+
})
|
|
253
|
+
// → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
|
|
254
|
+
|
|
255
|
+
// Arbitrary AIs via the generic map
|
|
256
|
+
utils.buildGs1DigitalLink({
|
|
257
|
+
collection,
|
|
258
|
+
gtin: '05012345678900',
|
|
259
|
+
ais: { '11': new Date(2025, 0, 1), '3103': '000500' }, // production date + net weight
|
|
260
|
+
linkType: 'gs1:pip',
|
|
261
|
+
})
|
|
262
|
+
// → https://.../01/05012345678900?11=250101&3103=000500&linkType=gs1%3Apip
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`buildPortalPath` delegates its GTIN paths to this function, so both stay in sync (and both now
|
|
266
|
+
emit qualifiers in canonical `22 → 10 → 21` order).
|
|
267
|
+
|
|
212
268
|
## Use Cases
|
|
213
269
|
|
|
214
270
|
### QR Code Generation
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { iframe } from "./iframe";
|
|
|
5
5
|
export * as cache from './cache';
|
|
6
6
|
export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
|
|
7
7
|
export * as utils from './utils';
|
|
8
|
-
export type { PortalPathParams, ConditionParams, ConditionDebugOptions, ConditionDebugLogger, ConditionSet, Condition, UserInfo, ProductInfo, ProofInfo, CollectionInfo, } from './utils';
|
|
8
|
+
export type { PortalPathParams, Gs1DigitalLinkParams, ConditionParams, ConditionDebugOptions, ConditionDebugLogger, ConditionSet, Condition, UserInfo, ProductInfo, ProofInfo, CollectionInfo, } from './utils';
|
|
9
9
|
export type { LoginResponse, VerifyTokenResponse, AccountInfoResponse, AuthLocation, } from "./api/auth";
|
|
10
10
|
export type { UserAccountRegistrationRequest, } from "./types/auth";
|
|
11
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/utils/paths.d.ts
CHANGED
|
@@ -30,6 +30,13 @@ export interface PortalPathParams {
|
|
|
30
30
|
queryParams?: Record<string, string>;
|
|
31
31
|
/** Return only the path without domain (default: false, returns full URL) */
|
|
32
32
|
pathOnly?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Override custom-domain detection. When the collection is served from its own
|
|
35
|
+
* custom domain, a GS1 link resolves `/01/{gtin}` directly (the host identifies
|
|
36
|
+
* the collection), so the `/gc/{shortId}` prefix is dropped. Left undefined, this
|
|
37
|
+
* is auto-detected from `collection.redirectUrl` or a non-platform `portalUrl` host.
|
|
38
|
+
*/
|
|
39
|
+
customDomain?: boolean;
|
|
33
40
|
}
|
|
34
41
|
/**
|
|
35
42
|
* Builds a portal path/URL based on the provided parameters.
|
|
@@ -43,8 +50,9 @@ export interface PortalPathParams {
|
|
|
43
50
|
* Supports multiple path formats:
|
|
44
51
|
* - Basic product: `/c/{shortId}/{productId}`
|
|
45
52
|
* - With proof: `/c/{shortId}/{productId}/{proofId}`
|
|
46
|
-
* - GTIN (own): `/01/{gtin}`
|
|
47
|
-
*
|
|
53
|
+
* - GTIN (own, or on a custom domain): `/01/{gtin}` — `ownGtin` is read from the product;
|
|
54
|
+
* a custom domain (see `customDomain` / `collection.redirectUrl`) also uses this bare form
|
|
55
|
+
* - GTIN (not own, on the shared platform domain): `/gc/{shortId}/01/{gtin}`
|
|
48
56
|
* - With batch: adds `/10/{batchId}` and optionally `?17={expiryDate}`
|
|
49
57
|
* - With variant: adds `/22/{variantId}`
|
|
50
58
|
*
|
|
@@ -93,3 +101,87 @@ export interface PortalPathParams {
|
|
|
93
101
|
* ```
|
|
94
102
|
*/
|
|
95
103
|
export declare function buildPortalPath(params: PortalPathParams): string;
|
|
104
|
+
export interface Gs1DigitalLinkParams {
|
|
105
|
+
/** Collection — provides the base domain (`portalUrl`), `shortId` (for `/gc` scoping) and custom-domain detection. */
|
|
106
|
+
collection?: Collection | {
|
|
107
|
+
shortId?: string;
|
|
108
|
+
portalUrl?: string;
|
|
109
|
+
redirectUrl?: string;
|
|
110
|
+
};
|
|
111
|
+
/** Explicit base domain (e.g. `"https://acme.com"`), overriding `collection.portalUrl`. */
|
|
112
|
+
domain?: string;
|
|
113
|
+
/** GTIN — the product's AI 01 identifier. Required (or provide `product`). */
|
|
114
|
+
gtin?: string;
|
|
115
|
+
/** Product object — extracts `gtin` and `ownGtin` when not given explicitly. */
|
|
116
|
+
product?: Product;
|
|
117
|
+
/** Override the global-owner flag; otherwise read from `product.ownGtin`. */
|
|
118
|
+
ownGtin?: boolean;
|
|
119
|
+
/** Consumer Product Variant (AI 22). A string or an object with `id`. */
|
|
120
|
+
cpv?: string | {
|
|
121
|
+
id: string;
|
|
122
|
+
};
|
|
123
|
+
/** Alias of `cpv` (AI 22). */
|
|
124
|
+
variant?: string | {
|
|
125
|
+
id: string;
|
|
126
|
+
};
|
|
127
|
+
/** Batch / lot (AI 10). A string or an object with `id`. */
|
|
128
|
+
lot?: string | {
|
|
129
|
+
id: string;
|
|
130
|
+
};
|
|
131
|
+
/** Alias of `lot` (AI 10). A `BatchResponse` also contributes its expiry date (AI 17) unless `expiry` is set. */
|
|
132
|
+
batch?: BatchResponse | string;
|
|
133
|
+
/** Serial (AI 21) — the specific item / proof. A string (serial / NFC / virtual id) or an object (`serialNumber` ?? `id`, e.g. a proof). */
|
|
134
|
+
serial?: string | {
|
|
135
|
+
id?: string;
|
|
136
|
+
serialNumber?: string;
|
|
137
|
+
};
|
|
138
|
+
/** Expiry date (AI 17). A `Date`, ISO string, or `YYMMDD` string. */
|
|
139
|
+
expiry?: string | Date;
|
|
140
|
+
/**
|
|
141
|
+
* Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
|
|
142
|
+
* `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
|
|
143
|
+
* `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
|
|
144
|
+
* the path in canonical order; everything else becomes a query-string data attribute.
|
|
145
|
+
*/
|
|
146
|
+
ais?: Record<string, string | number | Date>;
|
|
147
|
+
/** GS1 `linkType` (added as a `?linkType=` query param). */
|
|
148
|
+
linkType?: string;
|
|
149
|
+
/** Additional non-GS1 query params. */
|
|
150
|
+
queryParams?: Record<string, string>;
|
|
151
|
+
/** Override custom-domain detection (see {@link buildPortalPath}). */
|
|
152
|
+
customDomain?: boolean;
|
|
153
|
+
/** Return only the path, without a domain. */
|
|
154
|
+
pathOnly?: boolean;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Builds a GS1 Digital Link for a product, with full support for the standard
|
|
158
|
+
* Application Identifiers. Path qualifiers are emitted in the canonical GS1 order
|
|
159
|
+
* (`/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}`); data attributes (expiry, production
|
|
160
|
+
* date, weights, …) become query params; `linkType` is appended for GS1 resolution.
|
|
161
|
+
*
|
|
162
|
+
* A bare `/01/{gtin}` is used when the product owns the GTIN globally (`ownGtin`) or
|
|
163
|
+
* the collection is on its own custom domain; otherwise the GTIN is scoped to the
|
|
164
|
+
* collection with the `/gc/{shortId}` prefix on the shared platform domain.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* // GTIN + variant + lot + serial + expiry, on a custom domain
|
|
169
|
+
* buildGs1DigitalLink({
|
|
170
|
+
* collection, // portalUrl = https://acme.com
|
|
171
|
+
* gtin: '05012345678900',
|
|
172
|
+
* variant: 'red',
|
|
173
|
+
* lot: 'LOT42',
|
|
174
|
+
* serial: proof, // AI 21 from proof.serialNumber ?? proof.id
|
|
175
|
+
* expiry: '2026-06-30',
|
|
176
|
+
* })
|
|
177
|
+
* // → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
|
|
178
|
+
*
|
|
179
|
+
* // Arbitrary AIs via the generic map
|
|
180
|
+
* buildGs1DigitalLink({
|
|
181
|
+
* collection, gtin: '05012345678900',
|
|
182
|
+
* ais: { '11': new Date('2025-01-01'), '3103': '000500' }, // production date + net weight (kg)
|
|
183
|
+
* linkType: 'gs1:pip',
|
|
184
|
+
* })
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export declare function buildGs1DigitalLink(params: Gs1DigitalLinkParams): string;
|
package/dist/utils/paths.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
// Hosts served by the platform itself (not a collection's own custom domain). A base
|
|
2
|
+
// URL on any of these is NOT a custom domain, so non-master GTINs still need the
|
|
3
|
+
// `/gc/{shortId}` collection prefix. `portalUrl` is only ever set to the platform
|
|
4
|
+
// default or a collection's custom domain, so exact-host matching is sufficient here.
|
|
5
|
+
const PLATFORM_HOSTS = ['smartlinks.app', 'mysmartlinks.app', 'zt.smartlinks.io'];
|
|
6
|
+
/** True when `baseUrl`'s host is a collection's own custom domain (not a platform host). */
|
|
7
|
+
function baseIsCustomDomain(baseUrl) {
|
|
8
|
+
if (!baseUrl)
|
|
9
|
+
return false;
|
|
10
|
+
let host;
|
|
11
|
+
try {
|
|
12
|
+
host = new URL(baseUrl).hostname.toLowerCase().replace(/^www\./, '');
|
|
13
|
+
}
|
|
14
|
+
catch (_a) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return host.length > 0 && !PLATFORM_HOSTS.includes(host);
|
|
18
|
+
}
|
|
1
19
|
/**
|
|
2
20
|
* Builds a portal path/URL based on the provided parameters.
|
|
3
21
|
*
|
|
@@ -10,8 +28,9 @@
|
|
|
10
28
|
* Supports multiple path formats:
|
|
11
29
|
* - Basic product: `/c/{shortId}/{productId}`
|
|
12
30
|
* - With proof: `/c/{shortId}/{productId}/{proofId}`
|
|
13
|
-
* - GTIN (own): `/01/{gtin}`
|
|
14
|
-
*
|
|
31
|
+
* - GTIN (own, or on a custom domain): `/01/{gtin}` — `ownGtin` is read from the product;
|
|
32
|
+
* a custom domain (see `customDomain` / `collection.redirectUrl`) also uses this bare form
|
|
33
|
+
* - GTIN (not own, on the shared platform domain): `/gc/{shortId}/01/{gtin}`
|
|
15
34
|
* - With batch: adds `/10/{batchId}` and optionally `?17={expiryDate}`
|
|
16
35
|
* - With variant: adds `/22/{variantId}`
|
|
17
36
|
*
|
|
@@ -60,20 +79,26 @@
|
|
|
60
79
|
* ```
|
|
61
80
|
*/
|
|
62
81
|
export function buildPortalPath(params) {
|
|
63
|
-
var _a, _b;
|
|
82
|
+
var _a, _b, _c;
|
|
64
83
|
const { collection, product, productId, batch, batchId, variant, proof, queryParams = {}, pathOnly = false } = params;
|
|
65
84
|
// Extract values from collection
|
|
66
85
|
const shortId = collection.shortId;
|
|
67
86
|
const baseUrl = 'portalUrl' in collection ? collection.portalUrl : undefined;
|
|
87
|
+
// A collection on its own custom domain resolves `/01/{gtin}` directly (the host
|
|
88
|
+
// maps to the collection server-side), so the `/gc/{shortId}` prefix is dropped.
|
|
89
|
+
// Explicit override wins; otherwise detect from `redirectUrl` (the server's own
|
|
90
|
+
// custom-domain signal) or a non-platform `portalUrl` host.
|
|
91
|
+
const redirectUrl = 'redirectUrl' in collection ? collection.redirectUrl : undefined;
|
|
92
|
+
const customDomain = (_a = params.customDomain) !== null && _a !== void 0 ? _a : (!!redirectUrl || baseIsCustomDomain(baseUrl));
|
|
68
93
|
// Extract product values
|
|
69
94
|
let gtin;
|
|
70
95
|
let ownGtin;
|
|
71
96
|
let extractedProductId;
|
|
72
97
|
if (product) {
|
|
73
98
|
extractedProductId = product.id;
|
|
74
|
-
gtin = (
|
|
99
|
+
gtin = (_b = product.gtin) !== null && _b !== void 0 ? _b : undefined;
|
|
75
100
|
// ownGtin is a critical product setting - only read from product, never override
|
|
76
|
-
ownGtin = 'ownGtin' in product ? (
|
|
101
|
+
ownGtin = 'ownGtin' in product ? (_c = product.ownGtin) !== null && _c !== void 0 ? _c : undefined : undefined;
|
|
77
102
|
}
|
|
78
103
|
else if (productId) {
|
|
79
104
|
extractedProductId = productId;
|
|
@@ -110,32 +135,25 @@ export function buildPortalPath(params) {
|
|
|
110
135
|
? proof
|
|
111
136
|
: proof.id
|
|
112
137
|
: undefined;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Build pathname based on GTIN or product ID
|
|
138
|
+
// GTIN → delegate to the GS1 Digital Link generator (canonical AI ordering:
|
|
139
|
+
// /01/{gtin}/22/{cpv}/10/{lot}/21/{serial}, data attributes as query params).
|
|
116
140
|
if (gtin) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const dateStr = formatExpiryDate(expiryDate);
|
|
130
|
-
searchParams.append('17', dateStr);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Add variant (GS1 AI 22)
|
|
134
|
-
if (variantId) {
|
|
135
|
-
pathname += `/22/${variantId}`;
|
|
136
|
-
}
|
|
141
|
+
return buildGs1DigitalLink({
|
|
142
|
+
domain: baseUrl,
|
|
143
|
+
collection,
|
|
144
|
+
gtin,
|
|
145
|
+
ownGtin,
|
|
146
|
+
customDomain,
|
|
147
|
+
variant: variantId,
|
|
148
|
+
lot: extractedBatchId,
|
|
149
|
+
expiry: expiryDate,
|
|
150
|
+
queryParams,
|
|
151
|
+
pathOnly,
|
|
152
|
+
});
|
|
137
153
|
}
|
|
138
|
-
|
|
154
|
+
let pathname = '';
|
|
155
|
+
const searchParams = new URLSearchParams();
|
|
156
|
+
if (extractedProductId) {
|
|
139
157
|
// Regular product path
|
|
140
158
|
pathname = `/c/${shortId}/${extractedProductId}`;
|
|
141
159
|
// Add proof to path
|
|
@@ -177,3 +195,129 @@ function formatExpiryDate(date) {
|
|
|
177
195
|
const day = date.getDate().toString().padStart(2, '0');
|
|
178
196
|
return `${year}${month}${day}`;
|
|
179
197
|
}
|
|
198
|
+
// ─── GS1 Digital Link generator ──────────────────────────────────────────────
|
|
199
|
+
/** GS1 path qualifiers for a GTIN (AI 01) key, in the canonical GS1 Digital Link order. */
|
|
200
|
+
const GTIN_PATH_QUALIFIERS = ['22', '10', '21'];
|
|
201
|
+
/** GS1 Application Identifiers whose value is a date — formatted `YYMMDD`. */
|
|
202
|
+
const DATE_AIS = new Set(['11', '12', '13', '15', '16', '17']);
|
|
203
|
+
/** Pull a code out of a string or an object (`serialNumber` preferred, then `id`). */
|
|
204
|
+
function extractCode(v) {
|
|
205
|
+
var _a;
|
|
206
|
+
if (v == null)
|
|
207
|
+
return undefined;
|
|
208
|
+
if (typeof v === 'string')
|
|
209
|
+
return v;
|
|
210
|
+
return (_a = v.serialNumber) !== null && _a !== void 0 ? _a : v.id;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Builds a GS1 Digital Link for a product, with full support for the standard
|
|
214
|
+
* Application Identifiers. Path qualifiers are emitted in the canonical GS1 order
|
|
215
|
+
* (`/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}`); data attributes (expiry, production
|
|
216
|
+
* date, weights, …) become query params; `linkType` is appended for GS1 resolution.
|
|
217
|
+
*
|
|
218
|
+
* A bare `/01/{gtin}` is used when the product owns the GTIN globally (`ownGtin`) or
|
|
219
|
+
* the collection is on its own custom domain; otherwise the GTIN is scoped to the
|
|
220
|
+
* collection with the `/gc/{shortId}` prefix on the shared platform domain.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* // GTIN + variant + lot + serial + expiry, on a custom domain
|
|
225
|
+
* buildGs1DigitalLink({
|
|
226
|
+
* collection, // portalUrl = https://acme.com
|
|
227
|
+
* gtin: '05012345678900',
|
|
228
|
+
* variant: 'red',
|
|
229
|
+
* lot: 'LOT42',
|
|
230
|
+
* serial: proof, // AI 21 from proof.serialNumber ?? proof.id
|
|
231
|
+
* expiry: '2026-06-30',
|
|
232
|
+
* })
|
|
233
|
+
* // → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
|
|
234
|
+
*
|
|
235
|
+
* // Arbitrary AIs via the generic map
|
|
236
|
+
* buildGs1DigitalLink({
|
|
237
|
+
* collection, gtin: '05012345678900',
|
|
238
|
+
* ais: { '11': new Date('2025-01-01'), '3103': '000500' }, // production date + net weight (kg)
|
|
239
|
+
* linkType: 'gs1:pip',
|
|
240
|
+
* })
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
export function buildGs1DigitalLink(params) {
|
|
244
|
+
var _a, _b, _c, _d, _e, _f;
|
|
245
|
+
const { collection, product, expiry, linkType, ais = {}, queryParams = {}, pathOnly = false } = params;
|
|
246
|
+
const gtin = (_b = (_a = params.gtin) !== null && _a !== void 0 ? _a : product === null || product === void 0 ? void 0 : product.gtin) !== null && _b !== void 0 ? _b : undefined;
|
|
247
|
+
if (!gtin) {
|
|
248
|
+
throw new Error('buildGs1DigitalLink requires a `gtin` (or a `product` that has one)');
|
|
249
|
+
}
|
|
250
|
+
const ownGtin = (_c = params.ownGtin) !== null && _c !== void 0 ? _c : (product && 'ownGtin' in product ? !!product.ownGtin : false);
|
|
251
|
+
// Base domain + custom-domain scoping (same rules as buildPortalPath).
|
|
252
|
+
const shortId = collection === null || collection === void 0 ? void 0 : collection.shortId;
|
|
253
|
+
const baseUrl = (_d = params.domain) !== null && _d !== void 0 ? _d : (collection && 'portalUrl' in collection ? collection.portalUrl : undefined);
|
|
254
|
+
const redirectUrl = collection && 'redirectUrl' in collection ? collection.redirectUrl : undefined;
|
|
255
|
+
const customDomain = (_e = params.customDomain) !== null && _e !== void 0 ? _e : (!!redirectUrl || baseIsCustomDomain(baseUrl));
|
|
256
|
+
// ── Path qualifiers (AI 22 → 10 → 21) ──
|
|
257
|
+
const pathAIs = {};
|
|
258
|
+
const cpv = extractCode((_f = params.cpv) !== null && _f !== void 0 ? _f : params.variant);
|
|
259
|
+
if (cpv != null)
|
|
260
|
+
pathAIs['22'] = cpv;
|
|
261
|
+
let batchExpiry;
|
|
262
|
+
let lot = extractCode(params.lot);
|
|
263
|
+
if (lot == null && params.batch != null) {
|
|
264
|
+
if (typeof params.batch === 'string') {
|
|
265
|
+
lot = params.batch;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
lot = params.batch.id;
|
|
269
|
+
const exp = params.batch.expiryDate;
|
|
270
|
+
if (exp) {
|
|
271
|
+
batchExpiry = (typeof exp === 'object' && 'seconds' in exp)
|
|
272
|
+
? new Date(exp.seconds * 1000)
|
|
273
|
+
: exp;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (lot != null)
|
|
278
|
+
pathAIs['10'] = lot;
|
|
279
|
+
const serial = extractCode(params.serial);
|
|
280
|
+
if (serial != null)
|
|
281
|
+
pathAIs['21'] = serial;
|
|
282
|
+
// ── Data attributes (query string) ──
|
|
283
|
+
const attrs = {};
|
|
284
|
+
const setAttr = (ai, value) => {
|
|
285
|
+
attrs[ai] = DATE_AIS.has(ai) && (value instanceof Date || typeof value === 'string')
|
|
286
|
+
? formatExpiryDate(value)
|
|
287
|
+
: String(value);
|
|
288
|
+
};
|
|
289
|
+
const effectiveExpiry = expiry !== null && expiry !== void 0 ? expiry : batchExpiry;
|
|
290
|
+
if (effectiveExpiry != null)
|
|
291
|
+
setAttr('17', effectiveExpiry);
|
|
292
|
+
// Generic AI map: classify each entry as a path qualifier or a data attribute.
|
|
293
|
+
for (const [ai, value] of Object.entries(ais)) {
|
|
294
|
+
if (value == null)
|
|
295
|
+
continue;
|
|
296
|
+
if (GTIN_PATH_QUALIFIERS.includes(ai)) {
|
|
297
|
+
pathAIs[ai] = String(value);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
setAttr(ai, value);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ── Compose the path ──
|
|
304
|
+
let pathname = (ownGtin || customDomain || !shortId) ? `/01/${gtin}` : `/gc/${shortId}/01/${gtin}`;
|
|
305
|
+
for (const ai of GTIN_PATH_QUALIFIERS) {
|
|
306
|
+
if (pathAIs[ai] != null)
|
|
307
|
+
pathname += `/${ai}/${pathAIs[ai]}`;
|
|
308
|
+
}
|
|
309
|
+
// ── Query string ──
|
|
310
|
+
const searchParams = new URLSearchParams();
|
|
311
|
+
for (const [ai, value] of Object.entries(attrs))
|
|
312
|
+
searchParams.append(ai, value);
|
|
313
|
+
if (linkType)
|
|
314
|
+
searchParams.append('linkType', linkType);
|
|
315
|
+
for (const [k, v] of Object.entries(queryParams))
|
|
316
|
+
searchParams.append(k, v);
|
|
317
|
+
const queryString = searchParams.toString();
|
|
318
|
+
const fullPath = pathname + (queryString ? `?${queryString}` : '');
|
|
319
|
+
if (pathOnly)
|
|
320
|
+
return fullPath;
|
|
321
|
+
const domain = baseUrl || 'https://smartlinks.app';
|
|
322
|
+
return domain.replace(/\/$/, '') + fullPath;
|
|
323
|
+
}
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.23 | Generated: 2026-08-28T10:57:24.158Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -35,6 +35,7 @@ For detailed guides on specific features:
|
|
|
35
35
|
- **[Proof Claiming Methods](proof-claiming-methods.md)** - All methods for claiming/registering product ownership (NFC tags, serial numbers, auto-generated claims)
|
|
36
36
|
- **[Proof Share Grants](proof-share-grants.md)** - Delegated, scoped, revocable bearer access to a single proof (read/comment/verify-owner links)
|
|
37
37
|
- **[Proof Ownership Transfer](proof-ownership-transfer.md)** - Moving a proof to a new owner: directed transfer, open release, accept/cancel, and the state machine
|
|
38
|
+
- **[Item Context](item-context.md)** - The `itemContext` container prop derived from a serial-proof URL or NFC tap (what item the URL points at)
|
|
38
39
|
- **[Product Facets SDK](PRODUCT_FACETS_SDK.md)** - Admin and public product facet endpoints and TypeScript interfaces
|
|
39
40
|
- **[Attestations](attestations.md)** - Append-only fact log with cryptographic chain integrity, time-series analytics, and public/owner/admin visibility
|
|
40
41
|
- **[Auth Kit](auth-kit.md)** - End-user authentication flows (email/password, magic link, OTP, OAuth) for microapps
|
|
@@ -8266,6 +8267,37 @@ interface PortalPathParams {
|
|
|
8266
8267
|
proof?: Proof | string
|
|
8267
8268
|
queryParams?: Record<string, string>
|
|
8268
8269
|
pathOnly?: boolean
|
|
8270
|
+
* Override custom-domain detection. When the collection is served from its own
|
|
8271
|
+
* custom domain, a GS1 link resolves `/01/{gtin}` directly (the host identifies
|
|
8272
|
+
* the collection), so the `/gc/{shortId}` prefix is dropped. Left undefined, this
|
|
8273
|
+
* is auto-detected from `collection.redirectUrl` or a non-platform `portalUrl` host.
|
|
8274
|
+
customDomain?: boolean
|
|
8275
|
+
}
|
|
8276
|
+
```
|
|
8277
|
+
|
|
8278
|
+
**Gs1DigitalLinkParams** (interface)
|
|
8279
|
+
```typescript
|
|
8280
|
+
interface Gs1DigitalLinkParams {
|
|
8281
|
+
collection?: Collection | { shortId?: string; portalUrl?: string; redirectUrl?: string }
|
|
8282
|
+
domain?: string
|
|
8283
|
+
gtin?: string
|
|
8284
|
+
product?: Product
|
|
8285
|
+
ownGtin?: boolean
|
|
8286
|
+
cpv?: string | { id: string }
|
|
8287
|
+
variant?: string | { id: string }
|
|
8288
|
+
lot?: string | { id: string }
|
|
8289
|
+
batch?: BatchResponse | string
|
|
8290
|
+
serial?: string | { id?: string; serialNumber?: string }
|
|
8291
|
+
expiry?: string | Date
|
|
8292
|
+
* Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
|
|
8293
|
+
* `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
|
|
8294
|
+
* `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
|
|
8295
|
+
* the path in canonical order; everything else becomes a query-string data attribute.
|
|
8296
|
+
ais?: Record<string, string | number | Date>
|
|
8297
|
+
linkType?: string
|
|
8298
|
+
queryParams?: Record<string, string>
|
|
8299
|
+
customDomain?: boolean
|
|
8300
|
+
pathOnly?: boolean
|
|
8269
8301
|
}
|
|
8270
8302
|
```
|
|
8271
8303
|
|
package/docs/utils.md
CHANGED
|
@@ -77,16 +77,26 @@ Builds a full portal URL based on the provided parameters. Returns full URL by d
|
|
|
77
77
|
|
|
78
78
|
- `pathOnly` (optional, default: `false`) - Return only the path without domain
|
|
79
79
|
Set to `true` to get `/c/abc/prod` instead of `https://domain.com/c/abc/prod`
|
|
80
|
+
- `customDomain` (optional) - Override custom-domain detection. When a collection is
|
|
81
|
+
served from its own custom domain, a GS1 link resolves `/01/{gtin}` directly (the host
|
|
82
|
+
identifies the collection), so the `/gc/{shortId}` prefix is dropped. Leave unset to
|
|
83
|
+
auto-detect from `collection.redirectUrl` or a non-platform `portalUrl` host.
|
|
80
84
|
|
|
81
85
|
**Path Formats:**
|
|
82
86
|
|
|
83
87
|
- Basic product: `/c/{shortId}/{productId}`
|
|
84
88
|
- With proof: `/c/{shortId}/{productId}/{proofId}`
|
|
85
|
-
- GTIN (own): `/01/{gtin}`
|
|
86
|
-
|
|
89
|
+
- GTIN (own, or on a custom domain): `/01/{gtin}` — when `product.ownGtin` is set, **or** the
|
|
90
|
+
collection is on its own custom domain (bare `/01/` resolves via the host)
|
|
91
|
+
- GTIN (not own, on the shared platform domain): `/gc/{shortId}/01/{gtin}`
|
|
87
92
|
- With batch: adds `/10/{batchId}` and optionally `?17={expiryDate}`
|
|
88
93
|
- With variant: adds `/22/{variantId}`
|
|
89
94
|
|
|
95
|
+
**Custom domain behaviour:** On `smartlinks.app`, a non-master GTIN needs the `/gc/{shortId}`
|
|
96
|
+
collection prefix so the server can scope it; a master GTIN (`ownGtin`) uses bare `/01/`.
|
|
97
|
+
On a collection's **own custom domain**, the host already identifies the collection, so the
|
|
98
|
+
generator emits the clean `https://customdomain/01/{gtin}` for any product — no `/gc/` prefix.
|
|
99
|
+
|
|
90
100
|
## Examples
|
|
91
101
|
|
|
92
102
|
### Full URL (Default Behavior)
|
|
@@ -209,6 +219,52 @@ utils.buildPortalPath({
|
|
|
209
219
|
// Returns: https://portal.smartlinks.io/c/abc123/prod123?utm_source=email&utm_campaign=launch&lang=fr
|
|
210
220
|
```
|
|
211
221
|
|
|
222
|
+
## GS1 Digital Links — `buildGs1DigitalLink(params)`
|
|
223
|
+
|
|
224
|
+
`buildPortalPath` covers portal routing; when you specifically want a **GS1 Digital Link**
|
|
225
|
+
with the full range of Application Identifiers (AIs), use `buildGs1DigitalLink`. It emits
|
|
226
|
+
path qualifiers in the **canonical GS1 order** — `/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}` —
|
|
227
|
+
routes data attributes (expiry, production date, weights, …) to the query string, and applies
|
|
228
|
+
the same custom-domain scoping (bare `/01/` on a custom domain or for a master GTIN, `/gc/{shortId}`
|
|
229
|
+
otherwise).
|
|
230
|
+
|
|
231
|
+
**Named inputs** for the common AIs, plus a generic **`ais` map** for anything else:
|
|
232
|
+
|
|
233
|
+
| Input | AI | Notes |
|
|
234
|
+
|-------|----|-------|
|
|
235
|
+
| `gtin` / `product.gtin` | 01 | Primary identifier (required) |
|
|
236
|
+
| `cpv` / `variant` | 22 | Consumer Product Variant (path) |
|
|
237
|
+
| `lot` / `batch` | 10 | Batch/lot (path); a batch object also supplies its expiry |
|
|
238
|
+
| `serial` | 21 | Specific item — a string, or an object (`serialNumber` ?? `id`, e.g. a proof / serial / virtual id) |
|
|
239
|
+
| `expiry` | 17 | Date → `YYMMDD` (query) |
|
|
240
|
+
| `ais: { [ai]: value }` | any | Date AIs (11/12/13/15/16/17) formatted `YYMMDD`; qualifier AIs (22/10/21) go in the path; the rest become query attributes |
|
|
241
|
+
| `linkType` | — | GS1 `linkType`, added as `?linkType=` |
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Variant + lot + serial + expiry, on a custom domain
|
|
245
|
+
utils.buildGs1DigitalLink({
|
|
246
|
+
collection, // portalUrl = https://acme.com
|
|
247
|
+
gtin: '05012345678900',
|
|
248
|
+
variant: 'red',
|
|
249
|
+
lot: 'LOT42',
|
|
250
|
+
serial: proof, // AI 21 from proof.serialNumber ?? proof.id
|
|
251
|
+
expiry: '2026-06-30',
|
|
252
|
+
})
|
|
253
|
+
// → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
|
|
254
|
+
|
|
255
|
+
// Arbitrary AIs via the generic map
|
|
256
|
+
utils.buildGs1DigitalLink({
|
|
257
|
+
collection,
|
|
258
|
+
gtin: '05012345678900',
|
|
259
|
+
ais: { '11': new Date(2025, 0, 1), '3103': '000500' }, // production date + net weight
|
|
260
|
+
linkType: 'gs1:pip',
|
|
261
|
+
})
|
|
262
|
+
// → https://.../01/05012345678900?11=250101&3103=000500&linkType=gs1%3Apip
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`buildPortalPath` delegates its GTIN paths to this function, so both stay in sync (and both now
|
|
266
|
+
emit qualifiers in canonical `22 → 10 → 21` order).
|
|
267
|
+
|
|
212
268
|
## Use Cases
|
|
213
269
|
|
|
214
270
|
### QR Code Generation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proveanything/smartlinks",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.23",
|
|
4
4
|
"description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
+
"test": "node test/run.cjs",
|
|
20
21
|
"build": "tsc && node generate-api-summary.js && node generate-openapi.js && node scripts/copy-docs-to-dist.js",
|
|
21
22
|
"docs": "typedoc",
|
|
22
23
|
"docs:summary": "node generate-api-summary.js",
|