@proveanything/smartlinks 2.0.5 → 2.0.9
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/ai.d.ts +1 -1
- package/dist/api/ai.js +1 -1
- package/dist/api/analytics.d.ts +1 -1
- package/dist/api/analytics.js +1 -1
- package/dist/api/appConfiguration.d.ts +3 -3
- package/dist/api/appConfiguration.js +3 -3
- package/dist/api/appObjects.d.ts +1 -1
- package/dist/api/appObjects.js +1 -1
- package/dist/api/asset.d.ts +1 -1
- package/dist/api/asset.js +2 -2
- package/dist/api/async.d.ts +1 -1
- package/dist/api/async.js +1 -1
- package/dist/api/attestation.d.ts +1 -1
- package/dist/api/attestation.js +1 -1
- package/dist/api/attestations.d.ts +1 -1
- package/dist/api/attestations.js +1 -1
- package/dist/api/auth.d.ts +2 -2
- package/dist/api/auth.js +2 -2
- package/dist/api/authKit.d.ts +1 -1
- package/dist/api/authKit.js +1 -1
- package/dist/api/batch.d.ts +1 -1
- package/dist/api/batch.js +1 -1
- package/dist/api/broadcasts.d.ts +2 -2
- package/dist/api/broadcasts.js +1 -1
- package/dist/api/claimSet.d.ts +1 -1
- package/dist/api/claimSet.js +1 -1
- package/dist/api/collection.d.ts +1 -1
- package/dist/api/collection.js +1 -1
- package/dist/api/comms.d.ts +15 -15
- package/dist/api/comms.js +1 -1
- package/dist/api/config.d.ts +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/contact.d.ts +1 -1
- package/dist/api/contact.js +1 -1
- package/dist/api/containers.d.ts +1 -1
- package/dist/api/containers.js +1 -1
- package/dist/api/crate.d.ts +1 -1
- package/dist/api/crate.js +1 -1
- package/dist/api/facets.d.ts +1 -1
- package/dist/api/facets.js +1 -1
- package/dist/api/form.js +1 -1
- package/dist/api/http.js +1 -1
- package/dist/api/index.d.ts +46 -46
- package/dist/api/index.js +46 -46
- package/dist/api/integrations.d.ts +1 -1
- package/dist/api/integrations.js +1 -1
- package/dist/api/interactions.d.ts +1 -1
- package/dist/api/interactions.js +1 -1
- package/dist/api/jobs.d.ts +1 -1
- package/dist/api/jobs.js +1 -1
- package/dist/api/journeys.d.ts +1 -1
- package/dist/api/journeys.js +1 -1
- package/dist/api/journeysAnalytics.d.ts +1 -1
- package/dist/api/journeysAnalytics.js +1 -1
- package/dist/api/location.d.ts +1 -1
- package/dist/api/location.js +1 -1
- package/dist/api/lots.d.ts +1 -1
- package/dist/api/lots.js +1 -1
- package/dist/api/loyalty.d.ts +1 -1
- package/dist/api/loyalty.js +1 -1
- package/dist/api/navigation.d.ts +1 -1
- package/dist/api/navigation.js +1 -1
- package/dist/api/nfc.d.ts +1 -1
- package/dist/api/nfc.js +1 -1
- package/dist/api/order.d.ts +1 -1
- package/dist/api/order.js +1 -1
- package/dist/api/product.d.ts +1 -1
- package/dist/api/product.js +1 -1
- package/dist/api/products.d.ts +1 -1
- package/dist/api/products.js +1 -1
- package/dist/api/proof.d.ts +1 -1
- package/dist/api/proof.js +1 -1
- package/dist/api/qr.d.ts +1 -1
- package/dist/api/qr.js +1 -1
- package/dist/api/realtime.d.ts +1 -1
- package/dist/api/realtime.js +1 -1
- package/dist/api/research.d.ts +1 -1
- package/dist/api/research.js +1 -1
- package/dist/api/secrets.d.ts +1 -1
- package/dist/api/secrets.js +1 -1
- package/dist/api/segments.d.ts +1 -1
- package/dist/api/segments.js +1 -1
- package/dist/api/sequence.js +1 -1
- package/dist/api/tags.d.ts +1 -1
- package/dist/api/tags.js +1 -1
- package/dist/api/template.d.ts +1 -1
- package/dist/api/template.js +1 -1
- package/dist/api/translations.d.ts +1 -1
- package/dist/api/translations.js +2 -2
- package/dist/api/variant.d.ts +1 -1
- package/dist/api/variant.js +1 -1
- package/dist/containers/types.d.ts +1 -1
- package/dist/docs/API_SUMMARY.md +7 -7
- package/dist/docs/agent-tools.md +111 -0
- package/dist/docs/ai.md +14 -520
- package/dist/docs/analytics.md +41 -2
- package/dist/docs/app-data-storage.md +0 -38
- package/dist/docs/app-manifest.md +104 -7
- package/dist/docs/app-objects.md +0 -148
- package/dist/docs/app-records-pattern.md +2 -2
- package/dist/docs/building-react-components.md +6 -14
- package/dist/docs/caching.md +20 -21
- package/dist/docs/container-tracking.md +2 -0
- package/dist/docs/containers.md +14 -66
- package/dist/docs/deploying-apps.md +8 -3
- package/dist/docs/executor.md +4 -4
- package/dist/docs/host-dependency-contract.md +159 -0
- package/dist/docs/iframe-responder.md +308 -0
- package/dist/docs/item-context.md +0 -2
- package/dist/docs/manifests.md +3 -3
- package/dist/docs/mobile-admin-container.md +4 -4
- package/dist/docs/mpa.md +5 -5
- package/dist/docs/native-facade.md +1 -1
- package/dist/docs/overview.md +36 -15
- package/dist/docs/portal-back-button.md +2 -3
- package/dist/docs/sequences.md +1 -1
- package/dist/docs/server-functions.md +2 -3
- package/dist/docs/widgets.md +11 -69
- package/dist/http.d.ts +24 -8
- package/dist/http.js +32 -14
- package/dist/iframe.d.ts +2 -2
- package/dist/iframe.js +1 -1
- package/dist/iframeResponder.d.ts +7 -1
- package/dist/iframeResponder.js +45 -4
- package/dist/index.d.ts +30 -27
- package/dist/index.js +10 -8
- package/dist/mobile-admin/errors.d.ts +1 -1
- package/dist/mobile-admin/types.d.ts +2 -2
- package/dist/openapi.yaml +12 -0
- package/dist/shared-dependencies.d.ts +37 -0
- package/dist/shared-dependencies.js +79 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/translationCache.d.ts +1 -1
- package/dist/types/appManifest.d.ts +23 -0
- package/dist/types/broadcasts.d.ts +1 -1
- package/dist/types/collection.d.ts +2 -2
- package/dist/types/comms.d.ts +5 -5
- package/dist/types/contact.d.ts +1 -1
- package/dist/types/facets.d.ts +1 -1
- package/dist/types/iframeResponder.d.ts +3 -3
- package/dist/types/index.d.ts +44 -44
- package/dist/types/index.js +44 -44
- package/dist/types/interaction.d.ts +1 -1
- package/dist/types/itemContext.d.ts +1 -1
- package/dist/types/journeysAnalytics.d.ts +1 -1
- package/dist/types/navigation.d.ts +1 -1
- package/dist/types/product.d.ts +1 -1
- package/dist/types/proof.d.ts +1 -1
- package/dist/types/segments.d.ts +1 -1
- package/dist/types/widgets.d.ts +2 -2
- package/dist/utils/conditions.d.ts +1 -1
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -3
- package/dist/utils/paths.d.ts +4 -4
- package/docs/API_SUMMARY.md +7 -7
- package/docs/agent-tools.md +111 -0
- package/docs/ai.md +14 -520
- package/docs/analytics.md +41 -2
- package/docs/app-data-storage.md +0 -38
- package/docs/app-manifest.md +104 -7
- package/docs/app-objects.md +0 -148
- package/docs/app-records-pattern.md +2 -2
- package/docs/building-react-components.md +6 -14
- package/docs/caching.md +20 -21
- package/docs/container-tracking.md +2 -0
- package/docs/containers.md +14 -66
- package/docs/deploying-apps.md +8 -3
- package/docs/executor.md +4 -4
- package/docs/host-dependency-contract.md +159 -0
- package/docs/iframe-responder.md +308 -0
- package/docs/item-context.md +0 -2
- package/docs/mobile-admin-container.md +4 -4
- package/docs/mpa.md +5 -5
- package/docs/native-facade.md +1 -1
- package/docs/overview.md +36 -15
- package/docs/portal-back-button.md +2 -3
- package/docs/sequences.md +1 -1
- package/docs/server-functions.md +2 -3
- package/docs/widgets.md +11 -69
- package/openapi.yaml +12 -0
- package/package.json +17 -6
- package/scripts/doctor.mjs +171 -0
- package/docs/analytics-metadata-conventions.md +0 -88
- package/docs/iframe-streaming-parent-changes.md +0 -308
- package/docs/manifests.md +0 -204
package/dist/docs/manifests.md
CHANGED
|
@@ -32,7 +32,7 @@ app.admin.json ← loaded on-demand (setup wizards, import, AI config flow
|
|
|
32
32
|
"instanceResolution": true,
|
|
33
33
|
"instanceParam": "widgetId",
|
|
34
34
|
"files": {
|
|
35
|
-
"js": { "umd": "dist/widgets.umd.js", "esm": "dist/widgets.
|
|
35
|
+
"js": { "umd": "dist/widgets.umd.js", "esm": "dist/widgets.esm.js" },
|
|
36
36
|
"css": null
|
|
37
37
|
},
|
|
38
38
|
"components": [
|
|
@@ -46,13 +46,13 @@ app.admin.json ← loaded on-demand (setup wizards, import, AI config flow
|
|
|
46
46
|
},
|
|
47
47
|
"containers": {
|
|
48
48
|
"files": {
|
|
49
|
-
"js": { "umd": "dist/containers.umd.js", "esm": "dist/containers.
|
|
49
|
+
"js": { "umd": "dist/containers.umd.js", "esm": "dist/containers.esm.js" },
|
|
50
50
|
"css": null
|
|
51
51
|
},
|
|
52
52
|
"components": [{ "name": "PublicContainer", "description": "Full app view." }]
|
|
53
53
|
},
|
|
54
54
|
"executor": {
|
|
55
|
-
"files": { "js": { "umd": "dist/executor.umd.js", "esm": "dist/executor.
|
|
55
|
+
"files": { "js": { "umd": "dist/executor.umd.js", "esm": "dist/executor.esm.js" } },
|
|
56
56
|
"factory": "createMyAppExecutor",
|
|
57
57
|
"exports": ["createMyAppExecutor", "getSEO", "getLLMContent"],
|
|
58
58
|
"description": "Programmatic configuration and SEO API for My App."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mobile Admin Container SDK
|
|
2
2
|
|
|
3
|
-
> **Version:** 1.0 · **Platform:** SmartLinks
|
|
3
|
+
> **Version:** 1.0 · **Platform:** SmartLinks R5 · **Last updated:** 2026-09-21
|
|
4
4
|
|
|
5
5
|
This document describes how to build a **Mobile Admin Container** — a SmartLinks microapp that provides an in-the-field operator/admin surface optimised for mobile devices. These containers ship as a **separate `mobileAdmin` bundle** (not inside the `containers` bundle) so that Capacitor plugins, offline helpers, and operator-only code never reach the public consumer bundle.
|
|
6
6
|
|
|
@@ -271,7 +271,7 @@ Declare the bundle under the top-level `mobileAdmin` key in `app.manifest.json`.
|
|
|
271
271
|
"meta": { "appId": "my-app", "name": "My App", "version": "1.0.0" },
|
|
272
272
|
|
|
273
273
|
"containers": {
|
|
274
|
-
"files": { "js": { "umd": "dist/containers.umd.js", "esm": "dist/containers.
|
|
274
|
+
"files": { "js": { "umd": "dist/containers.umd.js", "esm": "dist/containers.esm.js" }, "css": "dist/containers.css" },
|
|
275
275
|
"components": [
|
|
276
276
|
{ "name": "PublicContainer", "description": "Default consumer experience" }
|
|
277
277
|
]
|
|
@@ -281,7 +281,7 @@ Declare the bundle under the top-level `mobileAdmin` key in `app.manifest.json`.
|
|
|
281
281
|
"files": {
|
|
282
282
|
"js": {
|
|
283
283
|
"umd": "dist/mobile-admin.umd.js",
|
|
284
|
-
"esm": "dist/mobile-admin.
|
|
284
|
+
"esm": "dist/mobile-admin.esm.js"
|
|
285
285
|
},
|
|
286
286
|
"css": null
|
|
287
287
|
},
|
|
@@ -416,7 +416,7 @@ The mobile admin bundle has its own Vite config: `vite.config.mobile-admin.ts`.
|
|
|
416
416
|
|
|
417
417
|
```
|
|
418
418
|
dist/mobile-admin.umd.js
|
|
419
|
-
dist/mobile-admin.
|
|
419
|
+
dist/mobile-admin.esm.js
|
|
420
420
|
dist/mobile-admin.css (if needed)
|
|
421
421
|
```
|
|
422
422
|
|
package/dist/docs/mpa.md
CHANGED
|
@@ -56,9 +56,9 @@ vite build
|
|
|
56
56
|
| Step | Config / Script | Gate env var | Output |
|
|
57
57
|
|------|----------------|-------------|--------|
|
|
58
58
|
| 1 | `vite.config.ts` | Always runs | `index.html`, `admin.html`, `assets/*` |
|
|
59
|
-
| 2 | `vite.config.widget.ts` | `VITE_ENABLE_WIDGETS=true` | `widgets.umd.js`, `widgets.
|
|
60
|
-
| 3 | `vite.config.container.ts` | `VITE_ENABLE_CONTAINERS=true` | `containers.umd.js`, `containers.
|
|
61
|
-
| 4 | `vite.config.executor.ts` | `VITE_ENABLE_EXECUTOR!=false` | `executor.umd.js`, `executor.
|
|
59
|
+
| 2 | `vite.config.widget.ts` | `VITE_ENABLE_WIDGETS=true` | `widgets.umd.js`, `widgets.esm.js`, `widgets.css` |
|
|
60
|
+
| 3 | `vite.config.container.ts` | `VITE_ENABLE_CONTAINERS=true` | `containers.umd.js`, `containers.esm.js`, `containers.css` |
|
|
61
|
+
| 4 | `vite.config.executor.ts` | `VITE_ENABLE_EXECUTOR!=false` | `executor.umd.js`, `executor.esm.js` |
|
|
62
62
|
| 5 | `scripts/hash-bundles.mjs` | Always runs | Renames bundles with content hashes; patches `dist/app.manifest.json` |
|
|
63
63
|
|
|
64
64
|
Steps 2–4 produce a harmless stub file when their gate env var is not set. Step 5 detects and skips stub files automatically.
|
|
@@ -107,7 +107,7 @@ dist/
|
|
|
107
107
|
└── executor-[hash].es.js ← Executor bundle (ESM)
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
> Widget and container CSS files are only present when the bundle ships custom styles. Most apps set `"css": null` in the manifest because they rely entirely on Tailwind/shadcn from the parent. See the [
|
|
110
|
+
> Widget and container CSS files are only present when the bundle ships custom styles. Most apps set `"css": null` in the manifest because they rely entirely on Tailwind/shadcn from the parent. See the [App Configuration Files](app-manifest.md) for the CSS null warning.
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
|
@@ -133,6 +133,6 @@ If the embedded app exposes nested screens, document its "up" navigation path wi
|
|
|
133
133
|
| [Widgets](widgets.md) | Widget bundle: components, props, settings |
|
|
134
134
|
| [Containers](containers.md) | Container bundle: full-app embeds |
|
|
135
135
|
| [Executor Model](executor.md) | Executor bundle: SEO, LLM content, config mutations |
|
|
136
|
-
| [
|
|
136
|
+
| [App Configuration Files](app-manifest.md) | Manifest + admin reference; widget settings schema; AI workflows |
|
|
137
137
|
| [iframe Responder](iframe-responder.md) | Reading context params inside the iframe |
|
|
138
138
|
| [Portal Back Button](portal-back-button.md) | Hierarchy-aware back navigation for embedded apps |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Native Capability Facade (`host.native` / `SL.native`)
|
|
2
2
|
|
|
3
|
-
> **Version:** 1.12 · **Platform:** SmartLinks
|
|
3
|
+
> **Version:** 1.12 · **Platform:** SmartLinks R5 · **Last updated:** 2026-09-21
|
|
4
4
|
|
|
5
5
|
The `NativeFacade` is a thin contract layer between microapps and the device capabilities available on the current host shell (Kotlin, Capacitor iOS/Android, PWA, or browser). It lets a microapp call `host.native.share.share({...})` without knowing whether it's running over `window.SmartlinksScanner`, a Capacitor plugin, or `navigator.share`.
|
|
6
6
|
|
package/dist/docs/overview.md
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
# SmartLinks Microapp Development Guide
|
|
2
2
|
|
|
3
|
-
> **Platform revision:**
|
|
4
|
-
>
|
|
3
|
+
> **Platform revision:** R5 · **SDK:** `@proveanything/smartlinks@^2` · React 19 · Vite 8 · Router 7 · Tailwind 4 · TS 6.
|
|
4
|
+
> The canonical version/host stack lives in [host-dependency-contract.md](host-dependency-contract.md).
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## What Is a SmartLinks Microapp?
|
|
9
9
|
|
|
10
|
-
SmartLinks
|
|
10
|
+
SmartLinks connects **physical products to digital experiences**: each item has a digital identity — a **proof** — that a consumer reaches by scanning a QR / NFC tag, then claims and enriches over time. A **microapp** is a focused, embeddable React experience a brand adds to that product's digital life: product info, warranty registration, authenticity checks, manuals, competitions, loyalty, post-purchase support, and more.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Rather than baking every feature into the core platform, functionality is distributed across purpose-built apps, so a brand adds exactly the experiences its product needs. Each app:
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- **
|
|
19
|
-
- **Share context** through URL parameters (collection, product, or proof being viewed)
|
|
20
|
-
- **Inherit identity** from the parent platform's authentication system
|
|
21
|
-
- **Adapt visually** to the brand's theme configuration
|
|
22
|
-
- **Communicate bidirectionally** with the parent via postMessage for deep linking and navigation
|
|
14
|
+
- **Embeds** in the SmartLinks Portal (public) and Admin Console (management)
|
|
15
|
+
- **Shares context** through URL parameters (collection, product, proof)
|
|
16
|
+
- **Inherits identity** from the parent platform's auth
|
|
17
|
+
- **Adapts** to the brand's theme
|
|
18
|
+
- **Communicates** with the parent via postMessage for deep linking and navigation
|
|
23
19
|
|
|
24
20
|
### Deployment Modes
|
|
25
21
|
|
|
@@ -45,7 +41,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
45
41
|
|
|
46
42
|
> Product endpoints: use `products` (plural) for new integrations. The older `product` (singular) namespace remains for backward compatibility and is deprecated.
|
|
47
43
|
|
|
48
|
-
> **
|
|
44
|
+
> **SDK baseline: `@proveanything/smartlinks@^2.0`** (R5). Install/update with `npm install @proveanything/smartlinks@^2.0`. Individual features may need a higher minor — see the relevant doc. The single source of truth for the R5 host/version stack is [`host-dependency-contract.md`](host-dependency-contract.md).
|
|
49
45
|
|
|
50
46
|
| Topic | File | When to Use |
|
|
51
47
|
|-------|------|-------------|
|
|
@@ -64,12 +60,13 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
64
60
|
| **Mobile Admin Container** | `docs/mobile-admin-container.md` | Building a separate Capacitor-aware mobile admin bundle for field operators |
|
|
65
61
|
| **Executors** | `docs/executor.md` | Building executor bundles for SEO, LLM content, programmatic config |
|
|
66
62
|
| **Server Functions** | `docs/server-functions.md` | App-authored server-side functions `(ctx, event) ⇒ result`: security model, runtime surface, invoking |
|
|
63
|
+
| **Agent Tools** | `docs/agent-tools.md` | Exposing your app's actions to the SmartLinks agent — an MCP facade over server functions; declare now, live loop staged; replaces `app.admin.json` AI setup |
|
|
67
64
|
| **Deploying & Registering** | `docs/deploying-apps.md` | Getting your app into the platform: fast dev publish, channels, deploy keys, registering releases |
|
|
65
|
+
| **Host Dependency Contract (R5)** | `docs/host-dependency-contract.md` | The libraries the host provides (React 19, Router 7, Radix, liquidjs, …), the externalise-don't-bundle rule + Vite config, and React-18 backwards-compat |
|
|
68
66
|
| **Deep Linking** | `docs/deep-link-discovery.md` | URL state management, navigable states, portal menus, AI nav |
|
|
69
67
|
| **Portal Back Button** | `docs/portal-back-button.md` | Hierarchy-aware "up" navigation inside embedded apps |
|
|
70
68
|
| **Portal Request Action** | `docs/portal-request-action.md` | Triggering portal built-in actions (__qrScanner, __share, __logout, etc.) from sub-apps |
|
|
71
69
|
| **Interactions** | `docs/interactions.md` | Business events, outcomes, voting, competitions, and journey triggers |
|
|
72
|
-
| **AI-Native Manifests** | `docs/manifests.md` | `app.manifest.json`, `app.admin.json`, `ai-guide.md` structure |
|
|
73
70
|
| **App Config Files** | `docs/app-manifest.md` | Full field-by-field reference for both JSON config files |
|
|
74
71
|
| **Real-time Messaging** | `docs/realtime.md` | Adding Ably real-time features (chat, live updates) |
|
|
75
72
|
| **Liquid Templates** | `docs/liquid-templates.md` | Dynamic content rendering with LiquidJS |
|
|
@@ -87,6 +84,21 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
87
84
|
| **Proof Share Grants** | `docs/proof-share-grants.md` | Delegated, scoped, revocable bearer access to a single proof (read/comment/verify-owner) |
|
|
88
85
|
| **Proof Ownership Transfer** | `docs/proof-ownership-transfer.md` | Moving a proof's single owner (directed transfer / open release), accept/cancel, and the state machine |
|
|
89
86
|
| **appConfig / Feature Flags** | `docs/appConfig.md` | `appConfig` settings contract — installed apps, `system.features`/`entitledAppGroups`/`meters`, `isFeatureEnabled()` helper |
|
|
87
|
+
| **App Objects** | `docs/app-objects.md` | Queryable domain objects — `app.records` / `app.cases` / `app.threads`: JSONB zones, visibility, policies, aggregations |
|
|
88
|
+
| **App Data Storage** | `docs/app-data-storage.md` | Decision guide: pick between `appConfiguration` / `userAppData` / `app.records` |
|
|
89
|
+
| **Attestations** | `docs/attestations.md` | Postgres attestations API (proof-level user/admin data; replaces the legacy Firestore path) |
|
|
90
|
+
| **Caching** | `docs/caching.md` | SDK GET cache tiers + TTLs, `invalidateCache({ exact })`, and `force`/push guidance |
|
|
91
|
+
| **Comms & Broadcasts** | `docs/comms.md` | Transactional comms, campaigns, consent, push |
|
|
92
|
+
| **Container Tracking** | `docs/container-tracking.md` | Physical/logical **item** container groupings — *not* app containers (see containers.md) |
|
|
93
|
+
| **Lots** | `docs/lots.md` | Cross-SKU production "Lot" entity |
|
|
94
|
+
| **Loyalty** | `docs/loyalty.md` | Points, members, earning rules |
|
|
95
|
+
| **Sequences** | `docs/sequences.md` | Atomic monotonic number allocation (raffle / queue / edition) |
|
|
96
|
+
| **Integrations** | `docs/integrations.md` | Inbound/outbound external-system integration flows |
|
|
97
|
+
| **Item Context** | `docs/item-context.md` | The `itemContext` container prop (serial / NFC authenticity context) |
|
|
98
|
+
| **Native Facade** | `docs/native-facade.md` | `host.native` / `SL.native` device-capability facade |
|
|
99
|
+
| **Product Facets** | `docs/PRODUCT_FACETS_SDK.md` | Facet API reference (admin + public endpoints, types) |
|
|
100
|
+
| **AI Tools & Skills** | `docs/ai-tools-and-skills.md` | Platform AI capability registry (web research, extraction, images) |
|
|
101
|
+
| **Proof Comms Triggers** | `docs/proof-comms-triggers.md` | Transactional comms fired as a side-effect of proof actions |
|
|
90
102
|
|
|
91
103
|
---
|
|
92
104
|
|
|
@@ -97,6 +109,15 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
97
109
|
- **Context via URL params** — All contextual data is passed through URL parameters
|
|
98
110
|
- **SmartLinks NPM module** — All data access and platform interaction goes through `@proveanything/smartlinks`
|
|
99
111
|
- **No standalone auth** — Authentication is handled by the parent SmartLinks platform
|
|
112
|
+
|
|
113
|
+
> **Use the host-provided `SL`, don't instantiate your own.** When your app runs in a
|
|
114
|
+
> container/iframe, the host injects an already-initialized, authenticated SDK instance (the
|
|
115
|
+
> `SL` prop / the externalized `@proveanything/smartlinks` singleton). Import and use *that*.
|
|
116
|
+
> Calling `initializeApi()` yourself to spin up a **second** SDK instance is a common source
|
|
117
|
+
> of bugs: it won't share the host's auth (so it looks signed-out), won't share the host's
|
|
118
|
+
> cache (duplicate requests, stale data), and in proxy mode it bypasses the host's request
|
|
119
|
+
> routing. Rule of thumb: **host-provided `SL` for anything the platform owns** (auth, config,
|
|
120
|
+
> data, cache); only app-owned state is yours.
|
|
100
121
|
- **Multi-page build** — Separate bundles for public and admin — see `docs/mpa.md`
|
|
101
122
|
- **Embedded back navigation** — Use `docs/portal-back-button.md` when a sub-app has a real content hierarchy
|
|
102
123
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Portal Back Button — "Up" Navigation Inside Sub-Apps
|
|
2
2
|
|
|
3
|
-
> **For sub-app authors.**
|
|
4
|
-
>
|
|
5
|
-
> microapp authors discover it.
|
|
3
|
+
> **For sub-app authors.** How to cooperate with the portal shell's top-level
|
|
4
|
+
> back/up control. See also [`mpa.md`](mpa.md).
|
|
6
5
|
|
|
7
6
|
When a sub-app is embedded by a portal shell, the shell renders a top-level
|
|
8
7
|
back button. By default that button **exits the app entirely** when tapped —
|
package/dist/docs/sequences.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sequences & claim-order allocation
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **SmartLinks SDK 2.x** (current `latest`). Install `@proveanything/smartlinks@^2`.
|
|
4
4
|
|
|
5
5
|
A **sequence** hands out a guaranteed-unique, monotonic number — `1, 2, 3, …` — and stamps it
|
|
6
6
|
onto a record. It's the primitive behind raffle tickets, "you're the Nth to claim", queue
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Server functions ("edge functions")
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
5
|
-
> then. Published under the npm `next` tag; `latest` remains 1.x.
|
|
3
|
+
> **SmartLinks SDK 2.x** (current `latest`). Part of the installable-app platform
|
|
4
|
+
> (author → register → install → run → test). Install `@proveanything/smartlinks@^2`.
|
|
6
5
|
|
|
7
6
|
A **server function** is arbitrary server-side JavaScript your app deploys directly into
|
|
8
7
|
SmartLinks. It runs on the SmartLinks servers — with access to the full SDK, to your app's
|
package/dist/docs/widgets.md
CHANGED
|
@@ -14,7 +14,7 @@ Widgets are self-contained React components that:
|
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
16
|
┌─────────────────────────────────────────────────────────────────┐
|
|
17
|
-
│ Parent SmartLinks Portal (React
|
|
17
|
+
│ Parent SmartLinks Portal (React 19) │
|
|
18
18
|
│ │
|
|
19
19
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
20
20
|
│ │ Competition │ │ Music App │ │ Warranty │ │
|
|
@@ -53,53 +53,7 @@ Widgets are typically single-view components and don't need internal routing. If
|
|
|
53
53
|
|
|
54
54
|
### The `useAppContext()` Pattern
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
// src/hooks/useAppContext.ts
|
|
60
|
-
import { useContext, createContext, useMemo } from 'react';
|
|
61
|
-
import { useSearchParams } from 'react-router-dom';
|
|
62
|
-
|
|
63
|
-
export interface AppContextValue {
|
|
64
|
-
collectionId: string;
|
|
65
|
-
appId: string;
|
|
66
|
-
productId?: string;
|
|
67
|
-
proofId?: string;
|
|
68
|
-
pageId?: string;
|
|
69
|
-
lang?: string;
|
|
70
|
-
user?: { id: string; email: string; name?: string };
|
|
71
|
-
SL: typeof import('@proveanything/smartlinks');
|
|
72
|
-
onNavigate?: (request: any) => void;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const AppContext = createContext<AppContextValue | null>(null);
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Returns app context regardless of rendering mode.
|
|
79
|
-
* - Direct component mode: reads from AppContext (props)
|
|
80
|
-
* - Iframe mode: reads from URL search params
|
|
81
|
-
*/
|
|
82
|
-
export function useAppContext(): AppContextValue {
|
|
83
|
-
const ctx = useContext(AppContext);
|
|
84
|
-
|
|
85
|
-
// If context exists, we're in direct-component mode
|
|
86
|
-
if (ctx) return ctx;
|
|
87
|
-
|
|
88
|
-
// Otherwise, we're in iframe mode — read from URL params
|
|
89
|
-
const [searchParams] = useSearchParams();
|
|
90
|
-
const SL = (window as any).SL ?? require('@proveanything/smartlinks');
|
|
91
|
-
|
|
92
|
-
return useMemo(() => ({
|
|
93
|
-
collectionId: searchParams.get('collectionId') ?? '',
|
|
94
|
-
appId: searchParams.get('appId') ?? '',
|
|
95
|
-
productId: searchParams.get('productId') ?? undefined,
|
|
96
|
-
proofId: searchParams.get('proofId') ?? undefined,
|
|
97
|
-
pageId: searchParams.get('pageId') ?? undefined,
|
|
98
|
-
lang: searchParams.get('lang') ?? undefined,
|
|
99
|
-
SL,
|
|
100
|
-
}), [searchParams, SL]);
|
|
101
|
-
}
|
|
102
|
-
```
|
|
56
|
+
Widgets read their context through the shared **`useAppContext()`** hook so the same code works in both direct-component and iframe modes. The hook and `AppContext` provider are defined once — see **[building-react-components.md](building-react-components.md#the-useappcontext-pattern)** for the full implementation (don't re-define it per app).
|
|
103
57
|
|
|
104
58
|
**Usage in your widget:**
|
|
105
59
|
|
|
@@ -344,7 +298,7 @@ Declare support in `app.manifest.json`:
|
|
|
344
298
|
"files": {
|
|
345
299
|
"js": {
|
|
346
300
|
"umd": "dist/widgets.umd.js",
|
|
347
|
-
"esm": "dist/widgets.
|
|
301
|
+
"esm": "dist/widgets.esm.js"
|
|
348
302
|
},
|
|
349
303
|
"css": null
|
|
350
304
|
},
|
|
@@ -511,7 +465,7 @@ export { MyWidget } from './MyWidget';
|
|
|
511
465
|
// Update the manifest
|
|
512
466
|
export const WIDGET_MANIFEST = {
|
|
513
467
|
version: '1.0.0',
|
|
514
|
-
reactVersion: '
|
|
468
|
+
reactVersion: '19.x',
|
|
515
469
|
widgets: [
|
|
516
470
|
// ... existing widgets
|
|
517
471
|
{
|
|
@@ -570,7 +524,7 @@ The project includes a separate Vite config for building widgets:
|
|
|
570
524
|
# Build widgets only
|
|
571
525
|
vite build --config vite.config.widget.ts
|
|
572
526
|
|
|
573
|
-
# Output: dist/widgets.
|
|
527
|
+
# Output: dist/widgets.esm.js
|
|
574
528
|
```
|
|
575
529
|
|
|
576
530
|
### Build Configuration
|
|
@@ -582,23 +536,11 @@ The widget build:
|
|
|
582
536
|
- Minifies with esbuild for production
|
|
583
537
|
- Outputs to `/dist` alongside the main app (not a separate folder)
|
|
584
538
|
|
|
585
|
-
### Externalized
|
|
586
|
-
|
|
587
|
-
The widget bundle does **not** include these libraries—the parent app must provide them:
|
|
539
|
+
### Externalized dependencies
|
|
588
540
|
|
|
589
|
-
|
|
590
|
-
|---------|------------------|
|
|
591
|
-
| `react`, `react-dom` | Parent's React context |
|
|
592
|
-
| `@proveanything/smartlinks` | Passed via props as `SL` |
|
|
593
|
-
| `@proveanything/smartlinks-auth-ui` | Auth UI components (also available globally as `window.SmartlinksAuthUI`) |
|
|
594
|
-
| `tailwind-merge` | Utility for merging Tailwind classes |
|
|
595
|
-
| `clsx` | Utility for conditional class names |
|
|
596
|
-
| `class-variance-authority` | Utility for component variants |
|
|
541
|
+
The widget bundle does **not** include the host's shared libraries (React, the SDK, Radix, LiquidJS, …) — it **externalizes** them and resolves them from the host at runtime, so the bundle stays tiny and there's exactly one shared instance.
|
|
597
542
|
|
|
598
|
-
|
|
599
|
-
1. Reduces bundle size significantly
|
|
600
|
-
2. Removes JSDoc comments that inflate the bundle
|
|
601
|
-
3. Ensures consistent behavior with parent's versions
|
|
543
|
+
**The canonical, versioned list is the shared-dependency contract — don't hand-maintain your own here.** Import it from the SDK (`SHARED_DEPENDENCY_SPECIFIERS`) for your build's `external` list, and see **[host-dependency-contract.md](host-dependency-contract.md)** for the full table, the Vite `external`/`globals` config, and the *never bundle your own React* rule. `@proveanything/smartlinks-auth-ui` is externalized too (host global `window.SmartlinksAuthUI`) — do not ship a second copy.
|
|
602
544
|
|
|
603
545
|
### Enabling Widget Builds
|
|
604
546
|
|
|
@@ -626,7 +568,7 @@ import * as SL from '@proveanything/smartlinks';
|
|
|
626
568
|
|
|
627
569
|
// Dynamic import from app's CDN
|
|
628
570
|
const CompetitionWidget = lazy(() =>
|
|
629
|
-
import('https://competition-app.example.com/widgets.
|
|
571
|
+
import('https://competition-app.example.com/widgets.esm.js')
|
|
630
572
|
.then(m => ({ default: m.CompetitionWidget }))
|
|
631
573
|
);
|
|
632
574
|
|
|
@@ -670,7 +612,7 @@ import { WidgetWrapper, CompetitionWidget } from 'competition-app/widgets';
|
|
|
670
612
|
import { WIDGET_MANIFEST } from 'competition-app/widgets';
|
|
671
613
|
|
|
672
614
|
// Verify React version compatibility
|
|
673
|
-
if (!WIDGET_MANIFEST.reactVersion.startsWith('
|
|
615
|
+
if (!WIDGET_MANIFEST.reactVersion.startsWith('19')) {
|
|
674
616
|
console.warn('Widget React version mismatch');
|
|
675
617
|
}
|
|
676
618
|
|
|
@@ -741,7 +683,7 @@ Each app exports a `WIDGET_MANIFEST` for discovery:
|
|
|
741
683
|
```typescript
|
|
742
684
|
export const WIDGET_MANIFEST = {
|
|
743
685
|
version: '1.0.0', // Widget bundle version
|
|
744
|
-
reactVersion: '
|
|
686
|
+
reactVersion: '19.x', // Required React version
|
|
745
687
|
widgets: [
|
|
746
688
|
{
|
|
747
689
|
name: 'ExampleWidget',
|
package/dist/http.d.ts
CHANGED
|
@@ -168,22 +168,38 @@ export declare function configureSdkCache(options: {
|
|
|
168
168
|
serveStaleOnOffline?: boolean;
|
|
169
169
|
clearOnPageLoad?: boolean;
|
|
170
170
|
}): void;
|
|
171
|
+
/** Options for {@link invalidateCache}. */
|
|
172
|
+
export interface InvalidateCacheOptions {
|
|
173
|
+
/**
|
|
174
|
+
* Match the path **exactly** (ignoring any query string) instead of the default
|
|
175
|
+
* substring match. Use this to clear one resource without wiping everything
|
|
176
|
+
* nested under it — e.g. `invalidateCache('/collection/abc', { exact: true })`
|
|
177
|
+
* drops only that entry, not `/collection/abc/settings|widgets|products|proofs`.
|
|
178
|
+
*/
|
|
179
|
+
exact?: boolean;
|
|
180
|
+
}
|
|
171
181
|
/**
|
|
172
182
|
* Manually invalidate entries in the SDK's GET cache.
|
|
173
183
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
184
|
+
* Note: the GET cache is **in-memory, per page load** (with an optional L2
|
|
185
|
+
* IndexedDB layer when persistence is enabled) — it does not persist across
|
|
186
|
+
* reloads unless you opt into persistence, so it rarely needs disabling "for
|
|
187
|
+
* correctness".
|
|
188
|
+
*
|
|
189
|
+
* @param urlPattern - Substring match by default (every entry whose key
|
|
190
|
+
* *contains* this string is removed). With `{ exact: true }`, matches the path
|
|
191
|
+
* precisely. Omit to wipe the entire cache.
|
|
192
|
+
* @param options - See {@link InvalidateCacheOptions}.
|
|
177
193
|
*
|
|
178
194
|
* @example
|
|
179
195
|
* ```ts
|
|
180
|
-
* invalidateCache()
|
|
181
|
-
* invalidateCache('/collection/abc123')
|
|
182
|
-
* invalidateCache('/
|
|
183
|
-
* invalidateCache('/products/')
|
|
196
|
+
* invalidateCache() // clear everything
|
|
197
|
+
* invalidateCache('/collection/abc123') // that collection AND everything under it
|
|
198
|
+
* invalidateCache('/collection/abc123', { exact: true }) // ONLY that collection entry
|
|
199
|
+
* invalidateCache('/products/') // all canonical plural product responses
|
|
184
200
|
* ```
|
|
185
201
|
*/
|
|
186
|
-
export declare function invalidateCache(urlPattern?: string): void;
|
|
202
|
+
export declare function invalidateCache(urlPattern?: string, options?: InvalidateCacheOptions): void;
|
|
187
203
|
/**
|
|
188
204
|
* Upload a FormData payload via proxy with progress events using chunked postMessage.
|
|
189
205
|
* Parent is expected to implement the counterpart protocol.
|
package/dist/http.js
CHANGED
|
@@ -25,8 +25,8 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
|
25
25
|
function reject(value) { resume("throw", value); }
|
|
26
26
|
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
27
27
|
};
|
|
28
|
-
import { SmartlinksApiError, SmartlinksOfflineError } from "./types/error";
|
|
29
|
-
import { idbGet, idbSet, idbClear } from './persistentCache';
|
|
28
|
+
import { SmartlinksApiError, SmartlinksOfflineError } from "./types/error.js";
|
|
29
|
+
import { idbGet, idbSet, idbClear } from './persistentCache.js';
|
|
30
30
|
let baseURL = null;
|
|
31
31
|
let apiKey = undefined;
|
|
32
32
|
let bearerToken = undefined;
|
|
@@ -424,7 +424,7 @@ function normalizeErrorResponse(responseBody, statusCode) {
|
|
|
424
424
|
* @property {string} [options.bearerToken] - (Optional) Bearer token for AUTHORIZATION header
|
|
425
425
|
* @property {boolean} [options.proxyMode] - (Optional) Tells the API that it is running in an iframe via parent proxy
|
|
426
426
|
*/
|
|
427
|
-
import { iframe } from './iframe';
|
|
427
|
+
import { iframe } from './iframe.js';
|
|
428
428
|
export function initializeApi(options) {
|
|
429
429
|
var _a;
|
|
430
430
|
// Normalize baseURL by removing trailing slashes.
|
|
@@ -660,29 +660,47 @@ export function configureSdkCache(options) {
|
|
|
660
660
|
/**
|
|
661
661
|
* Manually invalidate entries in the SDK's GET cache.
|
|
662
662
|
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
663
|
+
* Note: the GET cache is **in-memory, per page load** (with an optional L2
|
|
664
|
+
* IndexedDB layer when persistence is enabled) — it does not persist across
|
|
665
|
+
* reloads unless you opt into persistence, so it rarely needs disabling "for
|
|
666
|
+
* correctness".
|
|
667
|
+
*
|
|
668
|
+
* @param urlPattern - Substring match by default (every entry whose key
|
|
669
|
+
* *contains* this string is removed). With `{ exact: true }`, matches the path
|
|
670
|
+
* precisely. Omit to wipe the entire cache.
|
|
671
|
+
* @param options - See {@link InvalidateCacheOptions}.
|
|
666
672
|
*
|
|
667
673
|
* @example
|
|
668
674
|
* ```ts
|
|
669
|
-
* invalidateCache()
|
|
670
|
-
* invalidateCache('/collection/abc123')
|
|
671
|
-
* invalidateCache('/
|
|
672
|
-
* invalidateCache('/products/')
|
|
675
|
+
* invalidateCache() // clear everything
|
|
676
|
+
* invalidateCache('/collection/abc123') // that collection AND everything under it
|
|
677
|
+
* invalidateCache('/collection/abc123', { exact: true }) // ONLY that collection entry
|
|
678
|
+
* invalidateCache('/products/') // all canonical plural product responses
|
|
673
679
|
* ```
|
|
674
680
|
*/
|
|
675
|
-
export function invalidateCache(urlPattern) {
|
|
681
|
+
export function invalidateCache(urlPattern, options) {
|
|
676
682
|
if (!urlPattern) {
|
|
677
683
|
clearHttpCache('invalidateCache(all)');
|
|
678
684
|
if (cachePersistence !== 'none')
|
|
679
685
|
idbClear().catch(() => { });
|
|
680
686
|
return;
|
|
681
687
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
688
|
+
if (options === null || options === void 0 ? void 0 : options.exact) {
|
|
689
|
+
const exactKey = buildCacheKey(urlPattern);
|
|
690
|
+
for (const key of httpCache.keys()) {
|
|
691
|
+
// Compare the path portion (drop query) so exact clears query variants of
|
|
692
|
+
// the same path but never sub-resources beneath it.
|
|
693
|
+
if (key.split('?')[0] === exactKey)
|
|
694
|
+
httpCache.delete(key);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
for (const key of httpCache.keys()) {
|
|
699
|
+
if (key.includes(urlPattern))
|
|
700
|
+
httpCache.delete(key);
|
|
701
|
+
}
|
|
685
702
|
}
|
|
703
|
+
// L2 (IndexedDB) sweep remains substring-based; the exact win is the in-memory L1.
|
|
686
704
|
if (cachePersistence !== 'none')
|
|
687
705
|
idbClear(urlPattern).catch(() => { });
|
|
688
706
|
}
|
package/dist/iframe.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
|
|
2
|
-
export type { IframeResponderOptions, CachedData, CollectionApp, RouteChangeMessage, SmartlinksIframeMessage, ProxyRequest, CustomProxyRequest, UploadStartMessage, UploadChunkMessage, UploadEndMessage, } from './types/iframeResponder';
|
|
1
|
+
export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder.js';
|
|
2
|
+
export type { IframeResponderOptions, CachedData, CollectionApp, RouteChangeMessage, SmartlinksIframeMessage, ProxyRequest, CustomProxyRequest, UploadStartMessage, UploadChunkMessage, UploadEndMessage, } from './types/iframeResponder.js';
|
|
3
3
|
export declare namespace iframe {
|
|
4
4
|
interface IframeResizeOptions {
|
|
5
5
|
/** Minimum ms between height postMessages (default 100). */
|
package/dist/iframe.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// These helpers are optional and safe in non-browser / Node environments.
|
|
4
4
|
// They build on the existing proxyMode infrastructure but can also be used standalone.
|
|
5
5
|
// Re-export IframeResponder for parent-side iframe communication
|
|
6
|
-
export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder';
|
|
6
|
+
export { IframeResponder, isAdminFromRoles, buildIframeSrc, } from './iframeResponder.js';
|
|
7
7
|
export var iframe;
|
|
8
8
|
(function (iframe) {
|
|
9
9
|
let autoResizeTimer;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IframeResponderOptions, CachedData } from './types/iframeResponder';
|
|
1
|
+
import type { IframeResponderOptions, CachedData } from './types/iframeResponder.js';
|
|
2
2
|
/**
|
|
3
3
|
* Parent-side iframe responder for SmartLinks microapp embedding.
|
|
4
4
|
*
|
|
@@ -30,6 +30,12 @@ export declare class IframeResponder {
|
|
|
30
30
|
private iframe;
|
|
31
31
|
private options;
|
|
32
32
|
private cache;
|
|
33
|
+
/**
|
|
34
|
+
* Timestamp of the last anonymous (`401`) `/account` result, so a burst of
|
|
35
|
+
* "am I logged in?" checks on one page load doesn't re-hit the API 3–4 times.
|
|
36
|
+
* Only used while `cache.user` is unset; cleared the moment a login lands.
|
|
37
|
+
*/
|
|
38
|
+
private lastAnonAccountAt;
|
|
33
39
|
private uploads;
|
|
34
40
|
private activeStreams;
|
|
35
41
|
private isInitialLoad;
|
package/dist/iframeResponder.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
2
|
// IframeResponder - Parent-side iframe communication handler
|
|
3
3
|
// =============================================================================
|
|
4
|
-
import * as cache from './cache';
|
|
5
|
-
import { collection } from './api/collection';
|
|
6
|
-
import { getBaseURL } from './http';
|
|
4
|
+
import * as cache from './cache.js';
|
|
5
|
+
import { collection } from './api/collection.js';
|
|
6
|
+
import { getBaseURL, hasAuthCredentials } from './http.js';
|
|
7
7
|
/**
|
|
8
8
|
* Parent-side iframe responder for SmartLinks microapp embedding.
|
|
9
9
|
*
|
|
@@ -34,6 +34,12 @@ import { getBaseURL } from './http';
|
|
|
34
34
|
export class IframeResponder {
|
|
35
35
|
constructor(options) {
|
|
36
36
|
this.iframe = null;
|
|
37
|
+
/**
|
|
38
|
+
* Timestamp of the last anonymous (`401`) `/account` result, so a burst of
|
|
39
|
+
* "am I logged in?" checks on one page load doesn't re-hit the API 3–4 times.
|
|
40
|
+
* Only used while `cache.user` is unset; cleared the moment a login lands.
|
|
41
|
+
*/
|
|
42
|
+
this.lastAnonAccountAt = 0;
|
|
37
43
|
this.uploads = new Map();
|
|
38
44
|
this.activeStreams = new Map();
|
|
39
45
|
this.isInitialLoad = true;
|
|
@@ -346,8 +352,10 @@ export class IframeResponder {
|
|
|
346
352
|
// TODO: Validate token using SDK auth utilities when available
|
|
347
353
|
// await auth.verifyToken(token);
|
|
348
354
|
await this.options.onAuthLogin(token, user, accountData);
|
|
349
|
-
// Update cache with new user
|
|
355
|
+
// Update cache with new user; drop any stale "anonymous" account marker
|
|
356
|
+
// so the next check reflects the fresh login immediately.
|
|
350
357
|
this.cache.user = user;
|
|
358
|
+
this.lastAnonAccountAt = 0;
|
|
351
359
|
this.sendResponse(event, {
|
|
352
360
|
type: 'smartlinks:authkit:login-acknowledged',
|
|
353
361
|
messageId,
|
|
@@ -414,6 +422,35 @@ export class IframeResponder {
|
|
|
414
422
|
return;
|
|
415
423
|
}
|
|
416
424
|
}
|
|
425
|
+
// "Am I logged in?" (GET /account) with no logged-in user cached. The positive
|
|
426
|
+
// path above already served logged-in users (cache.user set).
|
|
427
|
+
if (proxyData.method === 'GET' && path.includes('/account') && !this.cache.user) {
|
|
428
|
+
const notAuthenticated = () => {
|
|
429
|
+
response.statusCode = 401;
|
|
430
|
+
response.error = 'Not authenticated';
|
|
431
|
+
response.errorBody = { account: null, errorCode: 'NOT_AUTHENTICATED' };
|
|
432
|
+
this.sendResponse(event, response);
|
|
433
|
+
};
|
|
434
|
+
// Pre-emptive gate: will this request carry ANY credential the server could
|
|
435
|
+
// validate? The portal authenticates by bearer token (not cookie), and the
|
|
436
|
+
// proxy fetch forwards no cookie, so if neither the parent SDK nor the
|
|
437
|
+
// forwarded headers carry a token, the call is provably pointless — answer
|
|
438
|
+
// "not authenticated" locally, zero network calls.
|
|
439
|
+
const headers = (proxyData.headers || {});
|
|
440
|
+
const hasHeaderAuth = Object.keys(headers).some((k) => k.toLowerCase() === 'authorization' && !!headers[k]);
|
|
441
|
+
if (!hasAuthCredentials() && !hasHeaderAuth) {
|
|
442
|
+
notAuthenticated();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
// A credential IS present but no user is cached (e.g. an expired token):
|
|
446
|
+
// the first check goes to the network, then its 401 is remembered briefly
|
|
447
|
+
// so repeat checks in the same page load don't re-hit the API.
|
|
448
|
+
const ANON_ACCOUNT_TTL_MS = 30000;
|
|
449
|
+
if (this.lastAnonAccountAt && Date.now() - this.lastAnonAccountAt < ANON_ACCOUNT_TTL_MS) {
|
|
450
|
+
notAuthenticated();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
417
454
|
// Forward to actual API using SDK's configured baseURL
|
|
418
455
|
const baseUrl = getBaseURL();
|
|
419
456
|
if (!baseUrl) {
|
|
@@ -434,6 +471,10 @@ export class IframeResponder {
|
|
|
434
471
|
response.error = (responseData === null || responseData === void 0 ? void 0 : responseData.message) || (responseData === null || responseData === void 0 ? void 0 : responseData.errorText) || `Request failed with status ${fetchResponse.status}`;
|
|
435
472
|
response.statusCode = fetchResponse.status;
|
|
436
473
|
response.errorBody = responseData;
|
|
474
|
+
// Remember an anonymous /account result so repeat checks skip the network.
|
|
475
|
+
if (fetchResponse.status === 401 && path.includes('/account') && !this.cache.user) {
|
|
476
|
+
this.lastAnonAccountAt = Date.now();
|
|
477
|
+
}
|
|
437
478
|
}
|
|
438
479
|
else {
|
|
439
480
|
response.data = responseData;
|