@proveanything/smartlinks 1.3.46 → 1.4.1

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.
@@ -92,6 +92,12 @@ export declare namespace auth {
92
92
  /**
93
93
  * Gets current account information for the logged in user.
94
94
  * Returns user, owner, account, and location objects.
95
+ *
96
+ * Short-circuits immediately (no network request) when the SDK has no
97
+ * bearer token or API key set — the server would return 401 anyway.
98
+ * Throws a `SmartlinksApiError` with `statusCode 401` and
99
+ * `details.local = true` so callers can distinguish "never authenticated"
100
+ * from an actual server-side token rejection.
95
101
  */
96
102
  function getAccount(): Promise<AccountInfoResponse>;
97
103
  }
package/dist/api/auth.js CHANGED
@@ -1,4 +1,5 @@
1
- import { post, request, setBearerToken, getApiHeaders } from "../http";
1
+ import { post, request, setBearerToken, getApiHeaders, hasAuthCredentials } from "../http";
2
+ import { SmartlinksApiError } from "../types/error";
2
3
  /*
3
4
  user: Record<string, any>
4
5
  owner: Record<string, any>
@@ -84,8 +85,17 @@ export var auth;
84
85
  /**
85
86
  * Gets current account information for the logged in user.
86
87
  * Returns user, owner, account, and location objects.
88
+ *
89
+ * Short-circuits immediately (no network request) when the SDK has no
90
+ * bearer token or API key set — the server would return 401 anyway.
91
+ * Throws a `SmartlinksApiError` with `statusCode 401` and
92
+ * `details.local = true` so callers can distinguish "never authenticated"
93
+ * from an actual server-side token rejection.
87
94
  */
88
95
  async function getAccount() {
96
+ if (!hasAuthCredentials()) {
97
+ throw new SmartlinksApiError('Not authenticated: no bearer token or API key is set.', 401, { code: 401, errorCode: 'NOT_AUTHENTICATED', message: 'Not authenticated: no bearer token or API key is set.', details: { local: true } });
98
+ }
89
99
  return request("/public/auth/account");
90
100
  }
91
101
  auth.getAccount = getAccount;
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.3.46 | Generated: 2026-02-19T21:09:09.402Z
3
+ Version: 1.4.1 | Generated: 2026-02-20T19:32:34.980Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -18,6 +18,7 @@ For detailed guides on specific features:
18
18
  - **[Theme Defaults](theme-defaults.md)** - Default theme values and presets
19
19
  - **[Proof Claiming Methods](proof-claiming-methods.md)** - All methods for claiming/registering product ownership (NFC tags, serial numbers, auto-generated claims)
20
20
  - **[App Data Storage](app-data-storage.md)** - User-specific and collection-scoped app data storage
21
+ - **[AI Guide Template](ai-guide-template.md)** - A sample for an app on how to build an AI setup guide
21
22
 
22
23
  ## API Namespaces
23
24
 
@@ -108,13 +109,29 @@ Get the currently configured API base URL. Returns null if initializeApi() has n
108
109
  **isInitialized**() → `boolean`
109
110
  Returns true if initializeApi() has been called at least once. Useful for guards in widgets or shared modules that want to skip initialization when another module has already done it. ```ts if (!isInitialized()) { initializeApi({ baseURL: 'https://smartlinks.app/api/v1' }) } ```
110
111
 
