@proveanything/smartlinks 1.11.2 → 1.11.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.
- package/dist/docs/API_SUMMARY.md +1 -1
- package/dist/docs/mobile-admin-container.md +57 -15
- package/dist/docs/native-facade.md +170 -0
- package/dist/index.d.ts +4 -1
- package/dist/mobile-admin/errors.d.ts +8 -7
- package/dist/mobile-admin/types.d.ts +55 -9
- package/dist/native/types.d.ts +228 -0
- package/dist/native/types.js +15 -0
- package/docs/API_SUMMARY.md +1 -1
- package/docs/mobile-admin-container.md +57 -15
- package/docs/native-facade.md +170 -0
- package/package.json +1 -1
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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
|
|
|
7
|
-
> **See also:** [containers.md](containers.md) covers the public consumer container. The [Multiple Consumer Components](containers.md#multiple-consumer-components) section explains the consumer vs. admin bundle split.
|
|
7
|
+
> **See also:** [containers.md](containers.md) covers the public consumer container. The [Multiple Consumer Components](containers.md#multiple-consumer-components) section explains the consumer vs. admin bundle split. For the full `host.native` facade contract (share, clipboard, haptics, NFC, RFID, storage, etc.) see [native-facade.md](native-facade.md).
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -23,6 +23,7 @@ This document describes how to build a **Mobile Admin Container** — a SmartLin
|
|
|
23
23
|
11. [Build & Bundle Requirements](#build--bundle-requirements)
|
|
24
24
|
12. [Example: Minimal Mobile Admin Container](#example-minimal-mobile-admin-container)
|
|
25
25
|
13. [Best Practices](#best-practices)
|
|
26
|
+
14. [Native Capability Facade](#native-capability-facade)
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
@@ -58,7 +59,7 @@ Your container **never** detects the host directly. It receives a `host` prop fr
|
|
|
58
59
|
|
|
59
60
|
Every container mounted by the SmartLinks Mobile launcher receives a single `host` prop — `AdminMobileHostContext`. Do not reach for `window.SmartlinksScanner` or `window.Capacitor` directly; both are wrapped here.
|
|
60
61
|
|
|
61
|
-
> **SDK export** — `AdminMobileHostContext`, `AdminMobileCapability`, `AdminMobileHostId`, `AdminMobileEvent`, `
|
|
62
|
+
> **SDK export** — `AdminMobileHostContext`, `AdminMobileCapability`, `ActionableCapability`, `AdminMobileHostId`, `AdminMobileEvent`, `AdminMobileEventCallback`, `AdminMobileEventSubscriber`, `AdminMobileComponentManifest`, `AdminMobileBundleManifest`, and `NativeFacade` (plus all sub-facade interfaces) are exported from `@proveanything/smartlinks`. Import via `import type { AdminMobileHostContext } from '@proveanything/smartlinks'` — no local mirror needed. `ScannerEventSubscriber`, `MobileAdminComponentManifest`, and `MobileAdminBundleManifest` still export as deprecated aliases.
|
|
62
63
|
|
|
63
64
|
```typescript
|
|
64
65
|
interface AdminMobileHostContext {
|
|
@@ -82,8 +83,8 @@ interface AdminMobileHostContext {
|
|
|
82
83
|
keyboard: boolean
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
// Unified hardware event stream
|
|
86
|
-
events: { subscribe:
|
|
86
|
+
// Unified hardware event stream — callback type: AdminMobileEventCallback
|
|
87
|
+
events: { subscribe: (cb: AdminMobileEventCallback) => () => void }
|
|
87
88
|
|
|
88
89
|
// Promise-based hardware actions — reject with a structured error when unavailable
|
|
89
90
|
actions: {
|
|
@@ -97,12 +98,12 @@ interface AdminMobileHostContext {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
// Host-provided UI helpers
|
|
101
|
+
// Host-provided UI helpers — all optional, see §3 note
|
|
101
102
|
ui: {
|
|
102
|
-
toast
|
|
103
|
-
haptic
|
|
104
|
-
setHeaderTitle
|
|
105
|
-
navigateBack
|
|
103
|
+
toast?: (opts: { title: string; description?: string; variant?: 'default' | 'destructive' }) => void
|
|
104
|
+
haptic?: (style?: 'light' | 'success' | 'error') => void
|
|
105
|
+
setHeaderTitle?: (title: string | null) => void
|
|
106
|
+
navigateBack?: () => void
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// Network & device info
|
|
@@ -112,6 +113,10 @@ interface AdminMobileHostContext {
|
|
|
112
113
|
// Informational host version — use for logging/diagnostics, not feature detection.
|
|
113
114
|
// For feature detection prefer existence checks: 'requestNfcTap' in host.actions
|
|
114
115
|
_version: number
|
|
116
|
+
|
|
117
|
+
// Full native capability facade — share, clipboard, haptics, NFC, RFID, storage, etc.
|
|
118
|
+
// Optional: not every host stub populates it. See native-facade.md.
|
|
119
|
+
native?: NativeFacade
|
|
115
120
|
}
|
|
116
121
|
```
|
|
117
122
|
|
|
@@ -143,9 +148,10 @@ if (host.hardware.nfc) {
|
|
|
143
148
|
|
|
144
149
|
### `host.ui` — native helpers vs. your own components
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
All four `host.ui` methods are **optional by design**. A container may run in the Sidekick mobile shell, a standalone PWA or browser tab, Storybook, or a screenshot harness — none of those environments is guaranteed to have a native toast system, a managed header, or a back stack. Forcing every host implementer to provide them would exclude the web and desktop use-cases the rest of the contract explicitly supports. Always guard with `?.`:
|
|
147
152
|
|
|
148
|
-
`host.ui.toast()`
|
|
153
|
+
- `host.ui.toast?.({...})` / `host.ui.haptic?.('success')` — optional native feedback. Fall back to your own `<Toaster />` when absent.
|
|
154
|
+
- `host.ui.setHeaderTitle?.('Scanning…')` / `host.ui.navigateBack?.()` — host-shell integrations with no in-container equivalent. Silently no-op when the host doesn't implement them.
|
|
149
155
|
|
|
150
156
|
**Stub pattern for testing and Storybook:**
|
|
151
157
|
|
|
@@ -222,12 +228,23 @@ The custom Kotlin shell and both Capacitor shells ship the same baseline plugin
|
|
|
222
228
|
| `@capacitor/device` | `host.device.info()` |
|
|
223
229
|
| `@capacitor/share` | `host.actions.share()` |
|
|
224
230
|
| `@capacitor/clipboard` | `host.actions.clipboard.*` |
|
|
225
|
-
| `@capacitor/preferences` | `host.storage.*` *(planned)* |
|
|
231
|
+
| `@capacitor/preferences` | `host.storage.*` *(planned — see note below)* |
|
|
226
232
|
| `@capacitor/app` | host-managed (back button, deep links) |
|
|
227
233
|
| `@capacitor/status-bar` | host-managed |
|
|
228
234
|
| `@capacitor/keyboard` | host-managed |
|
|
229
235
|
| `@capacitor/toast` | wired into `host.ui.toast()` |
|
|
230
236
|
|
|
237
|
+
> **`host.storage` — planned shape.** Once released, the surface will wrap `@capacitor/preferences` directly:
|
|
238
|
+
> ```ts
|
|
239
|
+
> host.storage: {
|
|
240
|
+
> get(key: string): Promise<string | null>
|
|
241
|
+
> set(key: string, value: string): Promise<void>
|
|
242
|
+
> remove(key: string): Promise<void>
|
|
243
|
+
> keys(): Promise<string[]>
|
|
244
|
+
> }
|
|
245
|
+
> ```
|
|
246
|
+
> Until then: `localStorage` works on web hosts; use `@capacitor/preferences` directly (bundle it in) on native.
|
|
247
|
+
|
|
231
248
|
### Tier 2 — capability-gated (declare in manifest)
|
|
232
249
|
|
|
233
250
|
| Plugin | Capability flag |
|
|
@@ -385,7 +402,9 @@ try {
|
|
|
385
402
|
| `lifecycle: 'pause'` | App backgrounded | Pause readers to save battery |
|
|
386
403
|
| `lifecycle: 'resume'` | App foregrounded | Resubscribe; refresh stale data |
|
|
387
404
|
|
|
388
|
-
Use `host.events.subscribe` for all lifecycle events —
|
|
405
|
+
Use `host.events.subscribe` for all lifecycle events — all five host types (`custom-android`, `capacitor-ios`, `capacitor-android`, `pwa`, `browser`) are guaranteed to emit every `'pause'`/`'resume'`/`'offline'`/`'online'` phase. No `window.addEventListener('online')` fallback is needed.
|
|
406
|
+
|
|
407
|
+
> **Planned** — `phase: 'mount' | 'unmount'` events are on the roadmap. These will fire when the container becomes visible / is removed from the host view stack, enabling deferred reader start-up without a `useEffect` dependency.
|
|
389
408
|
|
|
390
409
|
---
|
|
391
410
|
|
|
@@ -521,10 +540,33 @@ export const MOBILE_ADMIN_MANIFEST = {
|
|
|
521
540
|
|
|
522
541
|
- **Always check `host.hardware.X` before calling `host.actions.X`** — never assume a capability is available.
|
|
523
542
|
- **Always wrap action calls in try/catch** — handle `HostPermissionDeniedError`, `HostTimeoutError`, and `HostCapabilityUnavailableError`.
|
|
524
|
-
- **
|
|
525
|
-
- **`host.ui.
|
|
543
|
+
- **All four `host.ui` methods are optional** (`toast`, `haptic`, `setHeaderTitle`, `navigateBack`) — guard every call with `?.`. See the `host.ui` section above.
|
|
544
|
+
- **`host.ui.setHeaderTitle` and `host.ui.navigateBack`** integrate with the host shell and have no in-container equivalent; call with `?.`.
|
|
526
545
|
- **Use `host.events.subscribe` for lifecycle events** — `'offline'`/`'online'`/`'pause'`/`'resume'` fire consistently on every host.
|
|
527
546
|
- **Never call `initializeApi`, never use top-level SDK imports for API calls** — `host.SL` is already configured. `SL.method()` instead of `host.SL.method()` silently uses the wrong baseURL.
|
|
528
547
|
- **Bundle Capacitor plugins in** (do not externalise) — so the component degrades gracefully on PWA/browser without crashing.
|
|
529
548
|
- **Declare `offline: true`** on a component if it queues writes locally — this signals the launcher to provision offline sync support.
|
|
530
549
|
- **Use `'methodName' in host.actions`** to feature-detect new host capabilities rather than comparing `host._version`. The version is informational only.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Native Capability Facade
|
|
554
|
+
|
|
555
|
+
`host.native` gives containers access to a broader set of device capabilities — share sheet, clipboard, full haptics API, storage, QR, NFC, RFID (Kotlin only), auth, and cross-shell events — through a single interface that falls back gracefully across all host environments.
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
// Check host.hardware.* for physical availability, then call via host.native
|
|
559
|
+
if (host.hardware.nfc && host.native?.nfc) {
|
|
560
|
+
const { uid } = await host.native.nfc.read({ timeoutMs: 10_000 })
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Storage always available (Preferences → localStorage → in-memory Map)
|
|
564
|
+
await host.native?.storage.set('lastScan', uid)
|
|
565
|
+
|
|
566
|
+
// Share sheet with web fallback
|
|
567
|
+
await host.native?.share.share({ title: 'Found tag', url: `https://app.example/tags/${uid}` })
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
`host.native` is optional on `AdminMobileHostContext` — host stubs (Storybook, unit tests) need not implement it. `native.rfid` is additionally optional within `NativeFacade` itself, as it only exists on `custom-android`.
|
|
571
|
+
|
|
572
|
+
For the full sub-facade table, fallback chains, and what's intentionally not wrapped, see **[native-facade.md](native-facade.md)**.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Native Capability Facade (`host.native` / `SL.native`)
|
|
2
|
+
|
|
3
|
+
> **Version:** 1.12 · **Platform:** SmartLinks R4 · **Last updated:** 2026-04-30
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
**The SDK owns the contract** (`src/native/types.ts`, re-exported from `@proveanything/smartlinks`). Each host implementation is responsible for wiring up the sub-facades and populating:
|
|
8
|
+
- `AdminMobileHostContext.native` (container prop, typed in the SDK), and
|
|
9
|
+
- `window.SL.native` (for UMD microapps that don't receive a host prop — host concern, not SDK).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Access patterns](#access-patterns)
|
|
16
|
+
2. [What's in the facade](#whats-in-the-facade)
|
|
17
|
+
3. [What's NOT in the facade](#whats-not-in-the-facade)
|
|
18
|
+
4. [Error contract](#error-contract)
|
|
19
|
+
5. [Type reference](#type-reference)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Access patterns
|
|
24
|
+
|
|
25
|
+
### Inside a Mobile Admin Container (recommended)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import type { AdminMobileHostContext } from '@proveanything/smartlinks'
|
|
29
|
+
|
|
30
|
+
function MyContainer({ host }: { host: AdminMobileHostContext }) {
|
|
31
|
+
const handleShare = async () => {
|
|
32
|
+
await host.native?.share.share({ title: 'SmartLinks', url: window.location.href })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleScan = async () => {
|
|
36
|
+
// Only present when the host provides a QR reader
|
|
37
|
+
const code = await host.native?.qr.scan()
|
|
38
|
+
if (code) processCode(code)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Always call via `host.native?.<facade>.<method>()` — `native` is optional because not every host exposes the full surface (e.g. a minimal browser stub may omit `rfid`).
|
|
44
|
+
|
|
45
|
+
For hardware-gated capabilities (NFC, RFID, QR, camera) also check `host.capabilities` or `host.hardware.*` before calling, as those flags reflect physical availability on the current device:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
if (host.hardware.nfc && host.native?.nfc) {
|
|
49
|
+
const { uid } = await host.native.nfc.read({ timeoutMs: 10_000 })
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Inside a UMD microapp (no host prop)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// window.SL.native is populated by the host at boot — host concern, not SDK
|
|
57
|
+
const native = (window as any).SL?.native as import('@proveanything/smartlinks').NativeFacade | undefined
|
|
58
|
+
const code = await native?.qr.scan()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## What's in the facade
|
|
64
|
+
|
|
65
|
+
These capabilities have meaningful fallbacks across Kotlin / Capacitor / web. Always go through the facade.
|
|
66
|
+
|
|
67
|
+
| Sub-facade | Key methods | Fallback chain |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `native.share` | `share({ title, url, text? })`, `canShare()` | Capacitor Share → `navigator.share` → copy to clipboard + toast |
|
|
70
|
+
| `native.clipboard` | `write(text)`, `read()` | Capacitor Clipboard → `navigator.clipboard` → `execCommand('copy')` |
|
|
71
|
+
| `native.haptics` | `impact(style?)`, `notification(style)`, `selection()` | Capacitor Haptics → `navigator.vibrate` → no-op |
|
|
72
|
+
| `native.network` | `getStatus()`, `addListener('change', cb)` | Capacitor Network → `navigator.onLine` + `'online'`/`'offline'` events |
|
|
73
|
+
| `native.device` | `getInfo()`, `getId()`, `getLanguageCode()` | Capacitor Device → UA parsing + cached UUID in `localStorage` |
|
|
74
|
+
| `native.storage` | `get(key)`, `set(key, value)`, `remove(key)`, `keys()` | Capacitor Preferences → `localStorage` → in-memory `Map` (private mode) |
|
|
75
|
+
| `native.qr` | `scan(opts?)` | Capacitor MLKit Barcode → html5-qrcode / `BarcodeDetector` |
|
|
76
|
+
| `native.auth` | `signInWithGoogle()`, `signOut()` | Kotlin bridge → Capacitor Browser + OAuth → web redirect |
|
|
77
|
+
| `native.nfc` | `read(opts?)`, `writeNdef(opts)`, `programTag(opts)`, `lockTag(opts)`, `isLocked(opts)` | Kotlin bridge → Web NFC (`NDEFReader`) → `HostCapabilityUnavailableError` |
|
|
78
|
+
| `native.rfid` | `startScan(opts?)`, `stopScan()`, `subscribe(cb)` | Kotlin bridge only — throws `HostCapabilityUnavailableError` on all other hosts |
|
|
79
|
+
| `native.events` | `subscribe(type, cb)`, `emit(type, payload?)` | Internal pub/sub bridging Kotlin `onSmartlinksData` + Capacitor listeners |
|
|
80
|
+
| `native.webSource` | `get()`, `set({ mode, channel?, liveUrl? })` | Kotlin bridge + Capacitor Preferences mirror |
|
|
81
|
+
|
|
82
|
+
> **`native.rfid` is the only optional sub-facade** on `NativeFacade` — it is only populated on `custom-android` hosts. All other sub-facades are present on every host, though individual methods may throw `HostCapabilityUnavailableError` when the underlying capability is absent.
|
|
83
|
+
|
|
84
|
+
> **`native.device` vs `host.device`** — `host.device.info()` is a simplified convenience that returns `{ model, platform }`. `host.native?.device.getInfo()` returns the full `DeviceInfo` (adds `osVersion`, `manufacturer`, `isVirtual`) and also provides `getId()` and `getLanguageCode()`. Use `host.device` when the convenience is enough.
|
|
85
|
+
|
|
86
|
+
> **`native.network` vs `host.network`** — `host.network.isOnline()` is a boolean convenience. `host.native?.network.getStatus()` returns `{ connected, connectionType }` including `'wifi'` / `'cellular'` distinction.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## What's NOT in the facade
|
|
91
|
+
|
|
92
|
+
These capabilities intentionally have no `SL.native` wrapper. Call the Capacitor plugin directly, or use the web API. The host provides them as Tier 1 or Tier 2 plugins (see [Capacitor Plugin Baseline](mobile-admin-container.md#capacitor-plugin-baseline)).
|
|
93
|
+
|
|
94
|
+
| Capability | Why not wrapped |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `@capacitor/push-notifications` | Push only exists in Capacitor builds; no cross-shell registration flow. Import and call directly; detect Capacitor first. |
|
|
97
|
+
| `@capacitor/local-notifications` | Same — no Kotlin or web equivalent worth abstracting. |
|
|
98
|
+
| `@capacitor/camera` | Plugin already abstracts iOS/Android. PWA's `<input capture>` is too different in shape to unify. |
|
|
99
|
+
| `@capacitor/filesystem` | No web equivalent (storage quotas, sandbox rules differ). Use `URL.createObjectURL` + download anchor on web. |
|
|
100
|
+
| `@capacitor/geolocation` | `navigator.geolocation` already works in WebViews; the Capacitor plugin wraps the same OS APIs. No facade adds value. |
|
|
101
|
+
| `@capacitor/keyboard`, `@capacitor/screen-*`, `@capacitor/splash-screen`, `@capacitor/status-bar`, `@capacitor/dialog`, `@capacitor/app` | Per-host boot-time config or platform-chrome concerns; not part of the microapp contract. |
|
|
102
|
+
| `@capacitor/browser` | In-app browser for OAuth is intentionally Capacitor-only. For opening external URLs use `native.share.share({ url })`. |
|
|
103
|
+
| `@capawesome/capacitor-file-picker` | Picker UX too divergent across web (`<input type="file">`) and native to wrap usefully. |
|
|
104
|
+
|
|
105
|
+
If a capability later needs consistent behaviour across shells (e.g. notifications reach enough hosts to warrant throttling / routing via the facade), promote it into `NativeFacade` by adding an interface to `src/native/types.ts` and a row to the table above.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Error contract
|
|
110
|
+
|
|
111
|
+
All facade methods reject with one of the three structured errors from `@proveanything/smartlinks`. A single catch block handles every capability and host combination:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import {
|
|
115
|
+
HostCapabilityUnavailableError,
|
|
116
|
+
HostPermissionDeniedError,
|
|
117
|
+
HostTimeoutError,
|
|
118
|
+
} from '@proveanything/smartlinks'
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const { uid } = await host.native?.nfc.read({ timeoutMs: 10_000 }) ?? {}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (err instanceof HostCapabilityUnavailableError) {
|
|
124
|
+
// host.capabilities won't list 'nfc' — we should have checked first
|
|
125
|
+
host.ui.toast?.({ title: 'NFC not available on this device', variant: 'destructive' })
|
|
126
|
+
} else if (err instanceof HostPermissionDeniedError) {
|
|
127
|
+
host.ui.toast?.({ title: 'NFC permission denied', variant: 'destructive' })
|
|
128
|
+
} else if (err instanceof HostTimeoutError) {
|
|
129
|
+
host.ui.toast?.({ title: `No tag detected after ${err.timeoutMs / 1000}s` })
|
|
130
|
+
} else {
|
|
131
|
+
throw err
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Type reference
|
|
139
|
+
|
|
140
|
+
All types below are exported from `@proveanything/smartlinks`:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import type {
|
|
144
|
+
NativeFacade, // root — use on AdminMobileHostContext.native
|
|
145
|
+
NativeCapability, // union of sub-facade keys: 'share' | 'clipboard' | ...
|
|
146
|
+
ShareFacade,
|
|
147
|
+
ClipboardFacade,
|
|
148
|
+
HapticsFacade,
|
|
149
|
+
HapticImpactStyle, // 'light' | 'medium' | 'heavy'
|
|
150
|
+
HapticNotificationStyle,
|
|
151
|
+
NetworkFacade,
|
|
152
|
+
NetworkStatus,
|
|
153
|
+
DeviceFacade,
|
|
154
|
+
DeviceInfo,
|
|
155
|
+
StorageFacade,
|
|
156
|
+
QrFacade,
|
|
157
|
+
QrScanOptions,
|
|
158
|
+
AuthFacade,
|
|
159
|
+
NfcFacade,
|
|
160
|
+
NfcReadResult,
|
|
161
|
+
RfidFacade,
|
|
162
|
+
RfidScanOptions,
|
|
163
|
+
EventsFacade,
|
|
164
|
+
WebSourceFacade,
|
|
165
|
+
WebSourceMode,
|
|
166
|
+
WebSourceConfig,
|
|
167
|
+
} from '@proveanything/smartlinks'
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The facade interfaces are contract-only — no runtime implementation is shipped in the SDK. You only need them for TypeScript type-checking (authoring containers or host stubs).
|
package/dist/index.d.ts
CHANGED
|
@@ -23,5 +23,8 @@ export type { Collection, CollectionResponse, CollectionCreateRequest, Collectio
|
|
|
23
23
|
export type { Proof, ProofResponse, ProofCreateRequest, ProofUpdateRequest, ProofClaimRequest, } from "./types/proof";
|
|
24
24
|
export type { QrShortCodeLookupResponse, } from "./types/qr";
|
|
25
25
|
export type { ReverseTagLookupParams, ReverseTagLookupResponse, } from "./types/tags";
|
|
26
|
-
export type { AdminMobileCapability, AdminMobileHostId, AdminMobileEvent,
|
|
26
|
+
export type { AdminMobileCapability, ActionableCapability, AdminMobileHostId, AdminMobileEvent, AdminMobileEventCallback, AdminMobileEventSubscriber, ScannerEventSubscriber, // @deprecated — use AdminMobileEventCallback
|
|
27
|
+
AdminMobileHostContext, AdminMobileComponentManifest, AdminMobileBundleManifest, MobileAdminComponentManifest, // @deprecated — use AdminMobileComponentManifest
|
|
28
|
+
MobileAdminBundleManifest, } from './mobile-admin/types';
|
|
27
29
|
export { HostCapabilityUnavailableError, HostPermissionDeniedError, HostTimeoutError, } from './mobile-admin/errors';
|
|
30
|
+
export type { NativeCapability, NativeFacade, ShareFacade, ClipboardFacade, HapticImpactStyle, HapticNotificationStyle, HapticsFacade, NetworkStatus, NetworkFacade, DeviceInfo, DeviceFacade, StorageFacade, QrScanOptions, QrFacade, AuthFacade, NfcReadResult, NfcFacade, RfidScanOptions, RfidFacade, EventsFacade, WebSourceMode, WebSourceConfig, WebSourceFacade, } from './native/types';
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* All three classes call `Object.setPrototypeOf(this, new.target.prototype)` so
|
|
6
6
|
* `instanceof` works correctly when transpiled to ES5.
|
|
7
7
|
*/
|
|
8
|
+
import type { ActionableCapability, AdminMobileHostId } from './types';
|
|
8
9
|
/**
|
|
9
10
|
* Thrown when a container requests a hardware action that the current host
|
|
10
11
|
* does not support (e.g. calling `requestNfcTap` on a `'pwa'` host).
|
|
@@ -20,10 +21,10 @@
|
|
|
20
21
|
*/
|
|
21
22
|
export declare class HostCapabilityUnavailableError extends Error {
|
|
22
23
|
/** The capability that was requested but is unavailable. */
|
|
23
|
-
capability:
|
|
24
|
-
/**
|
|
25
|
-
host:
|
|
26
|
-
constructor(capability:
|
|
24
|
+
capability: ActionableCapability;
|
|
25
|
+
/** The host on which the capability is unavailable. */
|
|
26
|
+
host: AdminMobileHostId;
|
|
27
|
+
constructor(capability: ActionableCapability, host: AdminMobileHostId);
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Thrown when the user denies a runtime permission request (e.g. camera or
|
|
@@ -40,8 +41,8 @@ export declare class HostCapabilityUnavailableError extends Error {
|
|
|
40
41
|
*/
|
|
41
42
|
export declare class HostPermissionDeniedError extends Error {
|
|
42
43
|
/** The capability for which permission was denied. */
|
|
43
|
-
capability:
|
|
44
|
-
constructor(capability:
|
|
44
|
+
capability: ActionableCapability;
|
|
45
|
+
constructor(capability: ActionableCapability);
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
48
|
* Thrown when a time-bounded host action (NFC tap, QR scan) exceeds its
|
|
@@ -58,7 +59,7 @@ export declare class HostPermissionDeniedError extends Error {
|
|
|
58
59
|
*/
|
|
59
60
|
export declare class HostTimeoutError extends Error {
|
|
60
61
|
/** The capability that timed out. */
|
|
61
|
-
capability: 'nfc' | 'qr'
|
|
62
|
+
capability: Extract<ActionableCapability, 'nfc' | 'qr' | 'geolocation'>;
|
|
62
63
|
/** The timeout threshold in milliseconds. */
|
|
63
64
|
timeoutMs: number;
|
|
64
65
|
constructor(capability: HostTimeoutError['capability'], timeoutMs: number);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import type { NativeFacade } from '../native/types';
|
|
1
2
|
/**
|
|
2
3
|
* Hardware/software capability tokens a mobile admin host may advertise.
|
|
3
4
|
* Passed to `AdminMobileHostContext.capabilities` and to
|
|
4
|
-
* `
|
|
5
|
+
* `AdminMobileComponentManifest.capabilities`.
|
|
5
6
|
*/
|
|
6
7
|
export type AdminMobileCapability = 'nfc' | 'nfc-advanced' | 'rfid' | 'qr' | 'camera' | 'keyboard' | 'geolocation' | 'push';
|
|
8
|
+
/**
|
|
9
|
+
* Subset of `AdminMobileCapability` that can be the subject of a structured
|
|
10
|
+
* error. `'keyboard'` is excluded — it is a passive event source with no
|
|
11
|
+
* request method that can fail.
|
|
12
|
+
*/
|
|
13
|
+
export type ActionableCapability = Exclude<AdminMobileCapability, 'keyboard'>;
|
|
7
14
|
/**
|
|
8
15
|
* Canonical identifiers for mobile admin host environments.
|
|
9
16
|
* Use for display / diagnostics only — feature-detect at runtime via
|
|
@@ -31,8 +38,19 @@ export type AdminMobileEvent = {
|
|
|
31
38
|
type: 'lifecycle';
|
|
32
39
|
phase: 'pause' | 'resume' | 'offline' | 'online';
|
|
33
40
|
};
|
|
34
|
-
/** Callback
|
|
35
|
-
export type
|
|
41
|
+
/** Callback invoked for every hardware event emitted by the host. */
|
|
42
|
+
export type AdminMobileEventCallback = (event: AdminMobileEvent) => void;
|
|
43
|
+
/**
|
|
44
|
+
* The full type of `AdminMobileHostContext.events.subscribe` —
|
|
45
|
+
* takes a callback and returns a cleanup function.
|
|
46
|
+
*/
|
|
47
|
+
export type AdminMobileEventSubscriber = (cb: AdminMobileEventCallback) => () => void;
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated Renamed to `AdminMobileEventCallback` in 1.12.
|
|
50
|
+
* Will be removed in a future minor release.
|
|
51
|
+
* @see AdminMobileEventCallback
|
|
52
|
+
*/
|
|
53
|
+
export type ScannerEventSubscriber = AdminMobileEventCallback;
|
|
36
54
|
/**
|
|
37
55
|
* The `host` prop passed to every mobile admin container component.
|
|
38
56
|
*
|
|
@@ -82,7 +100,7 @@ export interface AdminMobileHostContext {
|
|
|
82
100
|
* @param cb - Called for every incoming `AdminMobileEvent`.
|
|
83
101
|
* @returns Cleanup function — call inside `useEffect` return.
|
|
84
102
|
*/
|
|
85
|
-
subscribe: (cb:
|
|
103
|
+
subscribe: (cb: AdminMobileEventCallback) => () => void;
|
|
86
104
|
};
|
|
87
105
|
/** Imperative hardware actions. */
|
|
88
106
|
actions: {
|
|
@@ -137,8 +155,16 @@ export interface AdminMobileHostContext {
|
|
|
137
155
|
}) => void;
|
|
138
156
|
/** Trigger a haptic pulse. Optional — see interface note. */
|
|
139
157
|
haptic?: (style?: 'light' | 'success' | 'error') => void;
|
|
140
|
-
|
|
141
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Optional — host shell may not provide a managed header
|
|
160
|
+
* (browser tabs, Storybook, desktop views). Guard with `?.`.
|
|
161
|
+
*/
|
|
162
|
+
setHeaderTitle?: (title: string | null) => void;
|
|
163
|
+
/**
|
|
164
|
+
* Optional — host shell may not have a native back stack
|
|
165
|
+
* (browser tabs, Storybook, desktop views). Guard with `?.`.
|
|
166
|
+
*/
|
|
167
|
+
navigateBack?: () => void;
|
|
142
168
|
};
|
|
143
169
|
/** Network connectivity helpers. */
|
|
144
170
|
network: {
|
|
@@ -160,9 +186,19 @@ export interface AdminMobileHostContext {
|
|
|
160
186
|
* if ('requestNfcTap' in host.actions) { ... }
|
|
161
187
|
*/
|
|
162
188
|
_version: number;
|
|
189
|
+
/**
|
|
190
|
+
* Full native capability facade populated by the host at mount time.
|
|
191
|
+
* Not all sub-facades are present on every host — check before calling:
|
|
192
|
+
* @example
|
|
193
|
+
* const code = await host.native?.qr.scan();
|
|
194
|
+
* const { identifier } = await host.native?.device.getId() ?? {};
|
|
195
|
+
* For UMD microapps that do not receive a `host` prop, the same object
|
|
196
|
+
* is available at `window.SL.native` (host concern, not SDK).
|
|
197
|
+
*/
|
|
198
|
+
native?: NativeFacade;
|
|
163
199
|
}
|
|
164
200
|
/** Manifest metadata for a single mobile admin container component. */
|
|
165
|
-
export interface
|
|
201
|
+
export interface AdminMobileComponentManifest {
|
|
166
202
|
/** Component export name (matches the export in the bundle). */
|
|
167
203
|
name: string;
|
|
168
204
|
/** Human-readable description shown in the host launcher UI. */
|
|
@@ -184,7 +220,7 @@ export interface MobileAdminComponentManifest {
|
|
|
184
220
|
* Shape of the `mobileAdmin` key inside `app.manifest.json`.
|
|
185
221
|
* Describes the bundle files and the components it exports.
|
|
186
222
|
*/
|
|
187
|
-
export interface
|
|
223
|
+
export interface AdminMobileBundleManifest {
|
|
188
224
|
files: {
|
|
189
225
|
js: {
|
|
190
226
|
/** UMD bundle path (relative to dist root). Used by the custom-android host. */
|
|
@@ -195,5 +231,15 @@ export interface MobileAdminBundleManifest {
|
|
|
195
231
|
/** CSS bundle path, or `null` if the component ships no styles. */
|
|
196
232
|
css: string | null;
|
|
197
233
|
};
|
|
198
|
-
components:
|
|
234
|
+
components: AdminMobileComponentManifest[];
|
|
199
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* @deprecated Renamed to `AdminMobileComponentManifest` in 1.12.
|
|
238
|
+
* @see AdminMobileComponentManifest
|
|
239
|
+
*/
|
|
240
|
+
export type MobileAdminComponentManifest = AdminMobileComponentManifest;
|
|
241
|
+
/**
|
|
242
|
+
* @deprecated Renamed to `AdminMobileBundleManifest` in 1.12.
|
|
243
|
+
* @see AdminMobileBundleManifest
|
|
244
|
+
*/
|
|
245
|
+
export type MobileAdminBundleManifest = AdminMobileBundleManifest;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract types for the `SL.native` / `host.native` facade.
|
|
3
|
+
*
|
|
4
|
+
* These are **interface-only** — no runtime implementation lives here.
|
|
5
|
+
* The facade is implemented per-host (Sidekick, Capacitor builds, PWA) and
|
|
6
|
+
* exposed to containers via `AdminMobileHostContext.native` or (for UMD
|
|
7
|
+
* microapps) via `window.SL.native`. The SDK owns the shape; the host owns
|
|
8
|
+
* the wiring.
|
|
9
|
+
*
|
|
10
|
+
* All facade methods reject with one of the structured errors from
|
|
11
|
+
* `@proveanything/smartlinks` (`HostCapabilityUnavailableError`,
|
|
12
|
+
* `HostPermissionDeniedError`, `HostTimeoutError`) so a single catch handler
|
|
13
|
+
* covers every host and capability.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Keys of the `NativeFacade` interface — one token per sub-facade.
|
|
17
|
+
* Use `hasCapability(key)` on the host to check availability before calling.
|
|
18
|
+
*/
|
|
19
|
+
export type NativeCapability = 'share' | 'clipboard' | 'haptics' | 'network' | 'device' | 'storage' | 'qr' | 'auth' | 'nfc' | 'rfid' | 'events' | 'webSource';
|
|
20
|
+
/** Share sheet / URL sharing. */
|
|
21
|
+
export interface ShareFacade {
|
|
22
|
+
/**
|
|
23
|
+
* Trigger the native share sheet.
|
|
24
|
+
* Falls back to `navigator.share`, then silently copies `url` to clipboard.
|
|
25
|
+
*/
|
|
26
|
+
share(payload: {
|
|
27
|
+
title: string;
|
|
28
|
+
url: string;
|
|
29
|
+
text?: string;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
/** Returns `true` when a share sheet is available on this host. */
|
|
32
|
+
canShare(): Promise<boolean>;
|
|
33
|
+
}
|
|
34
|
+
/** Clipboard read / write. */
|
|
35
|
+
export interface ClipboardFacade {
|
|
36
|
+
/** Write plain text to the clipboard. */
|
|
37
|
+
write(text: string): Promise<void>;
|
|
38
|
+
/** Read plain text from the clipboard. Returns `''` if empty or denied. */
|
|
39
|
+
read(): Promise<string>;
|
|
40
|
+
}
|
|
41
|
+
/** Haptic feedback. */
|
|
42
|
+
export type HapticImpactStyle = 'light' | 'medium' | 'heavy';
|
|
43
|
+
export type HapticNotificationStyle = 'success' | 'warning' | 'error';
|
|
44
|
+
export interface HapticsFacade {
|
|
45
|
+
/** Physical impact pulse. Falls back to `navigator.vibrate`. */
|
|
46
|
+
impact(style?: HapticImpactStyle): Promise<void>;
|
|
47
|
+
/** Notification-style feedback. Falls back to `navigator.vibrate`. */
|
|
48
|
+
notification(style: HapticNotificationStyle): Promise<void>;
|
|
49
|
+
/** Selection-changed feedback. No-op when unsupported. */
|
|
50
|
+
selection(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/** Network status. */
|
|
53
|
+
export interface NetworkStatus {
|
|
54
|
+
connected: boolean;
|
|
55
|
+
connectionType: 'wifi' | 'cellular' | 'none' | 'unknown';
|
|
56
|
+
}
|
|
57
|
+
export interface NetworkFacade {
|
|
58
|
+
/** Returns current connection status. */
|
|
59
|
+
getStatus(): Promise<NetworkStatus>;
|
|
60
|
+
/**
|
|
61
|
+
* Subscribe to connectivity changes. Returns an unsubscribe function.
|
|
62
|
+
* Falls back to `window` `'online'`/`'offline'` events on web.
|
|
63
|
+
*/
|
|
64
|
+
addListener(event: 'change', cb: (status: NetworkStatus) => void): () => void;
|
|
65
|
+
}
|
|
66
|
+
/** Device metadata. */
|
|
67
|
+
export interface DeviceInfo {
|
|
68
|
+
model: string;
|
|
69
|
+
platform: 'ios' | 'android' | 'web';
|
|
70
|
+
osVersion: string;
|
|
71
|
+
manufacturer: string;
|
|
72
|
+
isVirtual: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface DeviceFacade {
|
|
75
|
+
/** Hardware and OS metadata. */
|
|
76
|
+
getInfo(): Promise<DeviceInfo>;
|
|
77
|
+
/**
|
|
78
|
+
* Stable device identifier — UUID persisted in `@capacitor/preferences`
|
|
79
|
+
* (native) or `localStorage` (web).
|
|
80
|
+
*/
|
|
81
|
+
getId(): Promise<{
|
|
82
|
+
identifier: string;
|
|
83
|
+
}>;
|
|
84
|
+
/** Current locale language code, e.g. `'en'`, `'fr'`. */
|
|
85
|
+
getLanguageCode(): Promise<{
|
|
86
|
+
value: string;
|
|
87
|
+
}>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Persistent key-value storage.
|
|
91
|
+
* Backed by `@capacitor/preferences` on native; `localStorage` on web;
|
|
92
|
+
* in-memory `Map` in private-browsing / third-party-cookie contexts.
|
|
93
|
+
*/
|
|
94
|
+
export interface StorageFacade {
|
|
95
|
+
get(key: string): Promise<string | null>;
|
|
96
|
+
set(key: string, value: string): Promise<void>;
|
|
97
|
+
remove(key: string): Promise<void>;
|
|
98
|
+
keys(): Promise<string[]>;
|
|
99
|
+
}
|
|
100
|
+
/** QR / barcode scan. */
|
|
101
|
+
export interface QrScanOptions {
|
|
102
|
+
/** BarcodeFormat strings to restrict scanning (e.g. `['QR_CODE', 'EAN_13']`). */
|
|
103
|
+
formats?: string[];
|
|
104
|
+
}
|
|
105
|
+
export interface QrFacade {
|
|
106
|
+
/**
|
|
107
|
+
* Open the barcode scanner UI and resolve with the decoded string.
|
|
108
|
+
* Uses Capacitor MLKit on native; html5-qrcode (or `BarcodeDetector`) on web.
|
|
109
|
+
* Throws `HostTimeoutError` when the user dismisses without scanning.
|
|
110
|
+
*/
|
|
111
|
+
scan(opts?: QrScanOptions): Promise<string>;
|
|
112
|
+
}
|
|
113
|
+
/** Authentication. */
|
|
114
|
+
export interface AuthFacade {
|
|
115
|
+
/**
|
|
116
|
+
* Initiate Google Sign-In. Returns the raw ID token for exchange with the
|
|
117
|
+
* SmartLinks auth backend. Falls back to an OAuth redirect flow on web.
|
|
118
|
+
*/
|
|
119
|
+
signInWithGoogle(): Promise<{
|
|
120
|
+
idToken: string;
|
|
121
|
+
}>;
|
|
122
|
+
signOut(): Promise<void>;
|
|
123
|
+
}
|
|
124
|
+
/** NFC tag operations. */
|
|
125
|
+
export interface NfcReadResult {
|
|
126
|
+
uid: string;
|
|
127
|
+
/** Raw NDEF payload (hex or base64 depending on host), when present. */
|
|
128
|
+
ndef?: string;
|
|
129
|
+
}
|
|
130
|
+
export interface NfcFacade {
|
|
131
|
+
/**
|
|
132
|
+
* Wait for the next NFC tap and return the tag's UID + optional NDEF.
|
|
133
|
+
* Throws `HostTimeoutError` after `opts.timeoutMs` (default: 30 s).
|
|
134
|
+
*/
|
|
135
|
+
read(opts?: {
|
|
136
|
+
timeoutMs?: number;
|
|
137
|
+
}): Promise<NfcReadResult>;
|
|
138
|
+
/** Write an NDEF record to a tag by UID. */
|
|
139
|
+
writeNdef(opts: {
|
|
140
|
+
uid: string;
|
|
141
|
+
payload: string;
|
|
142
|
+
}): Promise<void>;
|
|
143
|
+
/** Program an NTAG 21x / 424 profile. Host-specific; Kotlin only on advanced ops. */
|
|
144
|
+
programTag(opts: {
|
|
145
|
+
uid: string;
|
|
146
|
+
profile: string;
|
|
147
|
+
[key: string]: unknown;
|
|
148
|
+
}): Promise<void>;
|
|
149
|
+
/** Lock a tag against further writes. */
|
|
150
|
+
lockTag(opts: {
|
|
151
|
+
uid: string;
|
|
152
|
+
}): Promise<void>;
|
|
153
|
+
/** Returns whether the tag is currently locked. */
|
|
154
|
+
isLocked(opts: {
|
|
155
|
+
uid: string;
|
|
156
|
+
}): Promise<boolean>;
|
|
157
|
+
}
|
|
158
|
+
/** RFID mass-scan (UHF). Only available on `custom-android`. */
|
|
159
|
+
export interface RfidScanOptions {
|
|
160
|
+
/** Transmission power in dBm. */
|
|
161
|
+
power?: number;
|
|
162
|
+
/** Gen2 session flag (0–3). */
|
|
163
|
+
sessionFlag?: number;
|
|
164
|
+
}
|
|
165
|
+
export interface RfidFacade {
|
|
166
|
+
/** Start the RFID reader. Throws `HostCapabilityUnavailableError` on non-Kotlin hosts. */
|
|
167
|
+
startScan(opts?: RfidScanOptions): Promise<void>;
|
|
168
|
+
stopScan(): Promise<void>;
|
|
169
|
+
/** Subscribe to EPC bursts. Returns an unsubscribe function. */
|
|
170
|
+
subscribe(cb: (epcs: string[]) => void): () => void;
|
|
171
|
+
}
|
|
172
|
+
/** Cross-shell event bus. */
|
|
173
|
+
export interface EventsFacade {
|
|
174
|
+
/**
|
|
175
|
+
* Subscribe to named events from any of: the Kotlin bridge
|
|
176
|
+
* (`onSmartlinksData`), Capacitor plugin listeners, or internal pub/sub.
|
|
177
|
+
* Returns an unsubscribe function.
|
|
178
|
+
*/
|
|
179
|
+
subscribe(type: string, cb: (payload: unknown) => void): () => void;
|
|
180
|
+
/** Publish an event into the same bus. Useful for container ↔ container comms. */
|
|
181
|
+
emit(type: string, payload?: unknown): void;
|
|
182
|
+
}
|
|
183
|
+
/** OTA / web-source switching. */
|
|
184
|
+
export type WebSourceMode = 'live' | 'bundled' | 'channel';
|
|
185
|
+
export interface WebSourceConfig {
|
|
186
|
+
mode: WebSourceMode;
|
|
187
|
+
/** Only set when `mode === 'channel'`. */
|
|
188
|
+
channel?: string;
|
|
189
|
+
/** Only set when `mode === 'live'`. */
|
|
190
|
+
liveUrl?: string;
|
|
191
|
+
}
|
|
192
|
+
export interface WebSourceFacade {
|
|
193
|
+
/** Returns the currently active web source config. */
|
|
194
|
+
get(): Promise<WebSourceConfig>;
|
|
195
|
+
/** Switch web source mode. Takes effect on next app boot. */
|
|
196
|
+
set(config: WebSourceConfig): Promise<void>;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The full native capability facade exposed by the host on
|
|
200
|
+
* `AdminMobileHostContext.native` (containers) and `window.SL.native` (UMD
|
|
201
|
+
* microapps).
|
|
202
|
+
*
|
|
203
|
+
* Each sub-facade is present only when the host implements it. Check
|
|
204
|
+
* `'nfc' in host.native` (or `host.capabilities` for hardware-gated features)
|
|
205
|
+
* before calling.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* const { native } = host;
|
|
209
|
+
* if (native?.qr) {
|
|
210
|
+
* const code = await native.qr.scan();
|
|
211
|
+
* }
|
|
212
|
+
*/
|
|
213
|
+
export interface NativeFacade {
|
|
214
|
+
share: ShareFacade;
|
|
215
|
+
clipboard: ClipboardFacade;
|
|
216
|
+
haptics: HapticsFacade;
|
|
217
|
+
network: NetworkFacade;
|
|
218
|
+
device: DeviceFacade;
|
|
219
|
+
/** Cross-host persistent storage (`@capacitor/preferences` / `localStorage` fallback). */
|
|
220
|
+
storage: StorageFacade;
|
|
221
|
+
qr: QrFacade;
|
|
222
|
+
auth: AuthFacade;
|
|
223
|
+
nfc: NfcFacade;
|
|
224
|
+
/** RFID mass-scan. Only populated on `custom-android` hosts. */
|
|
225
|
+
rfid?: RfidFacade;
|
|
226
|
+
events: EventsFacade;
|
|
227
|
+
webSource: WebSourceFacade;
|
|
228
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract types for the `SL.native` / `host.native` facade.
|
|
3
|
+
*
|
|
4
|
+
* These are **interface-only** — no runtime implementation lives here.
|
|
5
|
+
* The facade is implemented per-host (Sidekick, Capacitor builds, PWA) and
|
|
6
|
+
* exposed to containers via `AdminMobileHostContext.native` or (for UMD
|
|
7
|
+
* microapps) via `window.SL.native`. The SDK owns the shape; the host owns
|
|
8
|
+
* the wiring.
|
|
9
|
+
*
|
|
10
|
+
* All facade methods reject with one of the structured errors from
|
|
11
|
+
* `@proveanything/smartlinks` (`HostCapabilityUnavailableError`,
|
|
12
|
+
* `HostPermissionDeniedError`, `HostTimeoutError`) so a single catch handler
|
|
13
|
+
* covers every host and capability.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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
|
|
|
7
|
-
> **See also:** [containers.md](containers.md) covers the public consumer container. The [Multiple Consumer Components](containers.md#multiple-consumer-components) section explains the consumer vs. admin bundle split.
|
|
7
|
+
> **See also:** [containers.md](containers.md) covers the public consumer container. The [Multiple Consumer Components](containers.md#multiple-consumer-components) section explains the consumer vs. admin bundle split. For the full `host.native` facade contract (share, clipboard, haptics, NFC, RFID, storage, etc.) see [native-facade.md](native-facade.md).
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -23,6 +23,7 @@ This document describes how to build a **Mobile Admin Container** — a SmartLin
|
|
|
23
23
|
11. [Build & Bundle Requirements](#build--bundle-requirements)
|
|
24
24
|
12. [Example: Minimal Mobile Admin Container](#example-minimal-mobile-admin-container)
|
|
25
25
|
13. [Best Practices](#best-practices)
|
|
26
|
+
14. [Native Capability Facade](#native-capability-facade)
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
@@ -58,7 +59,7 @@ Your container **never** detects the host directly. It receives a `host` prop fr
|
|
|
58
59
|
|
|
59
60
|
Every container mounted by the SmartLinks Mobile launcher receives a single `host` prop — `AdminMobileHostContext`. Do not reach for `window.SmartlinksScanner` or `window.Capacitor` directly; both are wrapped here.
|
|
60
61
|
|
|
61
|
-
> **SDK export** — `AdminMobileHostContext`, `AdminMobileCapability`, `AdminMobileHostId`, `AdminMobileEvent`, `
|
|
62
|
+
> **SDK export** — `AdminMobileHostContext`, `AdminMobileCapability`, `ActionableCapability`, `AdminMobileHostId`, `AdminMobileEvent`, `AdminMobileEventCallback`, `AdminMobileEventSubscriber`, `AdminMobileComponentManifest`, `AdminMobileBundleManifest`, and `NativeFacade` (plus all sub-facade interfaces) are exported from `@proveanything/smartlinks`. Import via `import type { AdminMobileHostContext } from '@proveanything/smartlinks'` — no local mirror needed. `ScannerEventSubscriber`, `MobileAdminComponentManifest`, and `MobileAdminBundleManifest` still export as deprecated aliases.
|
|
62
63
|
|
|
63
64
|
```typescript
|
|
64
65
|
interface AdminMobileHostContext {
|
|
@@ -82,8 +83,8 @@ interface AdminMobileHostContext {
|
|
|
82
83
|
keyboard: boolean
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
// Unified hardware event stream
|
|
86
|
-
events: { subscribe:
|
|
86
|
+
// Unified hardware event stream — callback type: AdminMobileEventCallback
|
|
87
|
+
events: { subscribe: (cb: AdminMobileEventCallback) => () => void }
|
|
87
88
|
|
|
88
89
|
// Promise-based hardware actions — reject with a structured error when unavailable
|
|
89
90
|
actions: {
|
|
@@ -97,12 +98,12 @@ interface AdminMobileHostContext {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
// Host-provided UI helpers
|
|
101
|
+
// Host-provided UI helpers — all optional, see §3 note
|
|
101
102
|
ui: {
|
|
102
|
-
toast
|
|
103
|
-
haptic
|
|
104
|
-
setHeaderTitle
|
|
105
|
-
navigateBack
|
|
103
|
+
toast?: (opts: { title: string; description?: string; variant?: 'default' | 'destructive' }) => void
|
|
104
|
+
haptic?: (style?: 'light' | 'success' | 'error') => void
|
|
105
|
+
setHeaderTitle?: (title: string | null) => void
|
|
106
|
+
navigateBack?: () => void
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// Network & device info
|
|
@@ -112,6 +113,10 @@ interface AdminMobileHostContext {
|
|
|
112
113
|
// Informational host version — use for logging/diagnostics, not feature detection.
|
|
113
114
|
// For feature detection prefer existence checks: 'requestNfcTap' in host.actions
|
|
114
115
|
_version: number
|
|
116
|
+
|
|
117
|
+
// Full native capability facade — share, clipboard, haptics, NFC, RFID, storage, etc.
|
|
118
|
+
// Optional: not every host stub populates it. See native-facade.md.
|
|
119
|
+
native?: NativeFacade
|
|
115
120
|
}
|
|
116
121
|
```
|
|
117
122
|
|
|
@@ -143,9 +148,10 @@ if (host.hardware.nfc) {
|
|
|
143
148
|
|
|
144
149
|
### `host.ui` — native helpers vs. your own components
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
All four `host.ui` methods are **optional by design**. A container may run in the Sidekick mobile shell, a standalone PWA or browser tab, Storybook, or a screenshot harness — none of those environments is guaranteed to have a native toast system, a managed header, or a back stack. Forcing every host implementer to provide them would exclude the web and desktop use-cases the rest of the contract explicitly supports. Always guard with `?.`:
|
|
147
152
|
|
|
148
|
-
`host.ui.toast()`
|
|
153
|
+
- `host.ui.toast?.({...})` / `host.ui.haptic?.('success')` — optional native feedback. Fall back to your own `<Toaster />` when absent.
|
|
154
|
+
- `host.ui.setHeaderTitle?.('Scanning…')` / `host.ui.navigateBack?.()` — host-shell integrations with no in-container equivalent. Silently no-op when the host doesn't implement them.
|
|
149
155
|
|
|
150
156
|
**Stub pattern for testing and Storybook:**
|
|
151
157
|
|
|
@@ -222,12 +228,23 @@ The custom Kotlin shell and both Capacitor shells ship the same baseline plugin
|
|
|
222
228
|
| `@capacitor/device` | `host.device.info()` |
|
|
223
229
|
| `@capacitor/share` | `host.actions.share()` |
|
|
224
230
|
| `@capacitor/clipboard` | `host.actions.clipboard.*` |
|
|
225
|
-
| `@capacitor/preferences` | `host.storage.*` *(planned)* |
|
|
231
|
+
| `@capacitor/preferences` | `host.storage.*` *(planned — see note below)* |
|
|
226
232
|
| `@capacitor/app` | host-managed (back button, deep links) |
|
|
227
233
|
| `@capacitor/status-bar` | host-managed |
|
|
228
234
|
| `@capacitor/keyboard` | host-managed |
|
|
229
235
|
| `@capacitor/toast` | wired into `host.ui.toast()` |
|
|
230
236
|
|
|
237
|
+
> **`host.storage` — planned shape.** Once released, the surface will wrap `@capacitor/preferences` directly:
|
|
238
|
+
> ```ts
|
|
239
|
+
> host.storage: {
|
|
240
|
+
> get(key: string): Promise<string | null>
|
|
241
|
+
> set(key: string, value: string): Promise<void>
|
|
242
|
+
> remove(key: string): Promise<void>
|
|
243
|
+
> keys(): Promise<string[]>
|
|
244
|
+
> }
|
|
245
|
+
> ```
|
|
246
|
+
> Until then: `localStorage` works on web hosts; use `@capacitor/preferences` directly (bundle it in) on native.
|
|
247
|
+
|
|
231
248
|
### Tier 2 — capability-gated (declare in manifest)
|
|
232
249
|
|
|
233
250
|
| Plugin | Capability flag |
|
|
@@ -385,7 +402,9 @@ try {
|
|
|
385
402
|
| `lifecycle: 'pause'` | App backgrounded | Pause readers to save battery |
|
|
386
403
|
| `lifecycle: 'resume'` | App foregrounded | Resubscribe; refresh stale data |
|
|
387
404
|
|
|
388
|
-
Use `host.events.subscribe` for all lifecycle events —
|
|
405
|
+
Use `host.events.subscribe` for all lifecycle events — all five host types (`custom-android`, `capacitor-ios`, `capacitor-android`, `pwa`, `browser`) are guaranteed to emit every `'pause'`/`'resume'`/`'offline'`/`'online'` phase. No `window.addEventListener('online')` fallback is needed.
|
|
406
|
+
|
|
407
|
+
> **Planned** — `phase: 'mount' | 'unmount'` events are on the roadmap. These will fire when the container becomes visible / is removed from the host view stack, enabling deferred reader start-up without a `useEffect` dependency.
|
|
389
408
|
|
|
390
409
|
---
|
|
391
410
|
|
|
@@ -521,10 +540,33 @@ export const MOBILE_ADMIN_MANIFEST = {
|
|
|
521
540
|
|
|
522
541
|
- **Always check `host.hardware.X` before calling `host.actions.X`** — never assume a capability is available.
|
|
523
542
|
- **Always wrap action calls in try/catch** — handle `HostPermissionDeniedError`, `HostTimeoutError`, and `HostCapabilityUnavailableError`.
|
|
524
|
-
- **
|
|
525
|
-
- **`host.ui.
|
|
543
|
+
- **All four `host.ui` methods are optional** (`toast`, `haptic`, `setHeaderTitle`, `navigateBack`) — guard every call with `?.`. See the `host.ui` section above.
|
|
544
|
+
- **`host.ui.setHeaderTitle` and `host.ui.navigateBack`** integrate with the host shell and have no in-container equivalent; call with `?.`.
|
|
526
545
|
- **Use `host.events.subscribe` for lifecycle events** — `'offline'`/`'online'`/`'pause'`/`'resume'` fire consistently on every host.
|
|
527
546
|
- **Never call `initializeApi`, never use top-level SDK imports for API calls** — `host.SL` is already configured. `SL.method()` instead of `host.SL.method()` silently uses the wrong baseURL.
|
|
528
547
|
- **Bundle Capacitor plugins in** (do not externalise) — so the component degrades gracefully on PWA/browser without crashing.
|
|
529
548
|
- **Declare `offline: true`** on a component if it queues writes locally — this signals the launcher to provision offline sync support.
|
|
530
549
|
- **Use `'methodName' in host.actions`** to feature-detect new host capabilities rather than comparing `host._version`. The version is informational only.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Native Capability Facade
|
|
554
|
+
|
|
555
|
+
`host.native` gives containers access to a broader set of device capabilities — share sheet, clipboard, full haptics API, storage, QR, NFC, RFID (Kotlin only), auth, and cross-shell events — through a single interface that falls back gracefully across all host environments.
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
// Check host.hardware.* for physical availability, then call via host.native
|
|
559
|
+
if (host.hardware.nfc && host.native?.nfc) {
|
|
560
|
+
const { uid } = await host.native.nfc.read({ timeoutMs: 10_000 })
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Storage always available (Preferences → localStorage → in-memory Map)
|
|
564
|
+
await host.native?.storage.set('lastScan', uid)
|
|
565
|
+
|
|
566
|
+
// Share sheet with web fallback
|
|
567
|
+
await host.native?.share.share({ title: 'Found tag', url: `https://app.example/tags/${uid}` })
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
`host.native` is optional on `AdminMobileHostContext` — host stubs (Storybook, unit tests) need not implement it. `native.rfid` is additionally optional within `NativeFacade` itself, as it only exists on `custom-android`.
|
|
571
|
+
|
|
572
|
+
For the full sub-facade table, fallback chains, and what's intentionally not wrapped, see **[native-facade.md](native-facade.md)**.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Native Capability Facade (`host.native` / `SL.native`)
|
|
2
|
+
|
|
3
|
+
> **Version:** 1.12 · **Platform:** SmartLinks R4 · **Last updated:** 2026-04-30
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
**The SDK owns the contract** (`src/native/types.ts`, re-exported from `@proveanything/smartlinks`). Each host implementation is responsible for wiring up the sub-facades and populating:
|
|
8
|
+
- `AdminMobileHostContext.native` (container prop, typed in the SDK), and
|
|
9
|
+
- `window.SL.native` (for UMD microapps that don't receive a host prop — host concern, not SDK).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Access patterns](#access-patterns)
|
|
16
|
+
2. [What's in the facade](#whats-in-the-facade)
|
|
17
|
+
3. [What's NOT in the facade](#whats-not-in-the-facade)
|
|
18
|
+
4. [Error contract](#error-contract)
|
|
19
|
+
5. [Type reference](#type-reference)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Access patterns
|
|
24
|
+
|
|
25
|
+
### Inside a Mobile Admin Container (recommended)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import type { AdminMobileHostContext } from '@proveanything/smartlinks'
|
|
29
|
+
|
|
30
|
+
function MyContainer({ host }: { host: AdminMobileHostContext }) {
|
|
31
|
+
const handleShare = async () => {
|
|
32
|
+
await host.native?.share.share({ title: 'SmartLinks', url: window.location.href })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleScan = async () => {
|
|
36
|
+
// Only present when the host provides a QR reader
|
|
37
|
+
const code = await host.native?.qr.scan()
|
|
38
|
+
if (code) processCode(code)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Always call via `host.native?.<facade>.<method>()` — `native` is optional because not every host exposes the full surface (e.g. a minimal browser stub may omit `rfid`).
|
|
44
|
+
|
|
45
|
+
For hardware-gated capabilities (NFC, RFID, QR, camera) also check `host.capabilities` or `host.hardware.*` before calling, as those flags reflect physical availability on the current device:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
if (host.hardware.nfc && host.native?.nfc) {
|
|
49
|
+
const { uid } = await host.native.nfc.read({ timeoutMs: 10_000 })
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Inside a UMD microapp (no host prop)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// window.SL.native is populated by the host at boot — host concern, not SDK
|
|
57
|
+
const native = (window as any).SL?.native as import('@proveanything/smartlinks').NativeFacade | undefined
|
|
58
|
+
const code = await native?.qr.scan()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## What's in the facade
|
|
64
|
+
|
|
65
|
+
These capabilities have meaningful fallbacks across Kotlin / Capacitor / web. Always go through the facade.
|
|
66
|
+
|
|
67
|
+
| Sub-facade | Key methods | Fallback chain |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `native.share` | `share({ title, url, text? })`, `canShare()` | Capacitor Share → `navigator.share` → copy to clipboard + toast |
|
|
70
|
+
| `native.clipboard` | `write(text)`, `read()` | Capacitor Clipboard → `navigator.clipboard` → `execCommand('copy')` |
|
|
71
|
+
| `native.haptics` | `impact(style?)`, `notification(style)`, `selection()` | Capacitor Haptics → `navigator.vibrate` → no-op |
|
|
72
|
+
| `native.network` | `getStatus()`, `addListener('change', cb)` | Capacitor Network → `navigator.onLine` + `'online'`/`'offline'` events |
|
|
73
|
+
| `native.device` | `getInfo()`, `getId()`, `getLanguageCode()` | Capacitor Device → UA parsing + cached UUID in `localStorage` |
|
|
74
|
+
| `native.storage` | `get(key)`, `set(key, value)`, `remove(key)`, `keys()` | Capacitor Preferences → `localStorage` → in-memory `Map` (private mode) |
|
|
75
|
+
| `native.qr` | `scan(opts?)` | Capacitor MLKit Barcode → html5-qrcode / `BarcodeDetector` |
|
|
76
|
+
| `native.auth` | `signInWithGoogle()`, `signOut()` | Kotlin bridge → Capacitor Browser + OAuth → web redirect |
|
|
77
|
+
| `native.nfc` | `read(opts?)`, `writeNdef(opts)`, `programTag(opts)`, `lockTag(opts)`, `isLocked(opts)` | Kotlin bridge → Web NFC (`NDEFReader`) → `HostCapabilityUnavailableError` |
|
|
78
|
+
| `native.rfid` | `startScan(opts?)`, `stopScan()`, `subscribe(cb)` | Kotlin bridge only — throws `HostCapabilityUnavailableError` on all other hosts |
|
|
79
|
+
| `native.events` | `subscribe(type, cb)`, `emit(type, payload?)` | Internal pub/sub bridging Kotlin `onSmartlinksData` + Capacitor listeners |
|
|
80
|
+
| `native.webSource` | `get()`, `set({ mode, channel?, liveUrl? })` | Kotlin bridge + Capacitor Preferences mirror |
|
|
81
|
+
|
|
82
|
+
> **`native.rfid` is the only optional sub-facade** on `NativeFacade` — it is only populated on `custom-android` hosts. All other sub-facades are present on every host, though individual methods may throw `HostCapabilityUnavailableError` when the underlying capability is absent.
|
|
83
|
+
|
|
84
|
+
> **`native.device` vs `host.device`** — `host.device.info()` is a simplified convenience that returns `{ model, platform }`. `host.native?.device.getInfo()` returns the full `DeviceInfo` (adds `osVersion`, `manufacturer`, `isVirtual`) and also provides `getId()` and `getLanguageCode()`. Use `host.device` when the convenience is enough.
|
|
85
|
+
|
|
86
|
+
> **`native.network` vs `host.network`** — `host.network.isOnline()` is a boolean convenience. `host.native?.network.getStatus()` returns `{ connected, connectionType }` including `'wifi'` / `'cellular'` distinction.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## What's NOT in the facade
|
|
91
|
+
|
|
92
|
+
These capabilities intentionally have no `SL.native` wrapper. Call the Capacitor plugin directly, or use the web API. The host provides them as Tier 1 or Tier 2 plugins (see [Capacitor Plugin Baseline](mobile-admin-container.md#capacitor-plugin-baseline)).
|
|
93
|
+
|
|
94
|
+
| Capability | Why not wrapped |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `@capacitor/push-notifications` | Push only exists in Capacitor builds; no cross-shell registration flow. Import and call directly; detect Capacitor first. |
|
|
97
|
+
| `@capacitor/local-notifications` | Same — no Kotlin or web equivalent worth abstracting. |
|
|
98
|
+
| `@capacitor/camera` | Plugin already abstracts iOS/Android. PWA's `<input capture>` is too different in shape to unify. |
|
|
99
|
+
| `@capacitor/filesystem` | No web equivalent (storage quotas, sandbox rules differ). Use `URL.createObjectURL` + download anchor on web. |
|
|
100
|
+
| `@capacitor/geolocation` | `navigator.geolocation` already works in WebViews; the Capacitor plugin wraps the same OS APIs. No facade adds value. |
|
|
101
|
+
| `@capacitor/keyboard`, `@capacitor/screen-*`, `@capacitor/splash-screen`, `@capacitor/status-bar`, `@capacitor/dialog`, `@capacitor/app` | Per-host boot-time config or platform-chrome concerns; not part of the microapp contract. |
|
|
102
|
+
| `@capacitor/browser` | In-app browser for OAuth is intentionally Capacitor-only. For opening external URLs use `native.share.share({ url })`. |
|
|
103
|
+
| `@capawesome/capacitor-file-picker` | Picker UX too divergent across web (`<input type="file">`) and native to wrap usefully. |
|
|
104
|
+
|
|
105
|
+
If a capability later needs consistent behaviour across shells (e.g. notifications reach enough hosts to warrant throttling / routing via the facade), promote it into `NativeFacade` by adding an interface to `src/native/types.ts` and a row to the table above.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Error contract
|
|
110
|
+
|
|
111
|
+
All facade methods reject with one of the three structured errors from `@proveanything/smartlinks`. A single catch block handles every capability and host combination:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import {
|
|
115
|
+
HostCapabilityUnavailableError,
|
|
116
|
+
HostPermissionDeniedError,
|
|
117
|
+
HostTimeoutError,
|
|
118
|
+
} from '@proveanything/smartlinks'
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const { uid } = await host.native?.nfc.read({ timeoutMs: 10_000 }) ?? {}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (err instanceof HostCapabilityUnavailableError) {
|
|
124
|
+
// host.capabilities won't list 'nfc' — we should have checked first
|
|
125
|
+
host.ui.toast?.({ title: 'NFC not available on this device', variant: 'destructive' })
|
|
126
|
+
} else if (err instanceof HostPermissionDeniedError) {
|
|
127
|
+
host.ui.toast?.({ title: 'NFC permission denied', variant: 'destructive' })
|
|
128
|
+
} else if (err instanceof HostTimeoutError) {
|
|
129
|
+
host.ui.toast?.({ title: `No tag detected after ${err.timeoutMs / 1000}s` })
|
|
130
|
+
} else {
|
|
131
|
+
throw err
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Type reference
|
|
139
|
+
|
|
140
|
+
All types below are exported from `@proveanything/smartlinks`:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import type {
|
|
144
|
+
NativeFacade, // root — use on AdminMobileHostContext.native
|
|
145
|
+
NativeCapability, // union of sub-facade keys: 'share' | 'clipboard' | ...
|
|
146
|
+
ShareFacade,
|
|
147
|
+
ClipboardFacade,
|
|
148
|
+
HapticsFacade,
|
|
149
|
+
HapticImpactStyle, // 'light' | 'medium' | 'heavy'
|
|
150
|
+
HapticNotificationStyle,
|
|
151
|
+
NetworkFacade,
|
|
152
|
+
NetworkStatus,
|
|
153
|
+
DeviceFacade,
|
|
154
|
+
DeviceInfo,
|
|
155
|
+
StorageFacade,
|
|
156
|
+
QrFacade,
|
|
157
|
+
QrScanOptions,
|
|
158
|
+
AuthFacade,
|
|
159
|
+
NfcFacade,
|
|
160
|
+
NfcReadResult,
|
|
161
|
+
RfidFacade,
|
|
162
|
+
RfidScanOptions,
|
|
163
|
+
EventsFacade,
|
|
164
|
+
WebSourceFacade,
|
|
165
|
+
WebSourceMode,
|
|
166
|
+
WebSourceConfig,
|
|
167
|
+
} from '@proveanything/smartlinks'
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The facade interfaces are contract-only — no runtime implementation is shipped in the SDK. You only need them for TypeScript type-checking (authoring containers or host stubs).
|