@proveanything/smartlinks 1.6.3 → 1.6.5

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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.6.3 | Generated: 2026-02-26T11:49:31.908Z
3
+ Version: 1.6.5 | Generated: 2026-03-03T14:01:39.215Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -21,6 +21,7 @@ For detailed guides on specific features:
21
21
  - **[App Data Storage](app-data-storage.md)** - User-specific and collection-scoped app data storage
22
22
  - **[App Objects: Cases, Threads & Records](app-objects.md)** - Generic app-scoped building blocks for support cases, discussions, bookings, registrations, and more
23
23
  - **[Communications](comms.md)** - Transactional sends, multi-channel broadcasts, consent management, push registration, and analytics
24
+ - **[Deep Link Discovery](deep-link-discovery.md)** - Registering and discovering navigable app states for portal menus and AI orchestration
24
25
  - **[AI Guide Template](ai-guide-template.md)** - A sample for an app on how to build an AI setup guide
25
26
 
26
27
  ## API Namespaces
@@ -35,7 +36,7 @@ The Smartlinks SDK is organized into the following namespaces:
35
36
  - **batch** - Group products into batches; manage serial number ranges and lookups.
36
37
  - **crate** - Organize products in containers/crates for logistics and grouping.
37
38
  - **form** - Build and manage dynamic forms used by apps and workflows.
38
- - **appConfiguration** - Read/write app configuration and scoped data (collection/product/proof).
39
+ - **appConfiguration** - Read/write app configuration and scoped data (collection/product/proof); hosts the deep-link registry. → [Guide](deep-link-discovery.md)
39
40
 
40
41
  — Identity & Access —
41
42
  - **auth** - Admin authentication and account ops: login/logout, tokens, account info.
@@ -4987,7 +4988,7 @@ Tries to register a new user account. Can return a bearer token, or a Firebase t
4987
4988
  Admin: Get a user bearer token (impersonation/automation). POST /admin/auth/userToken All fields are optional; at least one identifier should be provided.
4988
4989
 
4989
4990
  **getAccount**() → `Promise<AccountInfoResponse>`
4990
- 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.
4991
+ 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. This short-circuit is skipped when proxy mode is enabled, because in that case credentials are held by the parent frame and the local SDK may have no token set yet — the request must be forwarded to the parent to determine whether the user is authenticated.
4991
4992
 
4992
4993
  ### authKit
4993
4994
 
