@proveanything/smartlinks 1.15.22 → 1.15.24
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 +53 -1
- package/dist/docs/utils.md +47 -0
- package/dist/index.d.ts +1 -1
- package/dist/openapi.yaml +42 -0
- package/dist/types/segments.d.ts +25 -0
- package/dist/utils/paths.d.ts +91 -0
- package/dist/utils/paths.js +143 -27
- package/docs/API_SUMMARY.md +53 -1
- package/docs/utils.md +47 -0
- package/openapi.yaml +42 -0
- 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.24 | Generated: 2026-08-31T17:50:11.496Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -7531,6 +7531,23 @@ interface InteractionFilterValue {
|
|
|
7531
7531
|
}
|
|
7532
7532
|
```
|
|
7533
7533
|
|
|
7534
|
+
**OwnershipAttributePredicate** (interface)
|
|
7535
|
+
```typescript
|
|
7536
|
+
interface OwnershipAttributePredicate {
|
|
7537
|
+
path: string
|
|
7538
|
+
operator: OwnershipAttributeOperator
|
|
7539
|
+
value?: string | number
|
|
7540
|
+
}
|
|
7541
|
+
```
|
|
7542
|
+
|
|
7543
|
+
**OwnershipFacetSelector** (interface)
|
|
7544
|
+
```typescript
|
|
7545
|
+
interface OwnershipFacetSelector {
|
|
7546
|
+
key: string
|
|
7547
|
+
value: string
|
|
7548
|
+
}
|
|
7549
|
+
```
|
|
7550
|
+
|
|
7534
7551
|
**SegmentRecord** (interface)
|
|
7535
7552
|
```typescript
|
|
7536
7553
|
interface SegmentRecord {
|
|
@@ -7591,6 +7608,10 @@ interface SegmentRecipientsResponse {
|
|
|
7591
7608
|
}
|
|
7592
7609
|
```
|
|
7593
7610
|
|
|
7611
|
+
**CustomFieldOperator** = ``
|
|
7612
|
+
|
|
7613
|
+
**OwnershipAttributeOperator** = `'equals' | 'exists' | 'gt' | 'gte' | 'lt' | 'lte'`
|
|
7614
|
+
|
|
7594
7615
|
**SegmentFilterRule** = ``
|
|
7595
7616
|
|
|
7596
7617
|
### tags
|
|
@@ -8275,6 +8296,37 @@ interface PortalPathParams {
|
|
|
8275
8296
|
}
|
|
8276
8297
|
```
|
|
8277
8298
|
|
|
8299
|
+
**Gs1DigitalLinkParams** (interface)
|
|
8300
|
+
```typescript
|
|
8301
|
+
interface Gs1DigitalLinkParams {
|
|
8302
|
+
collection?: Collection | { shortId?: string; portalUrl?: string; redirectUrl?: string }
|
|
8303
|
+
domain?: string
|
|
8304
|
+
gtin?: string
|
|
8305
|
+
product?: Product
|
|
8306
|
+
ownGtin?: boolean
|
|
8307
|
+
* A real GS1 **Consumer Product Variant** code (AI 22). Use this when the brand has a
|
|
8308
|
+
* genuine CPV. Takes precedence over `variant` when both are given.
|
|
8309
|
+
cpv?: string | { id: string }
|
|
8310
|
+
* Internal variant id, emitted as AI 22 (the SmartLinks resolver reads path segment 22
|
|
8311
|
+
* as the variant). Prefer `cpv` when you have a real GS1 CPV code — a non-CPV variant id
|
|
8312
|
+
* in AI 22 is only meaningful to the SmartLinks resolver, not to third-party GS1 resolvers.
|
|
8313
|
+
variant?: string | { id: string }
|
|
8314
|
+
lot?: string | { id: string }
|
|
8315
|
+
batch?: BatchResponse | string
|
|
8316
|
+
serial?: string | { id?: string; serialNumber?: string }
|
|
8317
|
+
expiry?: string | Date
|
|
8318
|
+
* Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
|
|
8319
|
+
* `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
|
|
8320
|
+
* `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
|
|
8321
|
+
* the path in canonical order; everything else becomes a query-string data attribute.
|
|
8322
|
+
ais?: Record<string, string | number | Date>
|
|
8323
|
+
linkType?: string
|
|
8324
|
+
queryParams?: Record<string, string>
|
|
8325
|
+
customDomain?: boolean
|
|
8326
|
+
pathOnly?: boolean
|
|
8327
|
+
}
|
|
8328
|
+
```
|
|
8329
|
+
|
|
8278
8330
|
## API Functions
|
|
8279
8331
|
|
|
8280
8332
|
### analytics.admin
|
package/dist/docs/utils.md
CHANGED
|
@@ -219,6 +219,53 @@ utils.buildPortalPath({
|
|
|
219
219
|
// Returns: https://portal.smartlinks.io/c/abc123/prod123?utm_source=email&utm_campaign=launch&lang=fr
|
|
220
220
|
```
|
|
221
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` | 22 | Real GS1 Consumer Product Variant code (path). Wins over `variant`. |
|
|
237
|
+
| `variant` | 22 | Internal variant id, emitted as AI 22 for the SmartLinks resolver. Use `cpv` for a genuine GS1 CPV. |
|
|
238
|
+
| `lot` / `batch` | 10 | Batch/lot (path); a batch object also supplies its expiry |
|
|
239
|
+
| `serial` | 21 | Specific item — a string, or an object (`serialNumber` ?? `id`, e.g. a proof / serial / virtual id) |
|
|
240
|
+
| `expiry` | 17 | Date → `YYMMDD` (query) |
|
|
241
|
+
| `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 |
|
|
242
|
+
| `linkType` | — | GS1 `linkType`, added as `?linkType=` |
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// Variant + lot + serial + expiry, on a custom domain
|
|
246
|
+
utils.buildGs1DigitalLink({
|
|
247
|
+
collection, // portalUrl = https://acme.com
|
|
248
|
+
gtin: '05012345678900',
|
|
249
|
+
variant: 'red',
|
|
250
|
+
lot: 'LOT42',
|
|
251
|
+
serial: proof, // AI 21 from proof.serialNumber ?? proof.id
|
|
252
|
+
expiry: '2026-06-30',
|
|
253
|
+
})
|
|
254
|
+
// → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
|
|
255
|
+
|
|
256
|
+
// Arbitrary AIs via the generic map
|
|
257
|
+
utils.buildGs1DigitalLink({
|
|
258
|
+
collection,
|
|
259
|
+
gtin: '05012345678900',
|
|
260
|
+
ais: { '11': new Date(2025, 0, 1), '3103': '000500' }, // production date + net weight
|
|
261
|
+
linkType: 'gs1:pip',
|
|
262
|
+
})
|
|
263
|
+
// → https://.../01/05012345678900?11=250101&3103=000500&linkType=gs1%3Apip
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`buildPortalPath` delegates its GTIN paths to this function, so both stay in sync (and both now
|
|
267
|
+
emit qualifiers in canonical `22 → 10 → 21` order).
|
|
268
|
+
|
|
222
269
|
## Use Cases
|
|
223
270
|
|
|
224
271
|
### 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/openapi.yaml
CHANGED
|
@@ -25340,6 +25340,29 @@ components:
|
|
|
25340
25340
|
type: string
|
|
25341
25341
|
required:
|
|
25342
25342
|
- interactionId
|
|
25343
|
+
OwnershipAttributePredicate:
|
|
25344
|
+
type: object
|
|
25345
|
+
properties:
|
|
25346
|
+
path:
|
|
25347
|
+
type: string
|
|
25348
|
+
operator:
|
|
25349
|
+
$ref: "#/components/schemas/OwnershipAttributeOperator"
|
|
25350
|
+
value:
|
|
25351
|
+
type: object
|
|
25352
|
+
additionalProperties: true
|
|
25353
|
+
required:
|
|
25354
|
+
- path
|
|
25355
|
+
- operator
|
|
25356
|
+
OwnershipFacetSelector:
|
|
25357
|
+
type: object
|
|
25358
|
+
properties:
|
|
25359
|
+
key:
|
|
25360
|
+
type: string
|
|
25361
|
+
value:
|
|
25362
|
+
type: string
|
|
25363
|
+
required:
|
|
25364
|
+
- key
|
|
25365
|
+
- value
|
|
25343
25366
|
SegmentRecord:
|
|
25344
25367
|
type: object
|
|
25345
25368
|
properties:
|
|
@@ -25445,6 +25468,25 @@ components:
|
|
|
25445
25468
|
- limit
|
|
25446
25469
|
- offset
|
|
25447
25470
|
- total
|
|
25471
|
+
CustomFieldOperator:
|
|
25472
|
+
type: string
|
|
25473
|
+
enum:
|
|
25474
|
+
- equals
|
|
25475
|
+
- exists
|
|
25476
|
+
- gt
|
|
25477
|
+
- gte
|
|
25478
|
+
- lt
|
|
25479
|
+
- lte
|
|
25480
|
+
- before
|
|
25481
|
+
- after
|
|
25482
|
+
- within_days
|
|
25483
|
+
- older_than_days
|
|
25484
|
+
- "s JSON. Numeric ops (gt/gte/lt/lte) compare as a number. export type OwnershipAttributeOperator = "
|
|
25485
|
+
- " | "
|
|
25486
|
+
- " | "
|
|
25487
|
+
- " | "
|
|
25488
|
+
- " | "
|
|
25489
|
+
- " | "
|
|
25448
25490
|
Tag:
|
|
25449
25491
|
type: object
|
|
25450
25492
|
properties:
|
package/dist/types/segments.d.ts
CHANGED
|
@@ -5,6 +5,18 @@ export interface InteractionFilterValue {
|
|
|
5
5
|
from?: string;
|
|
6
6
|
to?: string;
|
|
7
7
|
}
|
|
8
|
+
export type CustomFieldOperator = 'equals' | 'exists' | 'gt' | 'gte' | 'lt' | 'lte' | 'before' | 'after' | 'within_days' | 'older_than_days';
|
|
9
|
+
export type OwnershipAttributeOperator = 'equals' | 'exists' | 'gt' | 'gte' | 'lt' | 'lte';
|
|
10
|
+
export interface OwnershipAttributePredicate {
|
|
11
|
+
path: string;
|
|
12
|
+
operator: OwnershipAttributeOperator;
|
|
13
|
+
/** Operand: a number for gt/gte/lt/lte, a string for equals; omitted for exists. */
|
|
14
|
+
value?: string | number;
|
|
15
|
+
}
|
|
16
|
+
export interface OwnershipFacetSelector {
|
|
17
|
+
key: string;
|
|
18
|
+
value: string;
|
|
19
|
+
}
|
|
8
20
|
export type SegmentFilterRule = {
|
|
9
21
|
field: 'interaction';
|
|
10
22
|
op: 'had' | 'exists';
|
|
@@ -28,6 +40,19 @@ export type SegmentFilterRule = {
|
|
|
28
40
|
from?: string;
|
|
29
41
|
to?: string;
|
|
30
42
|
};
|
|
43
|
+
} | {
|
|
44
|
+
type: 'custom_field';
|
|
45
|
+
field: string;
|
|
46
|
+
operator: CustomFieldOperator;
|
|
47
|
+
value?: string | number;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'ownership';
|
|
50
|
+
productId?: string;
|
|
51
|
+
facet?: OwnershipFacetSelector;
|
|
52
|
+
attribute?: OwnershipAttributePredicate;
|
|
53
|
+
olderThanDays?: number;
|
|
54
|
+
withinDays?: number;
|
|
55
|
+
includeDeleted?: boolean;
|
|
31
56
|
} | {
|
|
32
57
|
type: 'interaction';
|
|
33
58
|
interactionId: string;
|
package/dist/utils/paths.d.ts
CHANGED
|
@@ -101,3 +101,94 @@ export interface PortalPathParams {
|
|
|
101
101
|
* ```
|
|
102
102
|
*/
|
|
103
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
|
+
/**
|
|
120
|
+
* A real GS1 **Consumer Product Variant** code (AI 22). Use this when the brand has a
|
|
121
|
+
* genuine CPV. Takes precedence over `variant` when both are given.
|
|
122
|
+
*/
|
|
123
|
+
cpv?: string | {
|
|
124
|
+
id: string;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Internal variant id, emitted as AI 22 (the SmartLinks resolver reads path segment 22
|
|
128
|
+
* as the variant). Prefer `cpv` when you have a real GS1 CPV code — a non-CPV variant id
|
|
129
|
+
* in AI 22 is only meaningful to the SmartLinks resolver, not to third-party GS1 resolvers.
|
|
130
|
+
*/
|
|
131
|
+
variant?: string | {
|
|
132
|
+
id: string;
|
|
133
|
+
};
|
|
134
|
+
/** Batch / lot (AI 10). A string or an object with `id`. */
|
|
135
|
+
lot?: string | {
|
|
136
|
+
id: string;
|
|
137
|
+
};
|
|
138
|
+
/** Alias of `lot` (AI 10). A `BatchResponse` also contributes its expiry date (AI 17) unless `expiry` is set. */
|
|
139
|
+
batch?: BatchResponse | string;
|
|
140
|
+
/** Serial (AI 21) — the specific item / proof. A string (serial / NFC / virtual id) or an object (`serialNumber` ?? `id`, e.g. a proof). */
|
|
141
|
+
serial?: string | {
|
|
142
|
+
id?: string;
|
|
143
|
+
serialNumber?: string;
|
|
144
|
+
};
|
|
145
|
+
/** Expiry date (AI 17). A `Date`, ISO string, or `YYMMDD` string. */
|
|
146
|
+
expiry?: string | Date;
|
|
147
|
+
/**
|
|
148
|
+
* Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
|
|
149
|
+
* `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
|
|
150
|
+
* `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
|
|
151
|
+
* the path in canonical order; everything else becomes a query-string data attribute.
|
|
152
|
+
*/
|
|
153
|
+
ais?: Record<string, string | number | Date>;
|
|
154
|
+
/** GS1 `linkType` (added as a `?linkType=` query param). */
|
|
155
|
+
linkType?: string;
|
|
156
|
+
/** Additional non-GS1 query params. */
|
|
157
|
+
queryParams?: Record<string, string>;
|
|
158
|
+
/** Override custom-domain detection (see {@link buildPortalPath}). */
|
|
159
|
+
customDomain?: boolean;
|
|
160
|
+
/** Return only the path, without a domain. */
|
|
161
|
+
pathOnly?: boolean;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Builds a GS1 Digital Link for a product, with full support for the standard
|
|
165
|
+
* Application Identifiers. Path qualifiers are emitted in the canonical GS1 order
|
|
166
|
+
* (`/01/{gtin}/22/{cpv}/10/{lot}/21/{serial}`); data attributes (expiry, production
|
|
167
|
+
* date, weights, …) become query params; `linkType` is appended for GS1 resolution.
|
|
168
|
+
*
|
|
169
|
+
* A bare `/01/{gtin}` is used when the product owns the GTIN globally (`ownGtin`) or
|
|
170
|
+
* the collection is on its own custom domain; otherwise the GTIN is scoped to the
|
|
171
|
+
* collection with the `/gc/{shortId}` prefix on the shared platform domain.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* // GTIN + variant + lot + serial + expiry, on a custom domain
|
|
176
|
+
* buildGs1DigitalLink({
|
|
177
|
+
* collection, // portalUrl = https://acme.com
|
|
178
|
+
* gtin: '05012345678900',
|
|
179
|
+
* variant: 'red',
|
|
180
|
+
* lot: 'LOT42',
|
|
181
|
+
* serial: proof, // AI 21 from proof.serialNumber ?? proof.id
|
|
182
|
+
* expiry: '2026-06-30',
|
|
183
|
+
* })
|
|
184
|
+
* // → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
|
|
185
|
+
*
|
|
186
|
+
* // Arbitrary AIs via the generic map
|
|
187
|
+
* buildGs1DigitalLink({
|
|
188
|
+
* collection, gtin: '05012345678900',
|
|
189
|
+
* ais: { '11': new Date('2025-01-01'), '3103': '000500' }, // production date + net weight (kg)
|
|
190
|
+
* linkType: 'gs1:pip',
|
|
191
|
+
* })
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export declare function buildGs1DigitalLink(params: Gs1DigitalLinkParams): string;
|
package/dist/utils/paths.js
CHANGED
|
@@ -135,35 +135,25 @@ export function buildPortalPath(params) {
|
|
|
135
135
|
? proof
|
|
136
136
|
: proof.id
|
|
137
137
|
: undefined;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// 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).
|
|
141
140
|
if (gtin) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
pathname += `/10/${extractedBatchId}`;
|
|
155
|
-
// Add expiry date as query param (GS1 AI 17)
|
|
156
|
-
if (expiryDate) {
|
|
157
|
-
const dateStr = formatExpiryDate(expiryDate);
|
|
158
|
-
searchParams.append('17', dateStr);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// Add variant (GS1 AI 22)
|
|
162
|
-
if (variantId) {
|
|
163
|
-
pathname += `/22/${variantId}`;
|
|
164
|
-
}
|
|
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
|
+
});
|
|
165
153
|
}
|
|
166
|
-
|
|
154
|
+
let pathname = '';
|
|
155
|
+
const searchParams = new URLSearchParams();
|
|
156
|
+
if (extractedProductId) {
|
|
167
157
|
// Regular product path
|
|
168
158
|
pathname = `/c/${shortId}/${extractedProductId}`;
|
|
169
159
|
// Add proof to path
|
|
@@ -205,3 +195,129 @@ function formatExpiryDate(date) {
|
|
|
205
195
|
const day = date.getDate().toString().padStart(2, '0');
|
|
206
196
|
return `${year}${month}${day}`;
|
|
207
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.24 | Generated: 2026-08-31T17:50:11.496Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -7531,6 +7531,23 @@ interface InteractionFilterValue {
|
|
|
7531
7531
|
}
|
|
7532
7532
|
```
|
|
7533
7533
|
|
|
7534
|
+
**OwnershipAttributePredicate** (interface)
|
|
7535
|
+
```typescript
|
|
7536
|
+
interface OwnershipAttributePredicate {
|
|
7537
|
+
path: string
|
|
7538
|
+
operator: OwnershipAttributeOperator
|
|
7539
|
+
value?: string | number
|
|
7540
|
+
}
|
|
7541
|
+
```
|
|
7542
|
+
|
|
7543
|
+
**OwnershipFacetSelector** (interface)
|
|
7544
|
+
```typescript
|
|
7545
|
+
interface OwnershipFacetSelector {
|
|
7546
|
+
key: string
|
|
7547
|
+
value: string
|
|
7548
|
+
}
|
|
7549
|
+
```
|
|
7550
|
+
|
|
7534
7551
|
**SegmentRecord** (interface)
|
|
7535
7552
|
```typescript
|
|
7536
7553
|
interface SegmentRecord {
|
|
@@ -7591,6 +7608,10 @@ interface SegmentRecipientsResponse {
|
|
|
7591
7608
|
}
|
|
7592
7609
|
```
|
|
7593
7610
|
|
|
7611
|
+
**CustomFieldOperator** = ``
|
|
7612
|
+
|
|
7613
|
+
**OwnershipAttributeOperator** = `'equals' | 'exists' | 'gt' | 'gte' | 'lt' | 'lte'`
|
|
7614
|
+
|
|
7594
7615
|
**SegmentFilterRule** = ``
|
|
7595
7616
|
|
|
7596
7617
|
### tags
|
|
@@ -8275,6 +8296,37 @@ interface PortalPathParams {
|
|
|
8275
8296
|
}
|
|
8276
8297
|
```
|
|
8277
8298
|
|
|
8299
|
+
**Gs1DigitalLinkParams** (interface)
|
|
8300
|
+
```typescript
|
|
8301
|
+
interface Gs1DigitalLinkParams {
|
|
8302
|
+
collection?: Collection | { shortId?: string; portalUrl?: string; redirectUrl?: string }
|
|
8303
|
+
domain?: string
|
|
8304
|
+
gtin?: string
|
|
8305
|
+
product?: Product
|
|
8306
|
+
ownGtin?: boolean
|
|
8307
|
+
* A real GS1 **Consumer Product Variant** code (AI 22). Use this when the brand has a
|
|
8308
|
+
* genuine CPV. Takes precedence over `variant` when both are given.
|
|
8309
|
+
cpv?: string | { id: string }
|
|
8310
|
+
* Internal variant id, emitted as AI 22 (the SmartLinks resolver reads path segment 22
|
|
8311
|
+
* as the variant). Prefer `cpv` when you have a real GS1 CPV code — a non-CPV variant id
|
|
8312
|
+
* in AI 22 is only meaningful to the SmartLinks resolver, not to third-party GS1 resolvers.
|
|
8313
|
+
variant?: string | { id: string }
|
|
8314
|
+
lot?: string | { id: string }
|
|
8315
|
+
batch?: BatchResponse | string
|
|
8316
|
+
serial?: string | { id?: string; serialNumber?: string }
|
|
8317
|
+
expiry?: string | Date
|
|
8318
|
+
* Any other GS1 Application Identifiers as `{ [ai]: value }` — e.g.
|
|
8319
|
+
* `{ '11': prodDate, '3103': '000500' }`. Date AIs (11/12/13/15/16/17) accept a
|
|
8320
|
+
* `Date` and are formatted `YYMMDD`; path-qualifier AIs (22/10/21) are placed in
|
|
8321
|
+
* the path in canonical order; everything else becomes a query-string data attribute.
|
|
8322
|
+
ais?: Record<string, string | number | Date>
|
|
8323
|
+
linkType?: string
|
|
8324
|
+
queryParams?: Record<string, string>
|
|
8325
|
+
customDomain?: boolean
|
|
8326
|
+
pathOnly?: boolean
|
|
8327
|
+
}
|
|
8328
|
+
```
|
|
8329
|
+
|
|
8278
8330
|
## API Functions
|
|
8279
8331
|
|
|
8280
8332
|
### analytics.admin
|
package/docs/utils.md
CHANGED
|
@@ -219,6 +219,53 @@ utils.buildPortalPath({
|
|
|
219
219
|
// Returns: https://portal.smartlinks.io/c/abc123/prod123?utm_source=email&utm_campaign=launch&lang=fr
|
|
220
220
|
```
|
|
221
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` | 22 | Real GS1 Consumer Product Variant code (path). Wins over `variant`. |
|
|
237
|
+
| `variant` | 22 | Internal variant id, emitted as AI 22 for the SmartLinks resolver. Use `cpv` for a genuine GS1 CPV. |
|
|
238
|
+
| `lot` / `batch` | 10 | Batch/lot (path); a batch object also supplies its expiry |
|
|
239
|
+
| `serial` | 21 | Specific item — a string, or an object (`serialNumber` ?? `id`, e.g. a proof / serial / virtual id) |
|
|
240
|
+
| `expiry` | 17 | Date → `YYMMDD` (query) |
|
|
241
|
+
| `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 |
|
|
242
|
+
| `linkType` | — | GS1 `linkType`, added as `?linkType=` |
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// Variant + lot + serial + expiry, on a custom domain
|
|
246
|
+
utils.buildGs1DigitalLink({
|
|
247
|
+
collection, // portalUrl = https://acme.com
|
|
248
|
+
gtin: '05012345678900',
|
|
249
|
+
variant: 'red',
|
|
250
|
+
lot: 'LOT42',
|
|
251
|
+
serial: proof, // AI 21 from proof.serialNumber ?? proof.id
|
|
252
|
+
expiry: '2026-06-30',
|
|
253
|
+
})
|
|
254
|
+
// → https://acme.com/01/05012345678900/22/red/10/LOT42/21/{serial}?17=260630
|
|
255
|
+
|
|
256
|
+
// Arbitrary AIs via the generic map
|
|
257
|
+
utils.buildGs1DigitalLink({
|
|
258
|
+
collection,
|
|
259
|
+
gtin: '05012345678900',
|
|
260
|
+
ais: { '11': new Date(2025, 0, 1), '3103': '000500' }, // production date + net weight
|
|
261
|
+
linkType: 'gs1:pip',
|
|
262
|
+
})
|
|
263
|
+
// → https://.../01/05012345678900?11=250101&3103=000500&linkType=gs1%3Apip
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`buildPortalPath` delegates its GTIN paths to this function, so both stay in sync (and both now
|
|
267
|
+
emit qualifiers in canonical `22 → 10 → 21` order).
|
|
268
|
+
|
|
222
269
|
## Use Cases
|
|
223
270
|
|
|
224
271
|
### QR Code Generation
|
package/openapi.yaml
CHANGED
|
@@ -25340,6 +25340,29 @@ components:
|
|
|
25340
25340
|
type: string
|
|
25341
25341
|
required:
|
|
25342
25342
|
- interactionId
|
|
25343
|
+
OwnershipAttributePredicate:
|
|
25344
|
+
type: object
|
|
25345
|
+
properties:
|
|
25346
|
+
path:
|
|
25347
|
+
type: string
|
|
25348
|
+
operator:
|
|
25349
|
+
$ref: "#/components/schemas/OwnershipAttributeOperator"
|
|
25350
|
+
value:
|
|
25351
|
+
type: object
|
|
25352
|
+
additionalProperties: true
|
|
25353
|
+
required:
|
|
25354
|
+
- path
|
|
25355
|
+
- operator
|
|
25356
|
+
OwnershipFacetSelector:
|
|
25357
|
+
type: object
|
|
25358
|
+
properties:
|
|
25359
|
+
key:
|
|
25360
|
+
type: string
|
|
25361
|
+
value:
|
|
25362
|
+
type: string
|
|
25363
|
+
required:
|
|
25364
|
+
- key
|
|
25365
|
+
- value
|
|
25343
25366
|
SegmentRecord:
|
|
25344
25367
|
type: object
|
|
25345
25368
|
properties:
|
|
@@ -25445,6 +25468,25 @@ components:
|
|
|
25445
25468
|
- limit
|
|
25446
25469
|
- offset
|
|
25447
25470
|
- total
|
|
25471
|
+
CustomFieldOperator:
|
|
25472
|
+
type: string
|
|
25473
|
+
enum:
|
|
25474
|
+
- equals
|
|
25475
|
+
- exists
|
|
25476
|
+
- gt
|
|
25477
|
+
- gte
|
|
25478
|
+
- lt
|
|
25479
|
+
- lte
|
|
25480
|
+
- before
|
|
25481
|
+
- after
|
|
25482
|
+
- within_days
|
|
25483
|
+
- older_than_days
|
|
25484
|
+
- "s JSON. Numeric ops (gt/gte/lt/lte) compare as a number. export type OwnershipAttributeOperator = "
|
|
25485
|
+
- " | "
|
|
25486
|
+
- " | "
|
|
25487
|
+
- " | "
|
|
25488
|
+
- " | "
|
|
25489
|
+
- " | "
|
|
25448
25490
|
Tag:
|
|
25449
25491
|
type: object
|
|
25450
25492
|
properties:
|