112
+ **hasAuthCredentials**() → `boolean`
113
+ Returns true if the SDK currently has any auth credential set (bearer token or API key). Use this as a cheap pre-flight check before calling endpoints that require authentication, to avoid issuing a network request that you already know will return a 401. ```ts if (hasAuthCredentials()) { const account = await auth.getAccount() } ```
114
+
115
+ **configureSdkCache**(options: {
116
+ enabled?: boolean
117
+ ttlMs?: number
118
+ maxEntries?: number
119
+ persistence?: 'none' | 'indexeddb'
120
+ persistenceTtlMs?: number
121
+ serveStaleOnOffline?: boolean
122
+ }) → `void`
123
+ Configure the SDK's built-in in-memory GET cache. The cache is transparent — it sits inside the HTTP layer and requires no changes to your existing API calls. All GET requests benefit automatically. Per-resource rules (collections/products → 1 h, proofs → 30 s, etc.) override this value. in-memory only (`'none'`, default). Ignored in Node.js. fallback, from the original fetch time (default: 7 days). `SmartlinksOfflineError` with stale data instead of propagating the network error. ```ts // Enable IndexedDB persistence for offline support configureSdkCache({ persistence: 'indexeddb' }) // Disable cache entirely in test environments configureSdkCache({ enabled: false }) ```
124
+
125
+ **invalidateCache**(urlPattern?: string) → `void`
126
+ Manually invalidate entries in the SDK's GET cache. *contains* this string is removed. Omit (or pass `undefined`) to wipe the entire cache. ```ts invalidateCache() // clear everything invalidateCache('/collection/abc123') // one specific collection invalidateCache('/product/') // all product responses ```
127
+
111
128
  **proxyUploadFormData**(path: string,
112
129
  formData: FormData,
113
130
  onProgress?: (percent: number) → `void`
114
131
  Upload a FormData payload via proxy with progress events using chunked postMessage. Parent is expected to implement the counterpart protocol.
115
132
 
116
133
  **request**(path: string) → `Promise<T>`
117
- Internal helper that performs a GET request to \`\${baseURL}\${path}\`, injecting headers for apiKey or bearerToken if present. Returns the parsed JSON as T, or throws an Error.
134
+ Internal helper that performs a GET request to `${baseURL}${path}`, injecting headers for apiKey or bearerToken if present. Cache pipeline (when caching is not skipped): L1 hit → return from memory (no I/O) L2 hit → return from IndexedDB, promote to L1 (no network) Miss → fetch from network, store in L1 + L2 Offline → serve stale L2 entry via SmartlinksOfflineError (if persistence enabled) Concurrent identical GETs share one in-flight promise (deduplication). Node-safe: IndexedDB calls are no-ops when IDB is unavailable.
118
135
 
119
136
  **post**(path: string,
120
137
  body: any,
@@ -4128,7 +4145,7 @@ Tries to register a new user account. Can return a bearer token, or a Firebase t
4128
4145
  Admin: Get a user bearer token (impersonation/automation). POST /admin/auth/userToken All fields are optional; at least one identifier should be provided.
4129
4146
 
4130
4147
  **getAccount**() → `Promise<AccountInfoResponse>`
4131
- Gets current account information for the logged in user. Returns user, owner, account, and location objects.
4148
+ Gets current account information for the logged in user. Returns user, owner, account, and location objects. Short-circuits immediately (no network request) when the SDK has no bearer token or API key set — the server would return 401 anyway. Throws a `SmartlinksApiError` with `statusCode 401` and `details.local = true` so callers can distinguish "never authenticated" from an actual server-side token rejection.
4132
4149
 
4133
4150
  ### authKit
4134
4151
 
@@ -0,0 +1,241 @@
1
+ # AI Guide: Example App
2
+
3
+ <!-- CUSTOMIZE: Replace "Example App" and all content below with your app's specifics. -->
4
+ <!-- This file is consumed by AI orchestrators alongside public/app.manifest.json. -->
5
+ <!-- The manifest provides structured data; this file provides prose context and nuance. -->
6
+
7
+ ---
8
+
9
+ ## Purpose & Identity
10
+
11
+ <!-- CUSTOMIZE: Describe what your app does in 2-3 sentences. -->
12
+
13
+ The **Example App** is a template SmartLinks microapp that demonstrates all platform patterns including setup wizards, data imports, widget embedding, and analytics. It serves as a reference implementation for app authors building on the SmartLinks platform.
14
+
15
+ ---
16
+
17
+ ## Setup Wizard Instructions
18
+
19
+ An AI orchestrator should follow these steps to configure the Example App for a collection. The structured questions and schema are in `app.manifest.json` under `setup`.
20
+
21
+ ### Prerequisites
22
+
23
+ - The user must have **admin access** to the target collection.
24
+ - The `collectionId` and `appId` must be known (typically from URL context).
25
+
26
+ ### Questions to Ask
27
+
28
+ Walk the user through each question defined in `setup.questions[]` of the manifest:
29
+
30
+ 1. **App Title** (`appTitle`): Ask what the app should be called. Offer to auto-generate a suggestion using `SL.ai.chat.completions` if the user wants help (see `contentHints` in the manifest).
31
+ 2. **Enable Notifications** (`enableNotifications`): Ask whether the app should send notifications to users.
32
+
33
+ <!-- CUSTOMIZE: Add app-specific guidance for each question. For example: -->
34
+ <!-- "If the user is setting up a competition, suggest a title like 'Win a [Product Name]!'" -->
35
+ <!-- "For plant passports, notifications are typically disabled." -->
36
+
37
+ ### How to Save the Config
38
+
39
+ After collecting answers, validate against `setup.configSchema` and save:
40
+
41
+ ```typescript
42
+ await SL.appConfiguration.setConfig({
43
+ collectionId,
44
+ appId,
45
+ config: {
46
+ appTitle: "User's chosen title",
47
+ enableNotifications: false
48
+ },
49
+ admin: true // REQUIRED for admin operations
50
+ });
51
+ ```
52
+
53
+ ### Post-Setup Verification
54
+
55
+ After saving, confirm by reading the config back:
56
+
57
+ ```typescript
58
+ const saved = await SL.appConfiguration.getConfig({
59
+ collectionId,
60
+ appId,
61
+ admin: true
62
+ });
63
+ // Verify saved.appTitle and saved.enableNotifications match expectations
64
+ ```
65
+
66
+ Tell the user: "Your app is configured! You can adjust settings anytime by asking me to update the configuration."
67
+
68
+ ---
69
+
70
+ ## Import Instructions
71
+
72
+ The app supports bulk import of product-level configuration. The field definitions are in `app.manifest.json` under `import`.
73
+
74
+ ### CSV Template
75
+
76
+ Generate or share this template with the user:
77
+
78
+ ```csv
79
+ productId,appTitle,enableNotifications
80
+ prod_001,My First Product,true
81
+ prod_002,My Second Product,false
82
+ ```
83
+
84
+ <!-- CUSTOMIZE: Add real-world examples relevant to your app. -->
85
+
86
+ ### Field Validation Rules
87
+
88
+ | Field | Type | Required | Validation |
89
+ | --------------------- | ------- | -------- | ------------------------------------- |
90
+ | `productId` | string | ✅ | Must be a valid SmartLinks product ID |
91
+ | `appTitle` | string | ✅ | Non-empty string |
92
+ | `enableNotifications` | boolean | ❌ | Defaults to `false` |
93
+
94
+ <!-- CUSTOMIZE: Add app-specific validation rules. For example: -->
95
+ <!-- "For plant passports: botanicalName must be in Latin binomial format." -->
96
+
97
+ ### API Call Sequence
98
+
99
+ For each row in the CSV:
100
+
101
+ ```typescript
102
+ await SL.appConfiguration.setConfig({
103
+ collectionId,
104
+ productId: row.productId, // From CSV
105
+ appId,
106
+ config: {
107
+ appTitle: row.appTitle,
108
+ enableNotifications: row.enableNotifications ?? false
109
+ },
110
+ admin: true
111
+ });
112
+ ```
113
+
114
+ ### Error Handling
115
+
116
+ - If a `productId` is invalid, log the error and continue with the next row.
117
+ - After processing all rows, report a summary: `"Imported X of Y products successfully. Z failed."`.
118
+ - For failed rows, list the product ID and error message so the user can fix the data.
119
+
120
+ ### Cross-App Import
121
+
122
+ When importing data for multiple apps simultaneously:
123
+
124
+ 1. Fetch `app.manifest.json` from each app.
125
+ 2. Merge `import.fields` arrays (prefix field names with app name if there are conflicts).
126
+ 3. Generate a combined CSV template.
127
+ 4. For each row, split into separate payloads per app and call each app's `saveWith.method`.
128
+
129
+ ---
130
+
131
+ ## Widget Embedding Guide
132
+
133
+ ### Available Widgets
134
+
135
+ | Widget | Description | Sizes |
136
+ | --------------- | ------------------------------------------ | ------------------------ |
137
+ | `ExampleWidget` | Demo widget showing SmartLinks integration | compact, standard, large |
138
+
139
+ <!-- CUSTOMIZE: List all widgets your app exports. -->
140
+
141
+ ### Props Reference
142
+
143
+ All widgets receive `SmartLinksWidgetProps`:
144
+
145
+ | Prop | Type | Required | Description |
146
+ | ----------------- | -------------- | -------- | --------------------------------------- |
147
+ | `collectionId` | string | ✅ | Collection context |
148
+ | `appId` | string | ✅ | App identifier |
149
+ | `SL` | SmartLinks SDK | ✅ | Pre-initialized SDK instance |
150
+ | `productId` | string | ❌ | Product context |
151
+ | `proofId` | string | ❌ | Proof context |
152
+ | `user` | object | ❌ | Current user info |
153
+ | `onNavigate` | function | ❌ | Navigation callback |
154
+ | `publicPortalUrl` | string | ❌ | URL to full app for deep linking |
155
+ | `size` | string | ❌ | `"compact"`, `"standard"`, or `"large"` |
156
+ | `lang` | string | ❌ | Language code (e.g., `"en"`) |
157
+ | `translations` | object | ❌ | Translation overrides |
158
+
159
+ ### Example Code
160
+
161
+ ```tsx
162
+ import { ExampleWidget } from '@my-app/widgets';
163
+
164
+ <ExampleWidget
165
+ collectionId="col_123"
166
+ appId="example-app"
167
+ SL={SL}
168
+ size="standard"
169
+ onNavigate={(path) => router.push(path)}
170
+ />
171
+ ```
172
+
173
+ <!-- CUSTOMIZE: Show real widget usage with app-specific props. -->
174
+
175
+ ---
176
+
177
+ ## Tunable Settings
178
+
179
+ These settings can be adjusted after initial setup without reconfiguring everything. The AI can modify them in response to user requests like "turn off notifications" or optimization suggestions.
180
+
181
+ | Setting | Type | Description |
182
+ | --------------------- | ------- | ------------------------------ |
183
+ | `enableNotifications` | boolean | Toggle notifications on or off |
184
+
185
+ <!-- CUSTOMIZE: List all tunable fields with guidance on when to change them. -->
186
+
187
+ To update a tunable setting:
188
+
189
+ ```typescript
190
+ // 1. Read current config
191
+ const current = await SL.appConfiguration.getConfig({ collectionId, appId, admin: true });
192
+
193
+ // 2. Merge the change
194
+ await SL.appConfiguration.setConfig({
195
+ collectionId,
196
+ appId,
197
+ config: { ...current, enableNotifications: true },
198
+ admin: true
199
+ });
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Metrics & Analytics
205
+
206
+ ### Tracked Interactions
207
+
208
+ | Interaction ID | Description |
209
+ | -------------- | --------------------------------------------- |
210
+ | `page-view` | Tracks each time a user views the public page |
211
+
212
+ <!-- CUSTOMIZE: List all interaction IDs your app tracks. -->
213
+
214
+ ### KPIs
215
+
216
+ | KPI | How to Compute |
217
+ | ----------- | -------------------------------------------------------------------------------------- |
218
+ | Total Views | `SL.interactions.countsByOutcome(collectionId, { appId, interactionId: 'page-view' })` |
219
+
220
+ <!-- CUSTOMIZE: Add app-specific KPIs and interpretation guidance. -->
221
+
222
+ ---
223
+
224
+ ## Troubleshooting
225
+
226
+ ### Common Issues
227
+
228
+ | Issue | Cause | Fix |
229
+ | --------------------- | -------------------------- | ----------------------------------------------------- |
230
+ | Config save fails | Missing `admin: true` flag | Always include `admin: true` for admin operations |
231
+ | Widget doesn't render | Missing required props | Ensure `collectionId`, `appId`, and `SL` are provided |
232
+ | Import skips rows | Invalid `productId` | Verify product IDs exist in the collection |
233
+ | Theme not applied | Missing `?theme=` param | Check URL parameters or postMessage setup |
234
+
235
+ <!-- CUSTOMIZE: Add app-specific troubleshooting entries. -->
236
+
237
+ ### Getting Help
238
+
239
+ - **SDK Docs**: `node_modules/@proveanything/smartlinks/docs/`
240
+ - **App Manifest**: `public/app.manifest.json`
241
+ - **Platform Guide**: `src/docs/smartlinks/about.md`
package/dist/http.d.ts CHANGED
@@ -50,15 +50,88 @@ export declare function getBaseURL(): string | null;
50
50
  * ```
51
51
  */
52
52
  export declare function isInitialized(): boolean;
53
+ /**
54
+ * Returns true if the SDK currently has any auth credential set (bearer token
55
+ * or API key). Use this as a cheap pre-flight check before calling endpoints
56
+ * that require authentication, to avoid issuing a network request that you
57
+ * already know will return a 401.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * if (hasAuthCredentials()) {
62
+ * const account = await auth.getAccount()
63
+ * }
64
+ * ```
65
+ */
66
+ export declare function hasAuthCredentials(): boolean;
67
+ /**
68
+ * Configure the SDK's built-in in-memory GET cache.
69
+ *
70
+ * The cache is transparent — it sits inside the HTTP layer and requires no
71
+ * changes to your existing API calls. All GET requests benefit automatically.
72
+ *
73
+ * @param options.enabled - Turn caching on/off entirely (default: `true`)
74
+ * @param options.ttlMs - Default time-to-live in milliseconds (default: `60_000`).
75
+ * Per-resource rules (collections/products → 1 h,
76
+ * proofs → 30 s, etc.) override this value.
77
+ * @param options.maxEntries - L1 LRU eviction threshold (default: `200`)
78
+ * @param options.persistence - Enable IndexedDB L2 cache (`'indexeddb'`) or keep
79
+ * in-memory only (`'none'`, default). Ignored in Node.js.
80
+ * @param options.persistenceTtlMs - How long L2 entries are eligible as an offline stale
81
+ * fallback, from the original fetch time (default: 7 days).
82
+ * @param options.serveStaleOnOffline - When `true` (default) and persistence is on, throw
83
+ * `SmartlinksOfflineError` with stale data instead of
84
+ * propagating the network error.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * // Enable IndexedDB persistence for offline support
89
+ * configureSdkCache({ persistence: 'indexeddb' })
90
+ *
91
+ * // Disable cache entirely in test environments
92
+ * configureSdkCache({ enabled: false })
93
+ * ```
94
+ */
95
+ export declare function configureSdkCache(options: {
96
+ enabled?: boolean;
97
+ ttlMs?: number;
98
+ maxEntries?: number;
99
+ persistence?: 'none' | 'indexeddb';
100
+ persistenceTtlMs?: number;
101
+ serveStaleOnOffline?: boolean;
102
+ }): void;
103
+ /**
104
+ * Manually invalidate entries in the SDK's GET cache.
105
+ *
106
+ * @param urlPattern - Optional substring match. Every cache entry whose key
107
+ * *contains* this string is removed. Omit (or pass `undefined`) to wipe the
108
+ * entire cache.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * invalidateCache() // clear everything
113
+ * invalidateCache('/collection/abc123') // one specific collection
114
+ * invalidateCache('/product/') // all product responses
115
+ * ```
116
+ */
117
+ export declare function invalidateCache(urlPattern?: string): void;
53
118
  /**
54
119
  * Upload a FormData payload via proxy with progress events using chunked postMessage.
55
120
  * Parent is expected to implement the counterpart protocol.
56
121
  */
57
122
  export declare function proxyUploadFormData<T>(path: string, formData: FormData, onProgress?: (percent: number) => void): Promise<T>;
58
123
  /**
59
- * Internal helper that performs a GET request to \`\${baseURL}\${path}\`,
124
+ * Internal helper that performs a GET request to `${baseURL}${path}`,
60
125
  * injecting headers for apiKey or bearerToken if present.
61
- * Returns the parsed JSON as T, or throws an Error.
126
+ *
127
+ * Cache pipeline (when caching is not skipped):
128
+ * L1 hit → return from memory (no I/O)
129
+ * L2 hit → return from IndexedDB, promote to L1 (no network)
130
+ * Miss → fetch from network, store in L1 + L2
131
+ * Offline → serve stale L2 entry via SmartlinksOfflineError (if persistence enabled)
132
+ *
133
+ * Concurrent identical GETs share one in-flight promise (deduplication).
134
+ * Node-safe: IndexedDB calls are no-ops when IDB is unavailable.
62
135
  */
63
136
  export declare function request<T>(path: string): Promise<T>;
64
137
  /**