@@ -0,0 +1,516 @@
1
+ # Deep Link Discovery
2
+
3
+ Complete guide to registering and discovering navigable states in SmartLinks apps, enabling portal menus, AI orchestrators, and other apps to deep-link directly into specific views.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Overview](#overview)
10
+ - [Quick Start](#quick-start)
11
+ - [The `linkable` Registry](#the-linkable-registry)
12
+ - [Schema: DeepLinkEntry](#schema-deeplinkentry)
13
+ - [Entry Types](#entry-types)
14
+ - [Data-Driven (Dynamic)](#1-data-driven-entries-dynamic)
15
+ - [Route-Based (Static)](#2-route-based-entries-static)
16
+ - [Mixed](#3-mixed)
17
+ - [URL Resolution](#url-resolution)
18
+ - [Syncing the Registry](#syncing-the-registry)
19
+ - [Consumer Patterns](#consumer-patterns)
20
+ - [Portal Navigation Menu](#portal-navigation-menu)
21
+ - [AI Orchestration](#ai-orchestration)
22
+ - [Cross-App Navigation](#cross-app-navigation)
23
+ - [Manifest Declaration](#manifest-declaration)
24
+ - [TypeScript Types](#typescript-types)
25
+ - [Rules & Best Practices](#rules--best-practices)
26
+
27
+ ---
28
+
29
+ ## Overview
30
+
31
+ SmartLinks apps can expose **deep-linkable states** — specific views, pages, or configurations that other apps, the portal, or AI orchestrators can navigate to directly without loading the app first.
32
+
33
+ This is done by publishing a `linkable` array in the app's `appConfig`. Any consumer (portal shell, AI agent, another app) can read this array to discover what navigable states the app currently offers.
34
+
35
+ ```text
36
+ ┌─────────────────────────────────────────────────────────────────────┐
37
+ │ Your SmartLinks App │
38
+ │ │
39
+ │ ┌───────────────────────┐ writes ┌────────────────────────┐│
40
+ │ │ Admin / Data Events │ ─────────────► │ appConfig.linkable ││
41
+ │ └───────────────────────┘ └────────────────────────┘│
42
+ └─────────────────────────────────────────────────────────────────────┘
43
+
44
+ reads linkable[]
45
+
46
+ ┌───────────────────────────────────┼───────────────────────┐
47
+ ▼ ▼ ▼
48
+ ┌─────────────────────┐ ┌────────────────────┐ ┌─────────────────────┐
49
+ │ Portal Nav Menu │ │ AI Orchestrator │ │ Another App │
50
+ │ (sidebar / drawer) │ │ ("Go to About Us")│ │ (cross-app nav) │
51
+ └─────────────────────┘ └────────────────────┘ └─────────────────────┘
52
+ ```
53
+
54
+ ### Key Benefits
55
+
56
+ - ✅ **Discoverable** — consumers don't need to know the app's internal routing
57
+ - ✅ **Decoupled** — the app owns the registry; consumers just read it
58
+ - ✅ **Dynamic** — the registry updates automatically when content changes
59
+ - ✅ **AI-friendly** — machine-readable titles make states naturally addressable by LLMs
60
+ - ✅ **No extra endpoints** — built on top of the existing `appConfiguration` API
61
+
62
+ ---
63
+
64
+ ## Quick Start
65
+
66
+ **App side — publish linkable states:**
67
+
68
+ ```typescript
69
+ import { appConfiguration } from '@proveanything/smartlinks';
70
+
71
+ await appConfiguration.setConfig({
72
+ collectionId,
73
+ appId,
74
+ config: {
75
+ // ... other app config ...
76
+ linkable: [
77
+ { title: 'Gallery', path: '/gallery' },
78
+ { title: 'About Us', params: { pageId: 'about-us' } },
79
+ { title: 'FAQ', params: { pageId: 'faq' } },
80
+ ]
81
+ },
82
+ admin: true
83
+ });
84
+ ```
85
+
86
+ **Consumer side — read linkable states:**
87
+
88
+ ```typescript
89
+ import { appConfiguration } from '@proveanything/smartlinks';
90
+
91
+ const config = await appConfiguration.getConfig({ collectionId, appId });
92
+ const linkable = config?.linkable ?? [];
93
+
94
+ // linkable = [
95
+ // { title: 'Gallery', path: '/gallery' },
96
+ // { title: 'About Us', params: { pageId: 'about-us' } },
97
+ // { title: 'FAQ', params: { pageId: 'faq' } },
98
+ // ]
99
+ ```
100
+
101
+ ---
102
+
103
+ ## The `linkable` Registry
104
+
105
+ Each app **MAY** store a `linkable` array at the top level of its `appConfig`. This array describes all the deep-linkable states the app currently exposes.
106
+
107
+ ### Storage Location
108
+
109
+ The `linkable` key is **reserved** at the top level of `appConfig` for this purpose. Do not use it for anything else.
110
+
111
+ ```typescript
112
+ // Writing
113
+ await appConfiguration.setConfig({
114
+ collectionId,
115
+ appId,
116
+ config: {
117
+ // ... other app config ...
118
+ linkable: [ /* DeepLinkEntry[] */ ]
119
+ },
120
+ admin: true
121
+ });
122
+
123
+ // Reading
124
+ const config = await appConfiguration.getConfig({ collectionId, appId });
125
+ const linkable: DeepLinkEntry[] = config?.linkable ?? [];
126
+ ```
127
+
128
+ ### Admin vs Public Access
129
+
130
+ - **Writing** always requires `admin: true` — only the app itself (running with admin credentials) should update its own registry.
131
+ - **Reading** can be done publicly — portals and consumers typically read the registry without admin credentials.
132
+
133
+ ---
134
+
135
+ ## Schema: DeepLinkEntry
136
+
137
+ Each entry in the `linkable` array describes one navigable state:
138
+
139
+ ```typescript
140
+ interface DeepLinkEntry {
141
+ /** Human-readable label for this state (required) */
142
+ title: string;
143
+
144
+ /**
145
+ * Hash-route path within the app (optional).
146
+ * Omit or set to "/" for the app's default route.
147
+ *
148
+ * Examples: "/gallery", "/settings", "/settings/advanced"
149
+ */
150
+ path?: string;
151
+
152
+ /**
153
+ * App-specific query parameters (optional).
154
+ * Appended to the URL as search params inside the hash route.
155
+ * Used to specify content (e.g. which page, tab, or filter to show).
156
+ *
157
+ * Platform context params (collectionId, appId, productId, etc.)
158
+ * are injected automatically — do NOT include them here.
159
+ */
160
+ params?: Record<string, string>;
161
+ }
162
+ ```
163
+
164
+ ### Field Notes
165
+
166
+ | Field | Required | Notes |
167
+ |-------|----------|-------|
168
+ | `title` | ✅ | Displayed in menus and offered to AI agents. Should be concise and human-readable. |
169
+ | `path` | ❌ | Hash route within the app. Defaults to `/` if omitted. |
170
+ | `params` | ❌ | App-specific query params only. Platform params are injected automatically. |
171
+
172
+ > **Tip:** An entry with only a `title` (no `path` or `params`) is valid — it navigates to the app's default view. This is rarely useful unless the app's home screen is itself a meaningful destination distinct from other entries.
173
+
174
+ ---
175
+
176
+ ## Entry Types
177
+
178
+ ### 1. Data-Driven Entries (Dynamic)
179
+
180
+ Generated from app data — e.g., CMS pages, product guides, FAQ items. These are typically re-synced whenever the underlying data changes.
181
+
182
+ ```json
183
+ [
184
+ { "title": "About Us", "params": { "pageId": "about-us" } },
185
+ { "title": "Care Guide", "params": { "pageId": "care-guide" } },
186
+ { "title": "FAQ", "params": { "pageId": "faq" } }
187
+ ]
188
+ ```
189
+
190
+ **When to use:** The set of linkable states depends on admin-created content. The registry is rebuilt whenever pages are added, removed, published, or renamed.
191
+
192
+ ### 2. Route-Based Entries (Static)
193
+
194
+ Fixed routes that are built into the app. Declared once and rarely change.
195
+
196
+ ```json
197
+ [
198
+ { "title": "Gallery", "path": "/gallery" },
199
+ { "title": "Settings", "path": "/settings" },
200
+ { "title": "Advanced Settings", "path": "/settings", "params": { "tab": "advanced" } }
201
+ ]
202
+ ```
203
+
204
+ **When to use:** The app has multiple built-in views (tabs, sections) that should always be directly navigable, regardless of content.
205
+
206
+ > **Note:** Different entries can share the same `path` if they differ by `params` — as in the Settings example above.
207
+
208
+ ### 3. Mixed
209
+
210
+ Apps can freely combine both patterns. Static routes can appear alongside dynamic content entries:
211
+
212
+ ```json
213
+ [
214
+ { "title": "Gallery", "path": "/gallery" },
215
+ { "title": "About Us", "params": { "pageId": "about-us" } },
216
+ { "title": "FAQ", "params": { "pageId": "faq" } },
217
+ { "title": "Settings", "path": "/settings" }
218
+ ]
219
+ ```
220
+
221
+ **Ordering:** Entries are displayed in array order. Place the most commonly navigated states first.
222
+
223
+ ---
224
+
225
+ ## URL Resolution
226
+
227
+ The parent platform constructs the final navigation URL from an entry by combining:
228
+
229
+ 1. The app's **base URL**
230
+ 2. The **entry point** (`index.html` for public routes, `admin.html` for admin routes)
231
+ 3. The **hash route path** (defaults to `/`)
232
+ 4. Platform **context params** (`collectionId`, `appId`, `productId`, etc.)
233
+ 5. The entry's `params`
234
+
235
+ ### Examples
236
+
237
+ | Entry | Resolved URL |
238
+ |-------|--------------|
239
+ | `{ title: "About Us", params: { pageId: "about-us" } }` | `https://app.example.com/#/?collectionId=abc&appId=my-app&pageId=about-us` |
240
+ | `{ title: "Advanced Settings", path: "/settings", params: { tab: "advanced" } }` | `https://app.example.com/admin.html#/settings?collectionId=abc&appId=my-app&tab=advanced` |
241
+ | `{ title: "Gallery", path: "/gallery" }` | `https://app.example.com/#/gallery?collectionId=abc&appId=my-app` |
242
+ | `{ title: "Home" }` | `https://app.example.com/#/?collectionId=abc&appId=my-app` |
243
+
244
+ ### Injected Context Params
245
+
246
+ The platform automatically injects the following — **never include these in `params`**:
247
+
248
+ | Param | Source |
249
+ |-------|--------|
250
+ | `collectionId` | Active collection |
251
+ | `appId` | The app being navigated to |
252
+ | `productId` | Active product (if set) |
253
+ | `proofId` | Active proof (if set) |
254
+ | `lang` | Current language preference |
255
+ | `theme` | Active theme name |
256
+
257
+ ---
258
+
259
+ ## Syncing the Registry
260
+
261
+ ### When to Sync
262
+
263
+ Apps **MUST** update the `linkable` array whenever the set of navigable states changes:
264
+
265
+ - A page is created, deleted, published, or unpublished
266
+ - A page's `deepLinkable` flag is toggled
267
+ - A page title changes
268
+ - App routes are added or removed
269
+
270
+ ### How to Sync
271
+
272
+ The sync pattern is a **full replace** — read current config, overwrite `linkable`, save back. This avoids orphaned entries and keeps the logic simple.
273
+
274
+ ```typescript
275
+ import { appConfiguration } from '@proveanything/smartlinks';
276
+
277
+ async function syncLinkable(collectionId: string, appId: string): Promise<void> {
278
+ // 1. Fetch the current set of linkable states from your data source
279
+ // Always fetch fresh — never rely on a local cache
280
+ const entries = await fetchLinkableEntries(); // your app-specific logic
281
+
282
+ // 2. Read the existing appConfig to preserve other keys
283
+ let config: Record<string, unknown> = {};
284
+ try {
285
+ config = await appConfiguration.getConfig({
286
+ collectionId,
287
+ appId,
288
+ admin: true,
289
+ }) ?? {};
290
+ } catch {
291
+ // Config may not exist yet on first run — that's fine
292
+ }
293
+
294
+ // 3. Merge and save
295
+ await appConfiguration.setConfig({
296
+ collectionId,
297
+ appId,
298
+ config: { ...config, linkable: entries },
299
+ admin: true,
300
+ });
301
+ }
302
+ ```
303
+
304
+ ### What `fetchLinkableEntries` looks like
305
+
306
+ This function is app-specific. A typical CMS app might look like:
307
+
308
+ ```typescript
309
+ async function fetchLinkableEntries(): Promise<DeepLinkEntry[]> {
310
+ // Fetch your pages/content from the server (not from cache)
311
+ const pages = await myApi.getPublishedPages({ forceRefresh: true });
312
+
313
+ return pages
314
+ .filter(page => page.deepLinkable)
315
+ .map(page => ({
316
+ title: page.title,
317
+ params: { pageId: page.slug },
318
+ }));
319
+ }
320
+ ```
321
+
322
+ A route-based app simply returns a static array:
323
+
324
+ ```typescript
325
+ async function fetchLinkableEntries(): Promise<DeepLinkEntry[]> {
326
+ return [
327
+ { title: 'Gallery', path: '/gallery' },
328
+ { title: 'Settings', path: '/settings' },
329
+ ];
330
+ }
331
+ ```
332
+
333
+ ### Idempotency
334
+
335
+ The sync is always a full replace of the `linkable` array. Running it multiple times with the same data produces the same result — no duplicates, no orphans.
336
+
337
+ ### Clearing the Registry
338
+
339
+ To remove all linkable states (e.g., when an app is deactivated):
340
+
341
+ ```typescript
342
+ await appConfiguration.setConfig({
343
+ collectionId,
344
+ appId,
345
+ config: { ...config, linkable: [] },
346
+ admin: true,
347
+ });
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Consumer Patterns
353
+
354
+ ### Portal Navigation Menu
355
+
356
+ A portal shell can aggregate linkable states from all apps in a collection to build a unified navigation menu:
357
+
358
+ ```typescript
359
+ import { appConfiguration } from '@proveanything/smartlinks';
360
+
361
+ interface NavLink {
362
+ appId: string;
363
+ title: string;
364
+ path?: string;
365
+ params?: Record<string, string>;
366
+ }
367
+
368
+ async function buildNavMenu(collectionId: string, appIds: string[]): Promise<NavLink[]> {
369
+ const links: NavLink[] = [];
370
+
371
+ for (const appId of appIds) {
372
+ const config = await appConfiguration.getConfig({ collectionId, appId });
373
+ const entries: DeepLinkEntry[] = config?.linkable ?? [];
374
+
375
+ links.push(
376
+ ...entries.map(entry => ({ ...entry, appId }))
377
+ );
378
+ }
379
+
380
+ return links;
381
+ }
382
+ ```
383
+
384
+ ### AI Orchestration
385
+
386
+ An AI agent can discover what states an app exposes and offer navigation as part of a conversation:
387
+
388
+ ```typescript
389
+ import { appConfiguration } from '@proveanything/smartlinks';
390
+
391
+ async function getNavigationOptions(collectionId: string, appId: string) {
392
+ const config = await appConfiguration.getConfig({ collectionId, appId });
393
+ const linkable: DeepLinkEntry[] = config?.linkable ?? [];
394
+
395
+ // The AI now knows what's available:
396
+ // "I can navigate you to: Gallery, About Us, or FAQ.
397
+ // Which would you prefer?"
398
+ return linkable.map(entry => entry.title);
399
+ }
400
+ ```
401
+
402
+ The `title` field is intentionally human-readable so it can be injected directly into LLM prompts without additional transformation.
403
+
404
+ ### Cross-App Navigation
405
+
406
+ An app can use another app's `linkable` registry to construct a `NavigationRequest` for the portal's `onNavigate` callback:
407
+
408
+ ```typescript
409
+ // Inside a widget or container's onNavigate handler
410
+ const targetConfig = await appConfiguration.getConfig({
411
+ collectionId,
412
+ appId: 'target-app-id',
413
+ });
414
+
415
+ const entry = targetConfig?.linkable?.find(e => e.title === 'About Us');
416
+
417
+ if (entry) {
418
+ onNavigate({
419
+ appId: 'target-app-id',
420
+ deepLink: entry.path,
421
+ params: entry.params,
422
+ });
423
+ }
424
+ ```
425
+
426
+ ---
427
+
428
+ ## Manifest Declaration
429
+
430
+ Apps that support deep linking **SHOULD** declare the `linkable` key in their `app.admin.json` config schema. This signals to platform tooling that the key is auto-managed and prevents accidental manual edits:
431
+
432
+ ```json
433
+ {
434
+ "setup": {
435
+ "configSchema": {
436
+ "properties": {
437
+ "linkable": {
438
+ "type": "array",
439
+ "description": "Auto-managed deep link registry. Do not edit manually.",
440
+ "readOnly": true,
441
+ "items": {
442
+ "type": "object",
443
+ "required": ["title"],
444
+ "properties": {
445
+ "title": { "type": "string", "description": "Human-readable label" },
446
+ "path": { "type": "string", "description": "Hash route path, e.g. '/gallery'" },
447
+ "params": {
448
+ "type": "object",
449
+ "description": "App-specific query params. Do not include platform context params.",
450
+ "additionalProperties": { "type": "string" }
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+ }
459
+ ```
460
+
461
+ > **Note:** The `readOnly: true` flag is a hint to the admin UI to hide or disable this field in manual config editors.
462
+
463
+ ---
464
+
465
+ ## TypeScript Types
466
+
467
+ Copy these types into your app, or import them if they become part of the SDK's public API:
468
+
469
+ ```typescript
470
+ /**
471
+ * A single navigable state exposed by a SmartLinks app.
472
+ * Stored as an array at `appConfig.linkable`.
473
+ */
474
+ export interface DeepLinkEntry {
475
+ /** Human-readable label shown in menus and offered to AI agents */
476
+ title: string;
477
+ /**
478
+ * Hash route path within the app.
479
+ * Defaults to "/" if omitted.
480
+ * Examples: "/gallery", "/settings"
481
+ */
482
+ path?: string;
483
+ /**
484
+ * App-specific query params appended to the hash route URL.
485
+ * Do NOT include platform context params (collectionId, appId, etc.).
486
+ */
487
+ params?: Record<string, string>;
488
+ }
489
+
490
+ /**
491
+ * The shape of the `linkable` key in appConfig.
492
+ * Alias for convenience.
493
+ */
494
+ export type DeepLinkRegistry = DeepLinkEntry[];
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Rules & Best Practices
500
+
501
+ ### Rules
502
+
503
+ 1. **`linkable` is reserved** — do not use this key for other purposes in `appConfig`.
504
+ 2. **No platform context params in entries** — `collectionId`, `appId`, `productId`, `proofId`, `lang`, `theme` are injected by the platform automatically.
505
+ 3. **`title` is required** — every entry must have a human-readable label.
506
+ 4. **Sync from server** — always fetch fresh data when syncing; never read from a local cache or previously fetched state.
507
+ 5. **Full replace** — always overwrite the entire `linkable` array; never attempt to diff or patch individual entries.
508
+
509
+ ### Best Practices
510
+
511
+ - **Keep titles short and scannable** — they appear in navigation menus and AI prompts. Prefer "About Us" over "Our Company Story & Mission".
512
+ - **Order entries by relevance** — the first few entries are most likely to be shown in compact menus; place the most important states first.
513
+ - **Sync eagerly** — trigger a sync immediately after any content change rather than on a schedule. The operation is cheap and idempotent.
514
+ - **Handle missing `linkable` gracefully** — older apps won't have a `linkable` key. Always use `config?.linkable ?? []` rather than `config.linkable`.
515
+ - **Don't duplicate the default route** — if your app has only one view, an empty `linkable` array (or omitting it) is better than a single entry that just opens the app.
516
+ - **Test URL resolution** — confirm that your entries produce the correct URLs in the platform before publishing. Check that params are correctly appended and context params are not doubled up.