@proveanything/smartlinks 1.15.10 → 1.15.13
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/api/authKit.d.ts +58 -3
- package/dist/api/authKit.js +116 -4
- package/dist/docs/API_SUMMARY.md +125 -9
- package/dist/docs/analytics-metadata-conventions.md +4 -0
- package/dist/docs/analytics.md +37 -3
- package/dist/docs/appConfig.md +46 -5
- package/dist/docs/auth-kit.md +110 -0
- package/dist/docs/item-context.md +1 -2
- package/dist/docs/utils.md +2 -2
- package/dist/openapi.yaml +415 -1
- package/dist/types/analytics.d.ts +37 -2
- package/dist/types/appConfiguration.d.ts +16 -0
- package/dist/types/authKit.d.ts +90 -0
- package/dist/utils/conditions.d.ts +7 -11
- package/dist/utils/conditions.js +4 -10
- package/docs/API_SUMMARY.md +125 -9
- package/docs/analytics-metadata-conventions.md +4 -0
- package/docs/analytics.md +37 -3
- package/docs/appConfig.md +46 -5
- package/docs/auth-kit.md +110 -0
- package/docs/item-context.md +1 -2
- package/docs/utils.md +2 -2
- package/openapi.yaml +415 -1
- package/package.json +1 -1
package/dist/api/authKit.d.ts
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import type { AuthLoginResponse, AppleLoginOptions, RefreshResponse, LogoutResponse, PhoneSendCodeResponse, PhoneVerifyResponse, PasswordResetRequestResponse, VerifyResetTokenResponse, PasswordResetCompleteResponse, EmailVerificationActionResponse, EmailVerifyTokenResponse, AuthKitConfig, MagicLinkSendResponse, MagicLinkVerifyResponse, UserProfile, UpdateProfileResponse, ProfileUpdateData, SuccessResponse, SendWhatsAppRequest, SendWhatsAppResponse, ExchangeWhatsAppSessionResponse, VerifyWhatsAppResponse, WhatsAppStatusResponse, SendSmsVerifyRequest, SendSmsVerifyResponse, VerifySmsResponse, UpsertContactRequest, UpsertContactResponse } from "../types/authKit";
|
|
1
|
+
import type { AuthLoginResponse, AppleLoginOptions, RefreshResponse, LogoutResponse, PhoneSendCodeResponse, PhoneVerifyResponse, PasswordResetRequestResponse, VerifyResetTokenResponse, PasswordResetCompleteResponse, EmailVerificationActionResponse, EmailVerifyTokenResponse, AuthKitConfig, MagicLinkSendResponse, MagicLinkVerifyResponse, UserProfile, UpdateProfileResponse, ProfileUpdateData, SuccessResponse, SendWhatsAppRequest, SendWhatsAppResponse, ExchangeWhatsAppSessionResponse, VerifyWhatsAppResponse, WhatsAppStatusResponse, SendSmsVerifyRequest, SendSmsVerifyResponse, VerifySmsResponse, UpsertContactRequest, UpsertContactResponse, MfaChallengeSendResponse, MfaFinalizeResponse, MfaEnrollSendResponse, MfaEnrolledResponse, MfaFactorsResponse, TrustedDevice } from "../types/authKit";
|
|
2
2
|
/**
|
|
3
3
|
* Namespace containing helper functions for the new AuthKit API.
|
|
4
4
|
* Legacy collection-based authKit helpers retained (marked as *Legacy*).
|
|
5
5
|
*/
|
|
6
6
|
export declare namespace authKit {
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Login with email + password (public).
|
|
9
|
+
*
|
|
10
|
+
* When the client's MFA policy requires a step-up, the server returns **403
|
|
11
|
+
* `MFA_REQUIRED`** instead of a session — `login()` throws a `SmartlinksApiError` with
|
|
12
|
+
* `err.errorResponse?.errorCode === 'MFA_REQUIRED'` and the challenge details in
|
|
13
|
+
* `err.details` (see {@link MfaRequiredDetails}). Route the caller to
|
|
14
|
+
* {@link mfaChallengeSend} on that error; this method's return type is unchanged.
|
|
15
|
+
*
|
|
16
|
+
* @param trustedDeviceToken - Optional. If a previous MFA challenge on this device
|
|
17
|
+
* returned one (via {@link mfaChallengeVerify}/{@link mfaRecoveryCode} with
|
|
18
|
+
* `trustDevice: true`), pass it here to skip the challenge entirely as long as it's
|
|
19
|
+
* still valid. If it's revoked/expired, the server silently falls back to requiring a
|
|
20
|
+
* fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
21
|
+
*/
|
|
22
|
+
function login(clientId: string, email: string, password: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
|
|
9
23
|
/** Register a new user (public). */
|
|
10
24
|
function register(clientId: string, data: {
|
|
11
25
|
email: string;
|
|
@@ -124,6 +138,47 @@ export declare namespace authKit {
|
|
|
124
138
|
function verifyEmailChange(clientId: string, token: string): Promise<SuccessResponse>;
|
|
125
139
|
function updatePhone(clientId: string, phoneNumber: string, verificationCode: string): Promise<SuccessResponse>;
|
|
126
140
|
function deleteAccount(clientId: string, password: string, confirmText: string): Promise<SuccessResponse>;
|
|
141
|
+
/** Send (or resend) an MFA challenge code to the given factor (public). */
|
|
142
|
+
function mfaChallengeSend(clientId: string, mfaSessionToken: string, factor: 'email' | 'sms'): Promise<MfaChallengeSendResponse>;
|
|
143
|
+
/**
|
|
144
|
+
* Verify an MFA challenge code and finalize the login (public). On success this behaves
|
|
145
|
+
* like {@link login} — the bearer token is adopted and the cache invalidated.
|
|
146
|
+
*
|
|
147
|
+
* @param trustDevice - When `true`, the response includes `trustedDeviceToken` — persist
|
|
148
|
+
* it and pass it to future {@link login} calls to skip the challenge on this device.
|
|
149
|
+
*/
|
|
150
|
+
function mfaChallengeVerify(clientId: string, mfaSessionToken: string, code: string, trustDevice?: boolean, deviceLabel?: string): Promise<MfaFinalizeResponse>;
|
|
151
|
+
/**
|
|
152
|
+
* Finalize a challenged login using a single-use recovery code instead of a sent code
|
|
153
|
+
* (public). Same finalize-session behaviour as {@link mfaChallengeVerify}.
|
|
154
|
+
*/
|
|
155
|
+
function mfaRecoveryCode(clientId: string, mfaSessionToken: string, code: string, trustDevice?: boolean, deviceLabel?: string): Promise<MfaFinalizeResponse>;
|
|
156
|
+
/** List enrolled factors and recovery-code count for the current user (authenticated). */
|
|
157
|
+
function getMfaFactors(clientId: string): Promise<MfaFactorsResponse>;
|
|
158
|
+
/** Begin email-factor enrollment; sends a code to the account's existing email (authenticated). */
|
|
159
|
+
function enrollEmailMfa(clientId: string): Promise<MfaEnrollSendResponse>;
|
|
160
|
+
/** Confirm email-factor enrollment with the code sent by {@link enrollEmailMfa} (authenticated). */
|
|
161
|
+
function confirmEmailMfa(clientId: string, mfaSessionToken: string, code: string): Promise<MfaEnrolledResponse>;
|
|
162
|
+
/** Begin SMS-factor enrollment; sends a code to the given phone number (authenticated). */
|
|
163
|
+
function enrollSmsMfa(clientId: string, phoneNumber: string): Promise<MfaEnrollSendResponse>;
|
|
164
|
+
/** Confirm SMS-factor enrollment with the code sent by {@link enrollSmsMfa} (authenticated). */
|
|
165
|
+
function confirmSmsMfa(clientId: string, mfaSessionToken: string, code: string): Promise<MfaEnrolledResponse>;
|
|
166
|
+
/**
|
|
167
|
+
* Generate a fresh set of recovery codes, invalidating any previous set (authenticated).
|
|
168
|
+
* Returned **in plaintext, exactly once** — nothing else in the API ever returns them
|
|
169
|
+
* again, so the caller must display/export them immediately.
|
|
170
|
+
*/
|
|
171
|
+
function generateMfaRecoveryCodes(clientId: string, password: string): Promise<{
|
|
172
|
+
recoveryCodes: string[];
|
|
173
|
+
}>;
|
|
174
|
+
/** Remove an enrolled MFA factor (authenticated). Server route is DELETE with a body. */
|
|
175
|
+
function removeMfaFactor(clientId: string, factor: 'email' | 'sms', password: string): Promise<SuccessResponse>;
|
|
176
|
+
/** List devices trusted to skip MFA challenges for the current user (authenticated). */
|
|
177
|
+
function listTrustedDevices(clientId: string): Promise<{
|
|
178
|
+
devices: TrustedDevice[];
|
|
179
|
+
}>;
|
|
180
|
+
/** Revoke a single trusted device by id (authenticated). */
|
|
181
|
+
function revokeTrustedDevice(clientId: string, id: string): Promise<SuccessResponse>;
|
|
127
182
|
function load(authKitId: string): Promise<AuthKitConfig>;
|
|
128
183
|
function get(collectionId: string, authKitId: string): Promise<AuthKitConfig>;
|
|
129
184
|
function list(collectionId: string, admin?: boolean): Promise<AuthKitConfig[]>;
|
package/dist/api/authKit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { request, post, put, del, setBearerToken, invalidateCache } from "../http";
|
|
1
|
+
import { request, post, put, del, requestWithOptions, setBearerToken, invalidateCache } from "../http";
|
|
2
2
|
/**
|
|
3
3
|
* Namespace containing helper functions for the new AuthKit API.
|
|
4
4
|
* Legacy collection-based authKit helpers retained (marked as *Legacy*).
|
|
@@ -8,9 +8,26 @@ export var authKit;
|
|
|
8
8
|
/* ===================================
|
|
9
9
|
* Authentication (Per client)
|
|
10
10
|
* =================================== */
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Login with email + password (public).
|
|
13
|
+
*
|
|
14
|
+
* When the client's MFA policy requires a step-up, the server returns **403
|
|
15
|
+
* `MFA_REQUIRED`** instead of a session — `login()` throws a `SmartlinksApiError` with
|
|
16
|
+
* `err.errorResponse?.errorCode === 'MFA_REQUIRED'` and the challenge details in
|
|
17
|
+
* `err.details` (see {@link MfaRequiredDetails}). Route the caller to
|
|
18
|
+
* {@link mfaChallengeSend} on that error; this method's return type is unchanged.
|
|
19
|
+
*
|
|
20
|
+
* @param trustedDeviceToken - Optional. If a previous MFA challenge on this device
|
|
21
|
+
* returned one (via {@link mfaChallengeVerify}/{@link mfaRecoveryCode} with
|
|
22
|
+
* `trustDevice: true`), pass it here to skip the challenge entirely as long as it's
|
|
23
|
+
* still valid. If it's revoked/expired, the server silently falls back to requiring a
|
|
24
|
+
* fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
25
|
+
*/
|
|
26
|
+
async function login(clientId, email, password, trustedDeviceToken) {
|
|
27
|
+
const body = { email, password };
|
|
28
|
+
if (trustedDeviceToken)
|
|
29
|
+
body.trustedDeviceToken = trustedDeviceToken;
|
|
30
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/login`, body);
|
|
14
31
|
if (res.token) {
|
|
15
32
|
setBearerToken(res.token);
|
|
16
33
|
invalidateCache();
|
|
@@ -277,6 +294,101 @@ export var authKit;
|
|
|
277
294
|
return res;
|
|
278
295
|
}
|
|
279
296
|
authKit.deleteAccount = deleteAccount;
|
|
297
|
+
/* ===================================
|
|
298
|
+
* Step-up MFA — completing a challenged login (public)
|
|
299
|
+
*
|
|
300
|
+
* Called after login() throws MFA_REQUIRED. mfaSessionToken is short-lived (10 min) and
|
|
301
|
+
* single-use — burned on 5 failed attempts (MFA_TOO_MANY_ATTEMPTS) or on success.
|
|
302
|
+
* challenge/send can be called again on the same token (while unexpired/unburned) to
|
|
303
|
+
* switch factors or resend.
|
|
304
|
+
* =================================== */
|
|
305
|
+
/** Send (or resend) an MFA challenge code to the given factor (public). */
|
|
306
|
+
async function mfaChallengeSend(clientId, mfaSessionToken, factor) {
|
|
307
|
+
return post(`/authkit/${encodeURIComponent(clientId)}/mfa/challenge/send`, { mfaSessionToken, factor });
|
|
308
|
+
}
|
|
309
|
+
authKit.mfaChallengeSend = mfaChallengeSend;
|
|
310
|
+
/**
|
|
311
|
+
* Verify an MFA challenge code and finalize the login (public). On success this behaves
|
|
312
|
+
* like {@link login} — the bearer token is adopted and the cache invalidated.
|
|
313
|
+
*
|
|
314
|
+
* @param trustDevice - When `true`, the response includes `trustedDeviceToken` — persist
|
|
315
|
+
* it and pass it to future {@link login} calls to skip the challenge on this device.
|
|
316
|
+
*/
|
|
317
|
+
async function mfaChallengeVerify(clientId, mfaSessionToken, code, trustDevice, deviceLabel) {
|
|
318
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/mfa/challenge/verify`, { mfaSessionToken, code, trustDevice, deviceLabel });
|
|
319
|
+
if (res.token) {
|
|
320
|
+
setBearerToken(res.token);
|
|
321
|
+
invalidateCache();
|
|
322
|
+
}
|
|
323
|
+
return res;
|
|
324
|
+
}
|
|
325
|
+
authKit.mfaChallengeVerify = mfaChallengeVerify;
|
|
326
|
+
/**
|
|
327
|
+
* Finalize a challenged login using a single-use recovery code instead of a sent code
|
|
328
|
+
* (public). Same finalize-session behaviour as {@link mfaChallengeVerify}.
|
|
329
|
+
*/
|
|
330
|
+
async function mfaRecoveryCode(clientId, mfaSessionToken, code, trustDevice, deviceLabel) {
|
|
331
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/mfa/challenge/recovery-code`, { mfaSessionToken, code, trustDevice, deviceLabel });
|
|
332
|
+
if (res.token) {
|
|
333
|
+
setBearerToken(res.token);
|
|
334
|
+
invalidateCache();
|
|
335
|
+
}
|
|
336
|
+
return res;
|
|
337
|
+
}
|
|
338
|
+
authKit.mfaRecoveryCode = mfaRecoveryCode;
|
|
339
|
+
/* ===================================
|
|
340
|
+
* Step-up MFA — factor management (Authenticated)
|
|
341
|
+
* =================================== */
|
|
342
|
+
/** List enrolled factors and recovery-code count for the current user (authenticated). */
|
|
343
|
+
async function getMfaFactors(clientId) {
|
|
344
|
+
return request(`/authkit/${encodeURIComponent(clientId)}/mfa/factors`);
|
|
345
|
+
}
|
|
346
|
+
authKit.getMfaFactors = getMfaFactors;
|
|
347
|
+
/** Begin email-factor enrollment; sends a code to the account's existing email (authenticated). */
|
|
348
|
+
async function enrollEmailMfa(clientId) {
|
|
349
|
+
return post(`/authkit/${encodeURIComponent(clientId)}/mfa/factors/email/enroll`, {});
|
|
350
|
+
}
|
|
351
|
+
authKit.enrollEmailMfa = enrollEmailMfa;
|
|
352
|
+
/** Confirm email-factor enrollment with the code sent by {@link enrollEmailMfa} (authenticated). */
|
|
353
|
+
async function confirmEmailMfa(clientId, mfaSessionToken, code) {
|
|
354
|
+
return post(`/authkit/${encodeURIComponent(clientId)}/mfa/factors/email/confirm`, { mfaSessionToken, code });
|
|
355
|
+
}
|
|
356
|
+
authKit.confirmEmailMfa = confirmEmailMfa;
|
|
357
|
+
/** Begin SMS-factor enrollment; sends a code to the given phone number (authenticated). */
|
|
358
|
+
async function enrollSmsMfa(clientId, phoneNumber) {
|
|
359
|
+
return post(`/authkit/${encodeURIComponent(clientId)}/mfa/factors/sms/enroll`, { phoneNumber });
|
|
360
|
+
}
|
|
361
|
+
authKit.enrollSmsMfa = enrollSmsMfa;
|
|
362
|
+
/** Confirm SMS-factor enrollment with the code sent by {@link enrollSmsMfa} (authenticated). */
|
|
363
|
+
async function confirmSmsMfa(clientId, mfaSessionToken, code) {
|
|
364
|
+
return post(`/authkit/${encodeURIComponent(clientId)}/mfa/factors/sms/confirm`, { mfaSessionToken, code });
|
|
365
|
+
}
|
|
366
|
+
authKit.confirmSmsMfa = confirmSmsMfa;
|
|
367
|
+
/**
|
|
368
|
+
* Generate a fresh set of recovery codes, invalidating any previous set (authenticated).
|
|
369
|
+
* Returned **in plaintext, exactly once** — nothing else in the API ever returns them
|
|
370
|
+
* again, so the caller must display/export them immediately.
|
|
371
|
+
*/
|
|
372
|
+
async function generateMfaRecoveryCodes(clientId, password) {
|
|
373
|
+
return post(`/authkit/${encodeURIComponent(clientId)}/mfa/recovery-codes/generate`, { password });
|
|
374
|
+
}
|
|
375
|
+
authKit.generateMfaRecoveryCodes = generateMfaRecoveryCodes;
|
|
376
|
+
/** Remove an enrolled MFA factor (authenticated). Server route is DELETE with a body. */
|
|
377
|
+
async function removeMfaFactor(clientId, factor, password) {
|
|
378
|
+
const path = `/authkit/${encodeURIComponent(clientId)}/mfa/factors/${factor}`;
|
|
379
|
+
return requestWithOptions(path, { method: 'DELETE', body: JSON.stringify({ password }) });
|
|
380
|
+
}
|
|
381
|
+
authKit.removeMfaFactor = removeMfaFactor;
|
|
382
|
+
/** List devices trusted to skip MFA challenges for the current user (authenticated). */
|
|
383
|
+
async function listTrustedDevices(clientId) {
|
|
384
|
+
return request(`/authkit/${encodeURIComponent(clientId)}/mfa/trusted-devices`);
|
|
385
|
+
}
|
|
386
|
+
authKit.listTrustedDevices = listTrustedDevices;
|
|
387
|
+
/** Revoke a single trusted device by id (authenticated). */
|
|
388
|
+
async function revokeTrustedDevice(clientId, id) {
|
|
389
|
+
return del(`/authkit/${encodeURIComponent(clientId)}/mfa/trusted-devices/${encodeURIComponent(id)}`);
|
|
390
|
+
}
|
|
391
|
+
authKit.revokeTrustedDevice = revokeTrustedDevice;
|
|
280
392
|
/* ===================================
|
|
281
393
|
* Collection-based AuthKit
|
|
282
394
|
* =================================== */
|
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.13 | Generated: 2026-07-27T08:21:16.504Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -1139,6 +1139,17 @@ interface AnalyticsFilterRequest {
|
|
|
1139
1139
|
claimIds?: string[]
|
|
1140
1140
|
isAdmin?: boolean
|
|
1141
1141
|
hasLocation?: boolean
|
|
1142
|
+
* Filter web-events rows by the `source` column (list-match). Web-events
|
|
1143
|
+
* only - has no effect on `source: 'tag'` queries.
|
|
1144
|
+
*
|
|
1145
|
+
* There is deliberately no singular `source` filter: the request's own
|
|
1146
|
+
* top-level `source` field (`'events'` vs `'tag'`) already owns that name
|
|
1147
|
+
* as the table selector and predates this column - same word, two
|
|
1148
|
+
* different things. Use a single-element array (`sources: ['portal']`)
|
|
1149
|
+
* for an exact-match filter.
|
|
1150
|
+
sources?: string[]
|
|
1151
|
+
redirectMode?: string
|
|
1152
|
+
redirectModes?: string[]
|
|
1142
1153
|
}
|
|
1143
1154
|
```
|
|
1144
1155
|
|
|
@@ -1419,6 +1430,11 @@ interface AppliedOverridesSummary {
|
|
|
1419
1430
|
```typescript
|
|
1420
1431
|
interface SystemBlock {
|
|
1421
1432
|
basePlanId?: string
|
|
1433
|
+
* Stable capability tier microapps should branch on instead of
|
|
1434
|
+
* `basePlanId` — see docs/appConfig.md §4.1. Known tiers are `ProductMode`;
|
|
1435
|
+
* an unrecognised value is a future tier your code doesn't know about yet
|
|
1436
|
+
* — fail closed to the nearest tier you do understand rather than erroring.
|
|
1437
|
+
productMode?: ProductMode | (string & {})
|
|
1422
1438
|
addOnKeys?: string[]
|
|
1423
1439
|
apps?: string[]
|
|
1424
1440
|
* Explicit overrides only — an absent key is NOT "off". Resolve with
|
|
@@ -1451,6 +1467,8 @@ interface AppConfigSettings {
|
|
|
1451
1467
|
}
|
|
1452
1468
|
```
|
|
1453
1469
|
|
|
1470
|
+
**ProductMode** = `'simple_redirect' | 'rich_redirect' | 'inform' | 'engage'`
|
|
1471
|
+
|
|
1454
1472
|
### appManifest
|
|
1455
1473
|
|
|
1456
1474
|
**AppBundle** (interface)
|
|
@@ -3089,6 +3107,66 @@ interface AppleLoginOptions {
|
|
|
3089
3107
|
}
|
|
3090
3108
|
```
|
|
3091
3109
|
|
|
3110
|
+
**MfaRequiredDetails** (interface)
|
|
3111
|
+
```typescript
|
|
3112
|
+
interface MfaRequiredDetails {
|
|
3113
|
+
mfaSessionToken: string
|
|
3114
|
+
availableFactors: Array<'email' | 'sms'>
|
|
3115
|
+
preferredFactor: 'email' | 'sms'
|
|
3116
|
+
maskedDestinations: { email?: string; sms?: string }
|
|
3117
|
+
}
|
|
3118
|
+
```
|
|
3119
|
+
|
|
3120
|
+
**MfaChallengeSendResponse** (interface)
|
|
3121
|
+
```typescript
|
|
3122
|
+
interface MfaChallengeSendResponse {
|
|
3123
|
+
success: true
|
|
3124
|
+
factor: 'email' | 'sms'
|
|
3125
|
+
destination: string
|
|
3126
|
+
expiresAt: string
|
|
3127
|
+
}
|
|
3128
|
+
```
|
|
3129
|
+
|
|
3130
|
+
**MfaEnrollSendResponse** (interface)
|
|
3131
|
+
```typescript
|
|
3132
|
+
interface MfaEnrollSendResponse {
|
|
3133
|
+
mfaSessionToken: string
|
|
3134
|
+
destination: string
|
|
3135
|
+
expiresAt: string
|
|
3136
|
+
}
|
|
3137
|
+
```
|
|
3138
|
+
|
|
3139
|
+
**MfaEnrolledResponse** (interface)
|
|
3140
|
+
```typescript
|
|
3141
|
+
interface MfaEnrolledResponse {
|
|
3142
|
+
enrolled: true
|
|
3143
|
+
factor: 'email' | 'sms'
|
|
3144
|
+
}
|
|
3145
|
+
```
|
|
3146
|
+
|
|
3147
|
+
**MfaFactorsResponse** (interface)
|
|
3148
|
+
```typescript
|
|
3149
|
+
interface MfaFactorsResponse {
|
|
3150
|
+
enrolledFactors: {
|
|
3151
|
+
email?: { enrolledAt: string; destination: string }
|
|
3152
|
+
sms?: { enrolledAt: string; destination: string }
|
|
3153
|
+
}
|
|
3154
|
+
recoveryCodesRemaining: number
|
|
3155
|
+
mfaEnabledForClient: boolean
|
|
3156
|
+
}
|
|
3157
|
+
```
|
|
3158
|
+
|
|
3159
|
+
**TrustedDevice** (interface)
|
|
3160
|
+
```typescript
|
|
3161
|
+
interface TrustedDevice {
|
|
3162
|
+
id: string
|
|
3163
|
+
label: string | null
|
|
3164
|
+
createdAtMs: number
|
|
3165
|
+
lastUsedAtMs: number
|
|
3166
|
+
expiresAtMs: number
|
|
3167
|
+
}
|
|
3168
|
+
```
|
|
3169
|
+
|
|
3092
3170
|
**MagicLinkSendResponse** (interface)
|
|
3093
3171
|
```typescript
|
|
3094
3172
|
interface MagicLinkSendResponse {
|
|
@@ -3338,6 +3416,8 @@ interface AuthKitConfig {
|
|
|
3338
3416
|
|
|
3339
3417
|
**AuthKitErrorCode** = ``
|
|
3340
3418
|
|
|
3419
|
+
**MfaErrorCode** = ``
|
|
3420
|
+
|
|
3341
3421
|
**VerifyStatus** = `'pending' | 'verified' | 'failed' | 'expired' | 'unknown'`
|
|
3342
3422
|
|
|
3343
3423
|
### batch
|
|
@@ -8536,8 +8616,8 @@ Gets current account information for the logged in user. Returns user, owner, ac
|
|
|
8536
8616
|
|
|
8537
8617
|
### authKit
|
|
8538
8618
|
|
|
8539
|
-
**login**(clientId: string, email: string, password: string) → `Promise<AuthLoginResponse>`
|
|
8540
|
-
Login with email + password (public).
|
|
8619
|
+
**login**(clientId: string, email: string, password: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
|
|
8620
|
+
Login with email + password (public). When the client's MFA policy requires a step-up, the server returns **403 `MFA_REQUIRED`** instead of a session — `login()` throws a `SmartlinksApiError` with `err.errorResponse?.errorCode === 'MFA_REQUIRED'` and the challenge details in `err.details` (see {@link MfaRequiredDetails}). Route the caller to {@link mfaChallengeSend} on that error; this method's return type is unchanged. returned one (via {@link mfaChallengeVerify}/{@link mfaRecoveryCode} with `trustDevice: true`), pass it here to skip the challenge entirely as long as it's still valid. If it's revoked/expired, the server silently falls back to requiring a fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
8541
8621
|
|
|
8542
8622
|
**register**(clientId: string, data: { email: string; password: string; displayName?: string; accountData?: Record<string, any> }) → `Promise<AuthLoginResponse>`
|
|
8543
8623
|
Register a new user (public).
|
|
@@ -8629,23 +8709,59 @@ Update the authenticated user's profile and replace the bearer token when refres
|
|
|
8629
8709
|
**deleteAccount**(clientId: string, password: string, confirmText: string) → `Promise<SuccessResponse>`
|
|
8630
8710
|
Update the authenticated user's profile and replace the bearer token when refreshed claims are returned.
|
|
8631
8711
|
|
|
8712
|
+
**mfaChallengeSend**(clientId: string, mfaSessionToken: string, factor: 'email' | 'sms') → `Promise<MfaChallengeSendResponse>`
|
|
8713
|
+
Send (or resend) an MFA challenge code to the given factor (public).
|
|
8714
|
+
|
|
8715
|
+
**mfaChallengeVerify**(clientId: string, mfaSessionToken: string, code: string, trustDevice?: boolean, deviceLabel?: string) → `Promise<MfaFinalizeResponse>`
|
|
8716
|
+
Verify an MFA challenge code and finalize the login (public). On success this behaves like {@link login} — the bearer token is adopted and the cache invalidated. it and pass it to future {@link login} calls to skip the challenge on this device.
|
|
8717
|
+
|
|
8718
|
+
**mfaRecoveryCode**(clientId: string, mfaSessionToken: string, code: string, trustDevice?: boolean, deviceLabel?: string) → `Promise<MfaFinalizeResponse>`
|
|
8719
|
+
Finalize a challenged login using a single-use recovery code instead of a sent code (public). Same finalize-session behaviour as {@link mfaChallengeVerify}.
|
|
8720
|
+
|
|
8721
|
+
**getMfaFactors**(clientId: string) → `Promise<MfaFactorsResponse>`
|
|
8722
|
+
List enrolled factors and recovery-code count for the current user (authenticated).
|
|
8723
|
+
|
|
8724
|
+
**enrollEmailMfa**(clientId: string) → `Promise<MfaEnrollSendResponse>`
|
|
8725
|
+
Begin email-factor enrollment; sends a code to the account's existing email (authenticated).
|
|
8726
|
+
|
|
8727
|
+
**confirmEmailMfa**(clientId: string, mfaSessionToken: string, code: string) → `Promise<MfaEnrolledResponse>`
|
|
8728
|
+
Confirm email-factor enrollment with the code sent by {@link enrollEmailMfa} (authenticated).
|
|
8729
|
+
|
|
8730
|
+
**enrollSmsMfa**(clientId: string, phoneNumber: string) → `Promise<MfaEnrollSendResponse>`
|
|
8731
|
+
Begin SMS-factor enrollment; sends a code to the given phone number (authenticated).
|
|
8732
|
+
|
|
8733
|
+
**confirmSmsMfa**(clientId: string, mfaSessionToken: string, code: string) → `Promise<MfaEnrolledResponse>`
|
|
8734
|
+
Confirm SMS-factor enrollment with the code sent by {@link enrollSmsMfa} (authenticated).
|
|
8735
|
+
|
|
8736
|
+
**generateMfaRecoveryCodes**(clientId: string, password: string) → `Promise<`
|
|
8737
|
+
Generate a fresh set of recovery codes, invalidating any previous set (authenticated). Returned **in plaintext, exactly once** — nothing else in the API ever returns them again, so the caller must display/export them immediately.
|
|
8738
|
+
|
|
8739
|
+
**removeMfaFactor**(clientId: string, factor: 'email' | 'sms', password: string) → `Promise<SuccessResponse>`
|
|
8740
|
+
Remove an enrolled MFA factor (authenticated). Server route is DELETE with a body.
|
|
8741
|
+
|
|
8742
|
+
**listTrustedDevices**(clientId: string) → `Promise<`
|
|
8743
|
+
List devices trusted to skip MFA challenges for the current user (authenticated).
|
|
8744
|
+
|
|
8745
|
+
**revokeTrustedDevice**(clientId: string, id: string) → `Promise<SuccessResponse>`
|
|
8746
|
+
Revoke a single trusted device by id (authenticated).
|
|
8747
|
+
|
|
8632
8748
|
**load**(authKitId: string) → `Promise<AuthKitConfig>`
|
|
8633
|
-
|
|
8749
|
+
Revoke a single trusted device by id (authenticated).
|
|
8634
8750
|
|
|
8635
8751
|
**get**(collectionId: string, authKitId: string) → `Promise<AuthKitConfig>`
|
|
8636
|
-
|
|
8752
|
+
Revoke a single trusted device by id (authenticated).
|
|
8637
8753
|
|
|
8638
8754
|
**list**(collectionId: string, admin?: boolean) → `Promise<AuthKitConfig[]>`
|
|
8639
|
-
|
|
8755
|
+
Revoke a single trusted device by id (authenticated).
|
|
8640
8756
|
|
|
8641
8757
|
**create**(collectionId: string, data: any) → `Promise<AuthKitConfig>`
|
|
8642
|
-
|
|
8758
|
+
Revoke a single trusted device by id (authenticated).
|
|
8643
8759
|
|
|
8644
8760
|
**update**(collectionId: string, authKitId: string, data: any) → `Promise<AuthKitConfig>`
|
|
8645
|
-
|
|
8761
|
+
Revoke a single trusted device by id (authenticated).
|
|
8646
8762
|
|
|
8647
8763
|
**remove**(collectionId: string, authKitId: string) → `Promise<void>`
|
|
8648
|
-
|
|
8764
|
+
Revoke a single trusted device by id (authenticated).
|
|
8649
8765
|
|
|
8650
8766
|
### batch
|
|
8651
8767
|
|
|
@@ -15,6 +15,8 @@ Some of these are now promoted top-level analytics fields. Others remain good me
|
|
|
15
15
|
- `entryType`
|
|
16
16
|
- `pageId`
|
|
17
17
|
- `scanMethod`
|
|
18
|
+
- `source` - collection/web-events only. Free-form client app identifier, e.g. `'portal'`, `'hub'`. No enum/whitelist. Not available on tag events - see the `analytics.tag.track(event)` section in [docs/analytics.md](analytics.md) for why.
|
|
19
|
+
- `redirectMode` - tag-events only. Usually server-written; only set this yourself if you're logging a redirect-style event.
|
|
18
20
|
|
|
19
21
|
These should be sent as top-level analytics fields, not inside `metadata`.
|
|
20
22
|
|
|
@@ -59,6 +61,7 @@ These keys give teams a shared vocabulary for:
|
|
|
59
61
|
- Send promoted fields at top level.
|
|
60
62
|
- Keep values flat and scalar where possible so they are easier to filter and break down later.
|
|
61
63
|
- Promote a field to a first-class backend column only when it becomes a hot platform-wide dimension.
|
|
64
|
+
- Note: `source` (the event column) and the query-time `source` parameter (`'events' | 'tag'`, which table to query) are unrelated fields that happen to share a name. When filtering by the `source` column, use the plural `sources` array - there is no singular `source` filter, precisely to avoid colliding with the table selector.
|
|
62
65
|
|
|
63
66
|
---
|
|
64
67
|
|
|
@@ -77,6 +80,7 @@ analytics.collection.track({
|
|
|
77
80
|
campaign: 'summer-launch',
|
|
78
81
|
utmSource: 'email',
|
|
79
82
|
pageId: 'QR123',
|
|
83
|
+
source: 'portal',
|
|
80
84
|
metadata: {
|
|
81
85
|
pagePath: '/c/demo-collection',
|
|
82
86
|
},
|
package/dist/docs/analytics.md
CHANGED
|
@@ -48,6 +48,12 @@ The backend stores custom analytics dimensions in `metadata`, but promoted analy
|
|
|
48
48
|
|
|
49
49
|
See [docs/analytics-metadata-conventions.md](analytics-metadata-conventions.md) for the recommended key set.
|
|
50
50
|
|
|
51
|
+
### A note on server-written events
|
|
52
|
+
|
|
53
|
+
Not every row in these tables is written by your app. As of 2026-07-23, the server itself logs a `scan_redirect` event on the tag-events table at the moment a GS1 digital-link scan, claim short-link scan, or NFC tap decides on a redirect destination - before the client ever loads anything. This is separate from `scan_tag`, which your app still writes on landing exactly as before. `scan_blank_tag` remains excluded from the analytics pipeline.
|
|
54
|
+
|
|
55
|
+
Server-issued redirects also append `?sid=<value>` to the destination URL. If your landing page reads `sid` off the URL and reuses it as its own `sessionId` on the next `analytics.collection.track(...)` or `analytics.tag.track(...)` call, "did the redirect actually land" becomes a free `sessionId` join - nothing new to send, just reuse the value if it's present.
|
|
56
|
+
|
|
51
57
|
---
|
|
52
58
|
|
|
53
59
|
## Quick Start
|
|
@@ -67,6 +73,7 @@ analytics.collection.track({
|
|
|
67
73
|
appId: 'homepage',
|
|
68
74
|
path: '/c/demo-collection',
|
|
69
75
|
deviceType: 'mobile',
|
|
76
|
+
source: 'portal',
|
|
70
77
|
})
|
|
71
78
|
```
|
|
72
79
|
|
|
@@ -298,7 +305,9 @@ Tracks generic collection analytics events such as:
|
|
|
298
305
|
- internal navigation
|
|
299
306
|
- outbound link activity
|
|
300
307
|
|
|
301
|
-
Supported top-level fields include the core event fields plus promoted analytics columns such as `visitorId`, `referrerHost`, `pageId`, and `
|
|
308
|
+
Supported top-level fields include the core event fields plus promoted analytics columns such as `visitorId`, `referrerHost`, `pageId`, `entryType`, and `source`, along with custom metadata dimensions like `group`, `placement`, `pagePath`, and `qrCodeId`.
|
|
309
|
+
|
|
310
|
+
`source` is a free-form string identifying which client app logged the event, e.g. `'portal'`, `'hub'` - there's no enum or whitelist, send whatever identifies your app. It's web-events only; there is no equivalent field on tag events (see the `analytics.tag.track(event)` section below for why).
|
|
302
311
|
|
|
303
312
|
Example:
|
|
304
313
|
|
|
@@ -321,6 +330,7 @@ analytics.collection.track({
|
|
|
321
330
|
group: 'summer-launch',
|
|
322
331
|
placement: 'hero',
|
|
323
332
|
pageId: 'QR123',
|
|
333
|
+
source: 'portal',
|
|
324
334
|
metadata: { pagePath: '/c/demo-collection?pageId=QR123' },
|
|
325
335
|
})
|
|
326
336
|
```
|
|
@@ -334,7 +344,11 @@ Tracks physical scan analytics such as:
|
|
|
334
344
|
- claim/code activity
|
|
335
345
|
- admin vs customer scan behavior
|
|
336
346
|
|
|
337
|
-
Supported top-level fields include the core scan fields plus promoted analytics columns such as `visitorId`, `entryType`, and `
|
|
347
|
+
Supported top-level fields include the core scan fields plus promoted analytics columns such as `visitorId`, `entryType`, `scanMethod`, and `redirectMode`, along with custom metadata dimensions like `group`, `tag`, and campaign extras.
|
|
348
|
+
|
|
349
|
+
`redirectMode` is only relevant if you're logging a redirect-style event yourself - in practice it's mostly written by the server automatically (see [A note on server-written events](#a-note-on-server-written-events) above).
|
|
350
|
+
|
|
351
|
+
There is deliberately no `source` field on tag events, even though one exists on collection events. `eventType` already tells you who wrote a tag-events row - `scan_redirect` is always server-written, `scan_tag`/`scan_blank_tag` are always client-written - so a `source` column here would just duplicate that. To segment tag scans by NFC vs. QR, use `entryType`/`scanMethod` instead.
|
|
338
352
|
|
|
339
353
|
Example:
|
|
340
354
|
|
|
@@ -541,6 +555,24 @@ const countries = await analytics.admin.breakdown('demo-collection', {
|
|
|
541
555
|
})
|
|
542
556
|
```
|
|
543
557
|
|
|
558
|
+
Breaking down or filtering by the new `source` *column* (web-events only) works the same way, just watch the naming: the request's own top-level `source: 'events'` is the pre-existing table selector, and it sits alongside a `sources` array that filters by the column's value. They are unrelated fields that happen to share a name.
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
const appSources = await analytics.admin.breakdown('demo-collection', {
|
|
562
|
+
source: 'events', // table selector: query web-events
|
|
563
|
+
dimension: 'source', // break down by the `source` column
|
|
564
|
+
metric: 'count',
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
const portalOnly = await analytics.admin.events('demo-collection', {
|
|
568
|
+
source: 'events', // table selector
|
|
569
|
+
sources: ['portal'], // column filter - single-element array for exact match
|
|
570
|
+
limit: 100,
|
|
571
|
+
})
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
There is no singular `source` filter field for this reason - use `sources: ['portal']` for an exact match.
|
|
575
|
+
|
|
544
576
|
### Raw events
|
|
545
577
|
|
|
546
578
|
```typescript
|
|
@@ -588,7 +620,7 @@ For metrics, generic queries support:
|
|
|
588
620
|
- `uniqueSessions`
|
|
589
621
|
- `uniqueVisitors`
|
|
590
622
|
|
|
591
|
-
Extra collection-event filters include:
|
|
623
|
+
Extra collection-event (web-events) filters include:
|
|
592
624
|
|
|
593
625
|
- `appId`, `appIds[]`
|
|
594
626
|
- `destinationAppId`, `destinationAppIds[]`
|
|
@@ -596,6 +628,7 @@ Extra collection-event filters include:
|
|
|
596
628
|
- `href`, `path`
|
|
597
629
|
- `hrefContains`, `pathContains`
|
|
598
630
|
- `isExternal`
|
|
631
|
+
- `sources[]` - filter by the `source` column (list-match only, no singular form - see [Breakdown](#breakdown))
|
|
599
632
|
|
|
600
633
|
Extra tag-event filters include:
|
|
601
634
|
|
|
@@ -603,6 +636,7 @@ Extra tag-event filters include:
|
|
|
603
636
|
- `claimId`, `claimIds[]`
|
|
604
637
|
- `isAdmin`
|
|
605
638
|
- `hasLocation`
|
|
639
|
+
- `redirectMode`, `redirectModes[]`
|
|
606
640
|
|
|
607
641
|
---
|
|
608
642
|
|
package/dist/docs/appConfig.md
CHANGED
|
@@ -60,9 +60,15 @@ export interface AppEntry {
|
|
|
60
60
|
[k: string]: unknown; // per-module inline settings preserved
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Known `productMode` tiers, low → high — see §4.1. Not exhaustive; unrecognised values are future tiers. */
|
|
64
|
+
export type ProductMode = 'simple_redirect' | 'rich_redirect' | 'inform' | 'engage';
|
|
65
|
+
|
|
63
66
|
/** Resolved entitlements — safe for every client to read. */
|
|
64
67
|
export interface SystemBlock {
|
|
68
|
+
/** Internal billing identifier — not a capability signal, see §4. */
|
|
65
69
|
basePlanId?: string;
|
|
70
|
+
/** Stable capability tier — see §4.1. Prefer this over basePlanId. */
|
|
71
|
+
productMode?: ProductMode | (string & {});
|
|
66
72
|
addOnKeys?: string[];
|
|
67
73
|
apps?: string[]; // apps unlocked by add-ons
|
|
68
74
|
features?: Record<string, boolean>; // explicit overrides only — see §4.4 for absent-key resolution
|
|
@@ -184,12 +190,46 @@ Written only by `entitlements-reconcile`. Represents the union of:
|
|
|
184
190
|
- every add-on line item on their Stripe subscription
|
|
185
191
|
- `systemPrivate.overrides` (applied last, always wins)
|
|
186
192
|
|
|
187
|
-
|
|
193
|
+
`basePlanId` is an internal Stripe billing/plan identifier (string,
|
|
194
|
+
sourced from subscription metadata) — a billing concern, not a capability
|
|
195
|
+
signal. Don't branch app behavior on its literal value or its internal
|
|
196
|
+
plan names; that's what `productMode` is for.
|
|
197
|
+
|
|
198
|
+
### 4.1 `productMode`
|
|
199
|
+
|
|
200
|
+
Stable capability tier the app should branch on instead of `basePlanId`
|
|
201
|
+
(a billing identity that can change independently of capability). Written
|
|
202
|
+
by `entitlements-reconcile` on every sync. Typed as `ProductMode` in the
|
|
203
|
+
SDK (`src/types/appConfiguration.ts`) — a union of the known tiers below,
|
|
204
|
+
plus any other string, since future tiers will show up as new values
|
|
205
|
+
before the SDK type is updated to know their names.
|
|
206
|
+
|
|
207
|
+
Ladder, low → high:
|
|
208
|
+
|
|
209
|
+
| `productMode` | Unlocks (roughly) |
|
|
210
|
+
| ------------------ | --------------------------------------------------------------------------------- |
|
|
211
|
+
| `simple_redirect` | Redirect-only: scanning a tag/QR sends the user straight to a URL — no product/proof page, no catalog UI. |
|
|
212
|
+
| `rich_redirect` | Adds a rendered product/proof page (image, description, spec data) — still no deeper apps. |
|
|
213
|
+
| `inform` | Adds informational surfaces beyond the basic page — documentation, provenance, facts-style apps. |
|
|
214
|
+
| `engage` | Adds engagement/ownership capabilities — claim, CRM, loyalty, interaction tracking. Top of the ladder today. |
|
|
215
|
+
|
|
216
|
+
> These four one-liners are inferred from the tier names and ladder
|
|
217
|
+
> ordering, not copied from an authoritative source — worth a sanity check
|
|
218
|
+
> against the actual capability gating before treating them as precise.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
const mode = cfg.system?.productMode;
|
|
222
|
+
if (mode === 'simple_redirect' || mode === 'rich_redirect') {
|
|
223
|
+
// redirect-only surface — hide catalog UI
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Unknown values (future tiers your code predates) should fail closed to
|
|
228
|
+
the nearest tier you understand, not throw or assume the top tier.
|
|
188
229
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`hub_enrich`, `hub_engage`.
|
|
230
|
+
Not a feature flag and not a billing key — per-capability gating still
|
|
231
|
+
goes through `features` (§4.4); Stripe price selection still goes
|
|
232
|
+
through `basePlanId` + add-ons.
|
|
193
233
|
|
|
194
234
|
### 4.2 `addOnKeys[]`
|
|
195
235
|
|
|
@@ -253,6 +293,7 @@ about:
|
|
|
253
293
|
| Flag | Gates |
|
|
254
294
|
| -------------- | ---------------------------------------------------------------------- |
|
|
255
295
|
| `analytics` | Analytics surfaces (dashboards, reporting, event exploration). |
|
|
296
|
+
| `authenticity` | Live authenticity tracking — flags genuine vs. counterfeit items and enables auditing/tracing on them (e.g. impossible-travel detection). |
|
|
256
297
|
| `crm` | Customer interaction / CRM — contact records, broadcasts, segments. Sub-features hang off the `crm.*` dotted prefix, e.g. `crm.broadcasts`, `crm.segments`. |
|
|
257
298
|
| `hub` | The Hub module. |
|
|
258
299
|
| `portal` | QR code scan portals. |
|