@proveanything/smartlinks 1.15.17 → 1.15.19
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/README.md +10 -2
- package/dist/api/appObjects.d.ts +6 -0
- package/dist/api/appObjects.js +10 -0
- package/dist/api/authKit.d.ts +10 -0
- package/dist/api/authKit.js +10 -0
- package/dist/api/proof.d.ts +24 -1
- package/dist/api/proof.js +25 -2
- package/dist/docs/API_SUMMARY.md +96 -10
- package/dist/docs/app-objects.md +65 -0
- package/dist/docs/assets.md +81 -0
- package/dist/docs/auth-kit.md +73 -0
- package/dist/docs/proof-share-grants.md +232 -0
- package/dist/index.d.ts +2 -2
- package/dist/openapi.yaml +161 -7
- package/dist/types/appObjects.d.ts +14 -0
- package/dist/types/authKit.d.ts +67 -1
- package/dist/types/proof.d.ts +60 -5
- package/docs/API_SUMMARY.md +96 -10
- package/docs/app-objects.md +65 -0
- package/docs/assets.md +81 -0
- package/docs/auth-kit.md +73 -0
- package/docs/proof-share-grants.md +232 -0
- package/openapi.yaml +161 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ If you're new to the SDK, this is the easiest path:
|
|
|
22
22
|
|
|
23
23
|
- [docs/ai.md](docs/ai.md) — AI responses, chat, RAG, voice, streaming, and product assistants
|
|
24
24
|
- [docs/analytics.md](docs/analytics.md) — fire-and-forget web analytics, tag scan telemetry, and dashboard queries
|
|
25
|
+
- [docs/proof-share-grants.md](docs/proof-share-grants.md) — delegated, scoped, revocable share links for a proof (album sharing, guest comments, proof-of-ownership)
|
|
26
|
+
- [docs/assets.md](docs/assets.md) — asset uploads, including resumable uploads for large files (video)
|
|
27
|
+
- [docs/app-objects.md](docs/app-objects.md) — cases, threads (comments), and records
|
|
25
28
|
- [docs/translations.md](docs/translations.md) — runtime translation lookup, browser-side caching, and translation admin flows
|
|
26
29
|
- [docs/widgets.md](docs/widgets.md) — embeddable React components
|
|
27
30
|
- [docs/realtime.md](docs/realtime.md) — subscriptions and live updates
|
|
@@ -33,17 +36,22 @@ If you're new to the SDK, this is the easiest path:
|
|
|
33
36
|
- **Build an AI assistant** → start with [docs/ai.md](docs/ai.md)
|
|
34
37
|
- **Track page views, clicks, or tag scans** → start with [docs/analytics.md](docs/analytics.md)
|
|
35
38
|
- **Translate dynamic content with local browser caching** → start with [docs/translations.md](docs/translations.md)
|
|
39
|
+
- **Share a proof / album by link, or add guest comments** → start with [docs/proof-share-grants.md](docs/proof-share-grants.md)
|
|
40
|
+
- **Upload large video with resume-on-reconnect** → see [Resumable uploads](docs/assets.md#resumable-uploads-large-files-eg-video)
|
|
36
41
|
- **Fetch collections/products** → see [Quick start](README.md#quick-start)
|
|
37
42
|
- **Authenticate admins or end users** → see [Authentication](README.md#authentication)
|
|
38
43
|
- **Upload and manage files** → see [Assets](README.md#assets)
|
|
39
|
-
- **Browse the full surface area** → use [API_SUMMARY.md](API_SUMMARY.md) as reference
|
|
44
|
+
- **Browse the full surface area** → use [API_SUMMARY.md](docs/API_SUMMARY.md) as reference
|
|
40
45
|
|
|
41
46
|
For the full list of functions and types, see the API summary:
|
|
42
|
-
→ [API Summary](API_SUMMARY.md)
|
|
47
|
+
→ [API Summary](docs/API_SUMMARY.md)
|
|
43
48
|
|
|
44
49
|
**Documentation:**
|
|
45
50
|
- [AI & Chat Completions](docs/ai.md) - Chat completions, RAG, voice integration
|
|
46
51
|
- [Analytics](docs/analytics.md) - Fire-and-forget analytics tracking, tag scans, and dashboard queries
|
|
52
|
+
- [Proof Share Grants](docs/proof-share-grants.md) - Delegated, revocable share links; guest comments; proof-of-ownership
|
|
53
|
+
- [Assets](docs/assets.md) - Uploads, resumable large-file uploads, token uploads, replace/versioning
|
|
54
|
+
- [App Objects](docs/app-objects.md) - Cases, threads (comments), records
|
|
47
55
|
- [Translations](docs/translations.md) - Runtime translation lookup, browser-side IndexedDB caching, and admin translation management
|
|
48
56
|
- [Widgets](docs/widgets.md) - Embeddable React components
|
|
49
57
|
- [Realtime](docs/realtime.md) - Realtime data updates
|
package/dist/api/appObjects.d.ts
CHANGED
|
@@ -85,6 +85,12 @@ export declare namespace app {
|
|
|
85
85
|
* Atomically appends to replies array, increments replyCount, updates lastReplyAt
|
|
86
86
|
*/
|
|
87
87
|
function reply(collectionId: string, appId: string, threadId: string, input: ReplyInput, admin?: boolean): Promise<AppThread>;
|
|
88
|
+
/**
|
|
89
|
+
* Delete a single reply from a thread by its reply id (moderation).
|
|
90
|
+
* DELETE /threads/:threadId/reply/:replyId
|
|
91
|
+
* Authorised for the reply's author, the proof owner, or a collection admin.
|
|
92
|
+
*/
|
|
93
|
+
function deleteReply(collectionId: string, appId: string, threadId: string, replyId: string, admin?: boolean): Promise<AppThread>;
|
|
88
94
|
/**
|
|
89
95
|
* Get aggregate statistics for threads
|
|
90
96
|
* POST /threads/aggregate
|
package/dist/api/appObjects.js
CHANGED
|
@@ -164,6 +164,16 @@ export var app;
|
|
|
164
164
|
return post(path, input);
|
|
165
165
|
}
|
|
166
166
|
threads.reply = reply;
|
|
167
|
+
/**
|
|
168
|
+
* Delete a single reply from a thread by its reply id (moderation).
|
|
169
|
+
* DELETE /threads/:threadId/reply/:replyId
|
|
170
|
+
* Authorised for the reply's author, the proof owner, or a collection admin.
|
|
171
|
+
*/
|
|
172
|
+
async function deleteReply(collectionId, appId, threadId, replyId, admin = false) {
|
|
173
|
+
const path = `${basePath(collectionId, appId, admin)}/${encodeURIComponent(threadId)}/reply/${encodeURIComponent(replyId)}`;
|
|
174
|
+
return del(path);
|
|
175
|
+
}
|
|
176
|
+
threads.deleteReply = deleteReply;
|
|
167
177
|
/**
|
|
168
178
|
* Get aggregate statistics for threads
|
|
169
179
|
* POST /threads/aggregate
|
package/dist/api/authKit.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ export declare namespace authKit {
|
|
|
18
18
|
* `trustDevice: true`), pass it here to skip the challenge entirely as long as it's
|
|
19
19
|
* still valid. If it's revoked/expired, the server silently falls back to requiring a
|
|
20
20
|
* fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
21
|
+
*
|
|
22
|
+
* Security errors (thrown as `SmartlinksApiError`, see {@link LoginSecurityErrorCode}):
|
|
23
|
+
* - `ACCOUNT_TEMPORARILY_LOCKED` (429) — `err.details.retryAfterSeconds` says how long to wait.
|
|
24
|
+
* - `PASSWORD_EXPIRED` (403) — `err.details.resetToken` is short-lived; route into
|
|
25
|
+
* {@link completePasswordReset} to change the password in place.
|
|
21
26
|
*/
|
|
22
27
|
function login(clientId: string, email: string, password: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
|
|
23
28
|
/**
|
|
@@ -25,6 +30,11 @@ export declare namespace authKit {
|
|
|
25
30
|
*
|
|
26
31
|
* Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's
|
|
27
32
|
* nothing to challenge against.
|
|
33
|
+
*
|
|
34
|
+
* The new password is validated against the collection's `passwordPolicy` — may throw
|
|
35
|
+
* a {@link PasswordPolicyErrorCode} (400). The same validation applies to
|
|
36
|
+
* {@link completePasswordReset} and {@link changePassword}. Read the policy for a live
|
|
37
|
+
* checklist from `authKit.load(clientId)` → `config.security.passwordPolicy`.
|
|
28
38
|
*/
|
|
29
39
|
function register(clientId: string, data: {
|
|
30
40
|
email: string;
|
package/dist/api/authKit.js
CHANGED
|
@@ -22,6 +22,11 @@ export var authKit;
|
|
|
22
22
|
* `trustDevice: true`), pass it here to skip the challenge entirely as long as it's
|
|
23
23
|
* still valid. If it's revoked/expired, the server silently falls back to requiring a
|
|
24
24
|
* fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
|
|
25
|
+
*
|
|
26
|
+
* Security errors (thrown as `SmartlinksApiError`, see {@link LoginSecurityErrorCode}):
|
|
27
|
+
* - `ACCOUNT_TEMPORARILY_LOCKED` (429) — `err.details.retryAfterSeconds` says how long to wait.
|
|
28
|
+
* - `PASSWORD_EXPIRED` (403) — `err.details.resetToken` is short-lived; route into
|
|
29
|
+
* {@link completePasswordReset} to change the password in place.
|
|
25
30
|
*/
|
|
26
31
|
async function login(clientId, email, password, trustedDeviceToken) {
|
|
27
32
|
const body = { email, password };
|
|
@@ -40,6 +45,11 @@ export var authKit;
|
|
|
40
45
|
*
|
|
41
46
|
* Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's
|
|
42
47
|
* nothing to challenge against.
|
|
48
|
+
*
|
|
49
|
+
* The new password is validated against the collection's `passwordPolicy` — may throw
|
|
50
|
+
* a {@link PasswordPolicyErrorCode} (400). The same validation applies to
|
|
51
|
+
* {@link completePasswordReset} and {@link changePassword}. Read the policy for a live
|
|
52
|
+
* checklist from `authKit.load(clientId)` → `config.security.passwordPolicy`.
|
|
43
53
|
*/
|
|
44
54
|
async function register(clientId, data) {
|
|
45
55
|
return post(`/authkit/${encodeURIComponent(clientId)}/auth/register`, data);
|
package/dist/api/proof.d.ts
CHANGED
|
@@ -12,11 +12,34 @@ export declare namespace proof {
|
|
|
12
12
|
/**
|
|
13
13
|
* Create a proof for a product (admin only).
|
|
14
14
|
* POST /admin/collection/:collectionId/product/:productId/proof
|
|
15
|
+
*
|
|
16
|
+
* Pass the proof's content in a `proof` block, keyed by zone (see {@link ProofWrite}):
|
|
17
|
+
* ```ts
|
|
18
|
+
* proof.create(collectionId, productId, {
|
|
19
|
+
* proof: {
|
|
20
|
+
* values: { colour: 'red' }, // public + owner readable, owner + admin writable
|
|
21
|
+
* data: { serialNo: 1001 }, // public + owner readable, ADMIN-only writable
|
|
22
|
+
* admin: { costPrice: 4.20 }, // admin-only
|
|
23
|
+
* },
|
|
24
|
+
* claimable: true,
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
* Note: a top-level `data`/`admin` on the request body is legacy — top-level
|
|
28
|
+
* `data` gets folded into the values bag, so use `proof.data` for `proof.data`.
|
|
15
29
|
*/
|
|
16
|
-
function create(collectionId: string, productId: string,
|
|
30
|
+
function create(collectionId: string, productId: string, request: ProofCreateRequest): Promise<ProofResponse>;
|
|
17
31
|
/**
|
|
18
32
|
* Update a proof for a product (admin only).
|
|
19
33
|
* PUT /admin/collection/:collectionId/product/:productId/proof/:proofId
|
|
34
|
+
*
|
|
35
|
+
* Pass the fields to change **at the root**, keyed by zone (see {@link ProofWrite}):
|
|
36
|
+
* ```ts
|
|
37
|
+
* proof.update(collectionId, productId, proofId, {
|
|
38
|
+
* data: { serialNo: 1002 }, // → proof.data (admin-only writable)
|
|
39
|
+
* values: { colour: 'blue' }, // → proof.values
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
* Object zones deep-merge, so you can change one field without wiping the rest.
|
|
20
43
|
*/
|
|
21
44
|
function update(collectionId: string, productId: string, proofId: string, values: ProofUpdateRequest): Promise<ProofResponse>;
|
|
22
45
|
/**
|
package/dist/api/proof.js
CHANGED
|
@@ -26,15 +26,38 @@ export var proof;
|
|
|
26
26
|
/**
|
|
27
27
|
* Create a proof for a product (admin only).
|
|
28
28
|
* POST /admin/collection/:collectionId/product/:productId/proof
|
|
29
|
+
*
|
|
30
|
+
* Pass the proof's content in a `proof` block, keyed by zone (see {@link ProofWrite}):
|
|
31
|
+
* ```ts
|
|
32
|
+
* proof.create(collectionId, productId, {
|
|
33
|
+
* proof: {
|
|
34
|
+
* values: { colour: 'red' }, // public + owner readable, owner + admin writable
|
|
35
|
+
* data: { serialNo: 1001 }, // public + owner readable, ADMIN-only writable
|
|
36
|
+
* admin: { costPrice: 4.20 }, // admin-only
|
|
37
|
+
* },
|
|
38
|
+
* claimable: true,
|
|
39
|
+
* })
|
|
40
|
+
* ```
|
|
41
|
+
* Note: a top-level `data`/`admin` on the request body is legacy — top-level
|
|
42
|
+
* `data` gets folded into the values bag, so use `proof.data` for `proof.data`.
|
|
29
43
|
*/
|
|
30
|
-
async function create(collectionId, productId,
|
|
44
|
+
async function create(collectionId, productId, request) {
|
|
31
45
|
const path = `/admin/collection/${encodeURIComponent(collectionId)}/product/${encodeURIComponent(productId)}/proof`;
|
|
32
|
-
return post(path,
|
|
46
|
+
return post(path, request);
|
|
33
47
|
}
|
|
34
48
|
proof.create = create;
|
|
35
49
|
/**
|
|
36
50
|
* Update a proof for a product (admin only).
|
|
37
51
|
* PUT /admin/collection/:collectionId/product/:productId/proof/:proofId
|
|
52
|
+
*
|
|
53
|
+
* Pass the fields to change **at the root**, keyed by zone (see {@link ProofWrite}):
|
|
54
|
+
* ```ts
|
|
55
|
+
* proof.update(collectionId, productId, proofId, {
|
|
56
|
+
* data: { serialNo: 1002 }, // → proof.data (admin-only writable)
|
|
57
|
+
* values: { colour: 'blue' }, // → proof.values
|
|
58
|
+
* })
|
|
59
|
+
* ```
|
|
60
|
+
* Object zones deep-merge, so you can change one field without wiping the rest.
|
|
38
61
|
*/
|
|
39
62
|
async function update(collectionId, productId, proofId, values) {
|
|
40
63
|
const path = `/admin/collection/${encodeURIComponent(collectionId)}/product/${encodeURIComponent(productId)}/proof/${encodeURIComponent(proofId)}`;
|
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.19 | Generated: 2026-08-20T18:06:08.134Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -1993,6 +1993,10 @@ interface CreateThreadInput {
|
|
|
1993
1993
|
data?: Record<string, unknown>
|
|
1994
1994
|
owner?: Record<string, unknown>
|
|
1995
1995
|
admin?: Record<string, unknown> // admin only
|
|
1996
|
+
* Optional atomic first reply. Posting a comment no longer needs a separate
|
|
1997
|
+
* create-thread-then-reply round trip (which could orphan an empty thread on
|
|
1998
|
+
* partial failure). The reply is stored with a generated `id` and timestamp.
|
|
1999
|
+
firstReply?: ReplyInput
|
|
1996
2000
|
}
|
|
1997
2001
|
```
|
|
1998
2002
|
|
|
@@ -3460,6 +3464,55 @@ interface AuthKitConfig {
|
|
|
3460
3464
|
supportEmail?: string
|
|
3461
3465
|
redirectUrl?: string
|
|
3462
3466
|
updatedAt?: string
|
|
3467
|
+
* Per-collection security policy. On the public config endpoint only
|
|
3468
|
+
* `passwordPolicy` + `session` are returned (the client renders password
|
|
3469
|
+
* checklists / idle sign-out from them); `lockout` is admin-only and enforced
|
|
3470
|
+
* server-side. See {@link AuthKitSecurityConfig}.
|
|
3471
|
+
security?: AuthKitSecurityConfig
|
|
3472
|
+
}
|
|
3473
|
+
```
|
|
3474
|
+
|
|
3475
|
+
**AuthKitSecurityConfig** (interface)
|
|
3476
|
+
```typescript
|
|
3477
|
+
interface AuthKitSecurityConfig {
|
|
3478
|
+
passwordPolicy?: AuthKitPasswordPolicy
|
|
3479
|
+
session?: AuthKitSessionPolicy
|
|
3480
|
+
lockout?: AuthKitLockoutPolicy
|
|
3481
|
+
}
|
|
3482
|
+
```
|
|
3483
|
+
|
|
3484
|
+
**AuthKitPasswordPolicy** (interface)
|
|
3485
|
+
```typescript
|
|
3486
|
+
interface AuthKitPasswordPolicy {
|
|
3487
|
+
minLength?: number
|
|
3488
|
+
requireUppercase?: boolean
|
|
3489
|
+
requireLowercase?: boolean
|
|
3490
|
+
requireNumber?: boolean
|
|
3491
|
+
requireSymbol?: boolean
|
|
3492
|
+
blockCommonPasswords?: boolean
|
|
3493
|
+
expiryDays?: number
|
|
3494
|
+
historyCount?: number
|
|
3495
|
+
}
|
|
3496
|
+
```
|
|
3497
|
+
|
|
3498
|
+
**AuthKitSessionPolicy** (interface)
|
|
3499
|
+
```typescript
|
|
3500
|
+
interface AuthKitSessionPolicy {
|
|
3501
|
+
inactivityTimeoutMinutes?: number
|
|
3502
|
+
inactivityWarningSeconds?: number
|
|
3503
|
+
absoluteTimeoutHours?: number
|
|
3504
|
+
rememberMe?: boolean
|
|
3505
|
+
}
|
|
3506
|
+
```
|
|
3507
|
+
|
|
3508
|
+
**AuthKitLockoutPolicy** (interface)
|
|
3509
|
+
```typescript
|
|
3510
|
+
interface AuthKitLockoutPolicy {
|
|
3511
|
+
enabled?: boolean
|
|
3512
|
+
maxFailedAttempts?: number
|
|
3513
|
+
attemptWindowMinutes?: number
|
|
3514
|
+
lockoutMinutes?: number
|
|
3515
|
+
notifyUserOnLockout?: boolean
|
|
3463
3516
|
}
|
|
3464
3517
|
```
|
|
3465
3518
|
|
|
@@ -3471,6 +3524,10 @@ interface AuthKitConfig {
|
|
|
3471
3524
|
|
|
3472
3525
|
**VerifyStatus** = `'pending' | 'verified' | 'failed' | 'expired' | 'unknown'`
|
|
3473
3526
|
|
|
3527
|
+
**PasswordPolicyErrorCode** = ``
|
|
3528
|
+
|
|
3529
|
+
**LoginSecurityErrorCode** = ``
|
|
3530
|
+
|
|
3474
3531
|
### batch
|
|
3475
3532
|
|
|
3476
3533
|
**FirebaseTimestamp** (interface)
|
|
@@ -7275,14 +7332,36 @@ interface Proof {
|
|
|
7275
7332
|
}
|
|
7276
7333
|
```
|
|
7277
7334
|
|
|
7278
|
-
**
|
|
7335
|
+
**ProofWrite** (interface)
|
|
7279
7336
|
```typescript
|
|
7280
|
-
interface
|
|
7281
|
-
|
|
7337
|
+
interface ProofWrite {
|
|
7338
|
+
* Choose the proof's ID (serial, NFC id, etc.). Honoured **on create only** —
|
|
7339
|
+
* the ledger doc becomes `{productId}-{id}`. Omit to auto-generate. Ignored on
|
|
7340
|
+
* update (a proof's ID is immutable).
|
|
7341
|
+
id?: string
|
|
7342
|
+
values?: ProofValues
|
|
7282
7343
|
data?: Record<string, JsonValue>
|
|
7283
7344
|
admin?: Record<string, JsonValue>
|
|
7345
|
+
owner?: Record<string, JsonValue>
|
|
7346
|
+
claimable?: boolean
|
|
7347
|
+
[key: string]: JsonValue | Record<string, JsonValue> | ProofValues | undefined
|
|
7348
|
+
}
|
|
7349
|
+
```
|
|
7350
|
+
|
|
7351
|
+
**ProofCreateRequest** (interface)
|
|
7352
|
+
```typescript
|
|
7353
|
+
interface ProofCreateRequest {
|
|
7354
|
+
* The proof to create, by zone (mirrors the proof document). This is the clear,
|
|
7355
|
+
* recommended shape — `create(collectionId, productId, { proof: {...} })`.
|
|
7356
|
+
proof?: ProofWrite
|
|
7357
|
+
values?: ProofValues
|
|
7284
7358
|
claimable?: boolean
|
|
7285
7359
|
virtual?: boolean
|
|
7360
|
+
core?: ProofWrite
|
|
7361
|
+
* @deprecated On the request body this is folded into the **values bag**
|
|
7362
|
+
* (public + owner-writable) — NOT `proof.data`. Use `proof.data`.
|
|
7363
|
+
data?: Record<string, JsonValue>
|
|
7364
|
+
admin?: Record<string, JsonValue>
|
|
7286
7365
|
}
|
|
7287
7366
|
```
|
|
7288
7367
|
|
|
@@ -7339,7 +7418,7 @@ interface RedeemGrantOptions {
|
|
|
7339
7418
|
|
|
7340
7419
|
**ProofResponse** = `Proof`
|
|
7341
7420
|
|
|
7342
|
-
**ProofUpdateRequest** = `Partial<
|
|
7421
|
+
**ProofUpdateRequest** = `Partial<ProofWrite> & { proof?: ProofWrite }`
|
|
7343
7422
|
|
|
7344
7423
|
**ProofClaimRequest** = `Record<string, any>`
|
|
7345
7424
|
|
|
@@ -8435,6 +8514,13 @@ Soft delete a thread DELETE /threads/:threadId
|
|
|
8435
8514
|
admin: boolean = false) → `Promise<AppThread>`
|
|
8436
8515
|
Add a reply to a thread POST /threads/:threadId/reply Atomically appends to replies array, increments replyCount, updates lastReplyAt
|
|
8437
8516
|
|
|
8517
|
+
**deleteReply**(collectionId: string,
|
|
8518
|
+
appId: string,
|
|
8519
|
+
threadId: string,
|
|
8520
|
+
replyId: string,
|
|
8521
|
+
admin: boolean = false) → `Promise<AppThread>`
|
|
8522
|
+
Delete a single reply from a thread by its reply id (moderation). DELETE /threads/:threadId/reply/:replyId Authorised for the reply's author, the proof owner, or a collection admin.
|
|
8523
|
+
|
|
8438
8524
|
**aggregate**(collectionId: string,
|
|
8439
8525
|
appId: string,
|
|
8440
8526
|
request: AggregateRequest,
|
|
@@ -8737,10 +8823,10 @@ Gets current account information for the logged in user. Returns user, owner, ac
|
|
|
8737
8823
|
### authKit
|
|
8738
8824
|
|
|
8739
8825
|
**login**(clientId: string, email: string, password: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
|
|
8740
|
-
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.
|
|
8826
|
+
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. Security errors (thrown as `SmartlinksApiError`, see {@link LoginSecurityErrorCode}): - `ACCOUNT_TEMPORARILY_LOCKED` (429) — `err.details.retryAfterSeconds` says how long to wait. - `PASSWORD_EXPIRED` (403) — `err.details.resetToken` is short-lived; route into {@link completePasswordReset} to change the password in place.
|
|
8741
8827
|
|
|
8742
8828
|
**register**(clientId: string, data: { email: string; password: string; displayName?: string; accountData?: Record<string, any> }) → `Promise<AuthLoginResponse>`
|
|
8743
|
-
Register a new user (public). Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's nothing to challenge against.
|
|
8829
|
+
Register a new user (public). Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's nothing to challenge against. The new password is validated against the collection's `passwordPolicy` — may throw a {@link PasswordPolicyErrorCode} (400). The same validation applies to {@link completePasswordReset} and {@link changePassword}. Read the policy for a live checklist from `authKit.load(clientId)` → `config.security.passwordPolicy`.
|
|
8744
8830
|
|
|
8745
8831
|
**googleLogin**(clientId: string, idToken: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
|
|
8746
8832
|
Google OAuth login via ID token (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip the challenge on this device, same as {@link login}.
|
|
@@ -9941,14 +10027,14 @@ List all Proofs for a Collection.
|
|
|
9941
10027
|
|
|
9942
10028
|
**create**(collectionId: string,
|
|
9943
10029
|
productId: string,
|
|
9944
|
-
|
|
9945
|
-
Create a proof for a product (admin only). POST /admin/collection/:collectionId/product/:productId/proof
|
|
10030
|
+
request: ProofCreateRequest) → `Promise<ProofResponse>`
|
|
10031
|
+
Create a proof for a product (admin only). POST /admin/collection/:collectionId/product/:productId/proof Pass the proof's content in a `proof` block, keyed by zone (see {@link ProofWrite}): ```ts proof.create(collectionId, productId, { proof: { values: { colour: 'red' }, // public + owner readable, owner + admin writable data: { serialNo: 1001 }, // public + owner readable, ADMIN-only writable admin: { costPrice: 4.20 }, // admin-only }, claimable: true, }) ``` Note: a top-level `data`/`admin` on the request body is legacy — top-level `data` gets folded into the values bag, so use `proof.data` for `proof.data`.
|
|
9946
10032
|
|
|
9947
10033
|
**update**(collectionId: string,
|
|
9948
10034
|
productId: string,
|
|
9949
10035
|
proofId: string,
|
|
9950
10036
|
values: ProofUpdateRequest) → `Promise<ProofResponse>`
|
|
9951
|
-
Update a proof for a product (admin only). PUT /admin/collection/:collectionId/product/:productId/proof/:proofId
|
|
10037
|
+
Update a proof for a product (admin only). PUT /admin/collection/:collectionId/product/:productId/proof/:proofId Pass the fields to change **at the root**, keyed by zone (see {@link ProofWrite}): ```ts proof.update(collectionId, productId, proofId, { data: { serialNo: 1002 }, // → proof.data (admin-only writable) values: { colour: 'blue' }, // → proof.values }) ``` Object zones deep-merge, so you can change one field without wiping the rest.
|
|
9952
10038
|
|
|
9953
10039
|
**claim**(collectionId: string,
|
|
9954
10040
|
productId: string,
|
package/dist/docs/app-objects.md
CHANGED
|
@@ -429,6 +429,71 @@ const productComments = await app.threads.list(collectionId, appId, {
|
|
|
429
429
|
});
|
|
430
430
|
```
|
|
431
431
|
|
|
432
|
+
### Anchoring to app entities and proofs
|
|
433
|
+
|
|
434
|
+
Anchor a thread to your own entity with `parentType` + `parentId`. `parentId` is a
|
|
435
|
+
free-form string (SmartLinks short ids, not just UUIDs), so you can use your app's
|
|
436
|
+
native ids directly — no need to stash them in `body` or a tag.
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
await app.threads.create(collectionId, appId, {
|
|
440
|
+
parentType: 'memory',
|
|
441
|
+
parentId: memoryId, // e.g. "dVdthBQLAjQEnitU7aWy" — text, not a UUID
|
|
442
|
+
proofId, // anchor to a proof (enables grant-tier reads)
|
|
443
|
+
body: { text: 'Lovely photo' },
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
List filters that pair with this (see `ThreadListQueryParams`):
|
|
448
|
+
|
|
449
|
+
| Param | Purpose |
|
|
450
|
+
|-------|---------|
|
|
451
|
+
| `parentType` / `parentId` | one app entity's threads |
|
|
452
|
+
| `parentIds` | **batch** — every thread for many entities in one call (e.g. a feed) |
|
|
453
|
+
| `proofId` / `productId` | threads anchored to a proof / product |
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// One request for the 30 memories currently on screen:
|
|
457
|
+
const feed = await app.threads.list(collectionId, appId, {
|
|
458
|
+
parentType: 'memory',
|
|
459
|
+
parentIds: visibleMemoryIds,
|
|
460
|
+
sort: 'createdAt:desc',
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Atomic first comment (`firstReply`)
|
|
465
|
+
|
|
466
|
+
Posting the first comment no longer needs a create-thread-then-reply two-step (which
|
|
467
|
+
could leave an orphan empty thread if the reply failed). Pass `firstReply` to create
|
|
468
|
+
the thread and its first reply in one atomic call:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
await app.threads.create(collectionId, appId, {
|
|
472
|
+
parentType: 'memory', parentId: memoryId, proofId,
|
|
473
|
+
firstReply: { text: 'Lovely photo', authorName: 'Sam' },
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Deleting a reply (moderation)
|
|
478
|
+
|
|
479
|
+
Replies carry a stable `id`. Remove a single one — authorised for the reply's author,
|
|
480
|
+
the proof owner, or a collection admin:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
await app.threads.deleteReply(collectionId, appId, threadId, replyId);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Grant-tier access (private, shareable comments)
|
|
487
|
+
|
|
488
|
+
A comment thread on a shared album should be `visibility: 'owner'` (private to the
|
|
489
|
+
proof), with access coming from a **share grant** rather than opening the data to the
|
|
490
|
+
world. A holder of an active `read`/`comment` grant on the proof is treated as
|
|
491
|
+
owner-tier **for that proof only**. Enable it with the `publicCreate.threads.grant`
|
|
492
|
+
policy branch and carry the token via `setGrantToken`.
|
|
493
|
+
|
|
494
|
+
See **[Proof Share Grants](proof-share-grants.md)** for the full flow (create/redeem
|
|
495
|
+
grants, guest commenting, revocation, and the app config).
|
|
496
|
+
|
|
432
497
|
---
|
|
433
498
|
|
|
434
499
|
## Records
|
package/dist/docs/assets.md
CHANGED
|
@@ -243,6 +243,87 @@ await Api.asset.bulkDelete({
|
|
|
243
243
|
|
|
244
244
|
---
|
|
245
245
|
|
|
246
|
+
## Resumable uploads (large files, e.g. video)
|
|
247
|
+
|
|
248
|
+
`asset.upload()` is a single request — if the connection drops, the whole file
|
|
249
|
+
restarts. For large files on flaky connections (e.g. phone video), use a
|
|
250
|
+
**resumable** upload: the file is chunked directly to storage and can be paused,
|
|
251
|
+
resumed, and — crucially — **continued after a page reload or app restart**.
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { asset } from '@proveanything/smartlinks'
|
|
255
|
+
|
|
256
|
+
// 1. Open a resumable upload.
|
|
257
|
+
const handle = await asset.createResumableUpload({
|
|
258
|
+
file, // a File (input[type=file] / drag-drop)
|
|
259
|
+
scope: { type: 'proof', collectionId, productId, proofId },
|
|
260
|
+
appId: 'photo-memory',
|
|
261
|
+
// token: uploadToken, // for public/token uploads (see below)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// 2. Persist handle.id so the upload survives a reload.
|
|
265
|
+
localStorage.setItem('pendingUpload', handle.id)
|
|
266
|
+
|
|
267
|
+
// 3. Upload. Resumes automatically from the offset storage already holds.
|
|
268
|
+
const uploaded = await handle.start({
|
|
269
|
+
onProgress: (pct) => setProgress(pct), // 0–100
|
|
270
|
+
signal: abortController.signal, // optional: cancel a stalled upload
|
|
271
|
+
})
|
|
272
|
+
localStorage.removeItem('pendingUpload')
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Pause / resume, and resume after a reload
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
handle.pause() // stop after the current chunk
|
|
279
|
+
await handle.resume({ onProgress }) // continue
|
|
280
|
+
|
|
281
|
+
// After a reload / app kill — rehydrate from the persisted id and the same file:
|
|
282
|
+
const saved = localStorage.getItem('pendingUpload')
|
|
283
|
+
if (saved) {
|
|
284
|
+
const handle = await asset.resumeUpload(saved, file)
|
|
285
|
+
await handle.start({ onProgress }) // continues, does not restart
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### API
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
namespace asset {
|
|
293
|
+
createResumableUpload(options: CreateResumableUploadOptions): Promise<ResumableUploadHandle>
|
|
294
|
+
resumeUpload(handleId: string, file: File): Promise<ResumableUploadHandle>
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
interface CreateResumableUploadOptions {
|
|
298
|
+
file: File
|
|
299
|
+
scope: { type: 'collection'; collectionId: string }
|
|
300
|
+
| { type: 'product'; collectionId: string; productId: string }
|
|
301
|
+
| { type: 'proof'; collectionId: string; productId: string; proofId: string }
|
|
302
|
+
name?: string
|
|
303
|
+
metadata?: Record<string, any>
|
|
304
|
+
appId?: string
|
|
305
|
+
admin?: boolean // admin route (default is the public route)
|
|
306
|
+
token?: string // upload token for public/unauthenticated uploads
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
interface ResumableUploadHandle {
|
|
310
|
+
readonly id: string // durable, persistable — pass to resumeUpload() after a reload
|
|
311
|
+
readonly size: number // total bytes
|
|
312
|
+
start(opts?: { onProgress?: (pct: number) => void; signal?: AbortSignal }): Promise<Asset>
|
|
313
|
+
pause(): void
|
|
314
|
+
resume(opts?: { onProgress?: (pct: number) => void; signal?: AbortSignal }): Promise<Asset>
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Notes**
|
|
319
|
+
|
|
320
|
+
- `handle.id` is an opaque string that carries everything needed to resume — persist it as-is.
|
|
321
|
+
- On completion, `start()`/`resume()` resolves to the finalized `Asset` record.
|
|
322
|
+
- `pause()` causes the in-flight `start()`/`resume()` promise to reject with an `UploadPausedError`; call `resume()` to continue.
|
|
323
|
+
- `asset.upload()` also now accepts an `AbortSignal` (`upload({ ..., signal })`) for cancelling a stalled single-shot upload.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
246
327
|
## Public (token-based) uploads
|
|
247
328
|
|
|
248
329
|
For anonymous or contact-initiated uploads from the portal — no admin auth required.
|
package/dist/docs/auth-kit.md
CHANGED
|
@@ -502,6 +502,79 @@ await authKit.completePasswordReset(clientId, tokenFromUrl, 'newSecurePassword')
|
|
|
502
502
|
|
|
503
503
|
---
|
|
504
504
|
|
|
505
|
+
## Account security policy
|
|
506
|
+
|
|
507
|
+
Each collection can configure account-security rules (via the admin console). **The API
|
|
508
|
+
enforces all of it**; your login UI reads the policy for UX only (a live password checklist,
|
|
509
|
+
idle sign-out). Read it from the config:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
const config = await authKit.load(clientId);
|
|
513
|
+
const policy = config.security?.passwordPolicy; // min length, char classes, block-common
|
|
514
|
+
const session = config.security?.session; // inactivity + absolute timeouts, rememberMe
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
`lockout` values are admin-only and never returned here.
|
|
518
|
+
|
|
519
|
+
### Password policy
|
|
520
|
+
|
|
521
|
+
`register`, `completePasswordReset`, and `changePassword` validate the new password
|
|
522
|
+
server-side and throw a `SmartlinksApiError` with a {@link PasswordPolicyErrorCode}:
|
|
523
|
+
|
|
524
|
+
| `errorCode` (400) | Meaning |
|
|
525
|
+
|---|---|
|
|
526
|
+
| `PASSWORD_TOO_SHORT` | below `minLength` |
|
|
527
|
+
| `PASSWORD_REQUIREMENTS_NOT_MET` | missing a required character class |
|
|
528
|
+
| `PASSWORD_TOO_COMMON` | on the common/breached list |
|
|
529
|
+
| `PASSWORD_RECENTLY_USED` | matched one of the last `historyCount` passwords |
|
|
530
|
+
|
|
531
|
+
Render a live checklist from `policy` so users see the rules before submitting.
|
|
532
|
+
|
|
533
|
+
### Lockout
|
|
534
|
+
|
|
535
|
+
After too many failed logins the account is temporarily locked. `login` throws:
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
try {
|
|
539
|
+
await authKit.login(clientId, email, password);
|
|
540
|
+
} catch (err) {
|
|
541
|
+
if (err.errorCode === 'ACCOUNT_TEMPORARILY_LOCKED') {
|
|
542
|
+
const mins = Math.ceil(err.details.retryAfterSeconds / 60);
|
|
543
|
+
show(`Too many attempts. Try again in ${mins} minute(s).`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Failed MFA challenges count toward the same lock. Locking responds identically for unknown
|
|
549
|
+
accounts (no enumeration).
|
|
550
|
+
|
|
551
|
+
### Password expiry
|
|
552
|
+
|
|
553
|
+
If a password is older than `passwordPolicy.expiryDays`, a valid login is refused with
|
|
554
|
+
**403 `PASSWORD_EXPIRED`** carrying a short-lived `resetToken` — send the user straight into
|
|
555
|
+
the reset form to change it in place:
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
catch (err) {
|
|
559
|
+
if (err.errorCode === 'PASSWORD_EXPIRED') {
|
|
560
|
+
await authKit.completePasswordReset(clientId, err.details.resetToken, newPassword);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Session lifetime
|
|
566
|
+
|
|
567
|
+
- **Absolute timeout** (`session.absoluteTimeoutHours`) is enforced server-side on the native
|
|
568
|
+
refresh path: once the session is too old, `refreshToken` throws **401 `SESSION_EXPIRED`**
|
|
569
|
+
(see {@link RefreshErrorCode}) — clear storage and route to login. Web sessions use the
|
|
570
|
+
stateless bearer token and rely on inactivity sign-out below.
|
|
571
|
+
- **Inactivity timeout** (`session.inactivityTimeoutMinutes` / `inactivityWarningSeconds`) is
|
|
572
|
+
**client-enforced** — sign the user out after idle, warning first. Sync across tabs.
|
|
573
|
+
- **`session.rememberMe: false`** → don't persist tokens to durable storage; treat the session
|
|
574
|
+
as browser-scoped.
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
505
578
|
## Relationship to other parts of the SDK
|
|
506
579
|
|
|
507
580
|
| Concern | Where it lives |
|