@captello/ulc-webview-sdk 1.1.0 → 1.3.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/embedded-app.ts"],"names":[],"mappings":"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","file":"embedded-app.js","sourcesContent":["/**\n * `@captello/ulc-webview-sdk/embedded-app` — for the **Captello mobile app** (or any\n * host playing its role) that embeds an application in an iframe and serves it native\n * services over `postMessage`.\n *\n * {@link EmbeddedAppClient} wraps the iframe: it delivers the embedded application's\n * requests to typed handlers and posts the app's responses back. The embedded\n * application's own side is `MobileHostClient` (`@captello/ulc-webview-sdk/mobile-host`).\n *\n * Not to be confused with `CaptelloWebview`, which wraps an iframe of the *capture\n * webview* — a different protocol (JSON strings, snake_case types).\n *\n * @example\n * import { EmbeddedAppClient, MobileHostRequestType } from \"@captello/ulc-webview-sdk/embedded-app\";\n *\n * const embedded = new EmbeddedAppClient(iframe, { targetOrigin: new URL(iframe.src).origin });\n * embedded.onRequest(MobileHostRequestType.RequestAuthToken, async () => {\n * embedded.sendAuthToken(await mintMagicToken());\n * });\n * embedded.onRequest(MobileHostRequestType.OpenScanner, async () => {\n * for (const person of await scan()) embedded.sendScannerResult(person);\n * embedded.sendScannerClosed();\n * });\n */\n\nimport type { FrameLike, Unsubscribe } from \"./client\";\nimport {\n makeSubscription,\n MobileHostRequestType,\n MobileHostResponseType,\n parseMobileHostRequest,\n} from \"./mobile-app-protocol\";\nimport type { MobileHostRequest, MobileHostRequestMap, MobileHostResponse, ScannedPerson } from \"./mobile-app-protocol\";\n\nexport {\n MobileHostRequestType,\n MobileHostResponseType,\n parseMobileHostRequest,\n parseMobileHostResponse,\n} from \"./mobile-app-protocol\";\nexport type {\n MobileHostRequest,\n MobileHostRequestMap,\n MobileHostResponse,\n MobileHostResponseMap,\n AuthTokenMessage,\n ScannerResultMessage,\n ScannerClosedMessage,\n ScannedPerson,\n} from \"./mobile-app-protocol\";\nexport type { FrameLike } from \"./client\";\n\n/** Listener for a specific request type from the embedded application. */\nexport type MobileHostRequestListener<T extends MobileHostRequestType> = (request: MobileHostRequestMap[T]) => void;\n\n/** Listener for every request from the embedded application (used by {@link EmbeddedAppClient.onAnyRequest}). */\nexport type AnyMobileHostRequestListener = (request: MobileHostRequest) => void;\n\nexport interface EmbeddedAppClientOptions {\n /**\n * Origin to validate incoming requests against and to target outgoing responses.\n * A full URL is accepted and reduced to its origin.\n *\n * Defaults to the iframe's `src` origin, read fresh on every send so a `src` bound\n * after construction still works. When neither is available the client refuses to\n * send rather than fall back to `\"*\"` — a response can carry an auth token, and\n * `\"*\"` would hand it to whatever the frame navigated to.\n */\n targetOrigin?: string;\n /** Window to listen on. Defaults to the global `window`. */\n hostWindow?: Window;\n /**\n * Only accept requests whose `event.source` is the bound iframe's window. Defaults\n * to `true`. Set `false` if the application relays through another window.\n */\n matchSource?: boolean;\n}\n\nfunction toOrigin(value: string | undefined): string | undefined {\n if (!value) return undefined;\n if (value === \"*\") return value;\n try {\n return new URL(value).origin;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Mobile-app-side client for an application embedded in an iframe.\n *\n * Attaches a single `message` listener on construction; call {@link destroy} when the\n * iframe goes away.\n */\nexport class EmbeddedAppClient {\n private readonly frame: FrameLike;\n private readonly hostWindow: Window;\n private readonly explicitOrigin: string | undefined;\n private readonly matchSource: boolean;\n private readonly listeners = new Map<\n MobileHostRequestType,\n Set<MobileHostRequestListener<MobileHostRequestType>>\n >();\n private readonly anyListeners = new Set<AnyMobileHostRequestListener>();\n private readonly boundHandler = (event: MessageEvent) => this.handleMessage(event);\n private destroyed = false;\n\n constructor(frame: FrameLike, options: EmbeddedAppClientOptions = {}) {\n if (!frame) {\n throw new Error(\"EmbeddedAppClient: a mounted iframe element (or { contentWindow }) is required.\");\n }\n const hostWindow = options.hostWindow ?? (typeof window !== \"undefined\" ? window : undefined);\n if (!hostWindow) {\n throw new Error(\n \"EmbeddedAppClient: no host window available. Pass `hostWindow` when constructing outside a browser.\",\n );\n }\n this.frame = frame;\n this.hostWindow = hostWindow;\n this.explicitOrigin = toOrigin(options.targetOrigin);\n this.matchSource = options.matchSource ?? true;\n this.hostWindow.addEventListener(\"message\", this.boundHandler);\n }\n\n /** Subscribe to one request type. Returns a handle with `unsubscribe()` (also callable). */\n onRequest<T extends MobileHostRequestType>(type: T, listener: MobileHostRequestListener<T>): Unsubscribe {\n let set = this.listeners.get(type);\n if (!set) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener as MobileHostRequestListener<MobileHostRequestType>);\n return makeSubscription(() => {\n set?.delete(listener as MobileHostRequestListener<MobileHostRequestType>);\n });\n }\n\n /** Subscribe to every request. */\n onAnyRequest(listener: AnyMobileHostRequestListener): Unsubscribe {\n this.anyListeners.add(listener);\n return makeSubscription(() => {\n this.anyListeners.delete(listener);\n });\n }\n\n /**\n * Post a response into the embedded application. Returns `false` when nothing was\n * sent: the client is destroyed, the iframe has no window, or no target origin could\n * be resolved (see {@link EmbeddedAppClientOptions.targetOrigin}).\n */\n send(response: MobileHostResponse): boolean {\n if (this.destroyed) return false;\n const target = this.frame.contentWindow;\n const origin = this.resolveOrigin();\n if (!target || !origin) return false;\n target.postMessage(response, origin);\n return true;\n }\n\n /** Answer a {@link MobileHostRequestType.RequestAuthToken} request. */\n sendAuthToken(token: string): boolean {\n return this.send({ type: MobileHostResponseType.AuthToken, token });\n }\n\n /** Post one scanned person of the current scanner session. */\n sendScannerResult(person: ScannedPerson): boolean {\n return this.send({\n type: MobileHostResponseType.ScannerResult,\n badgeId: person.badgeId,\n result: person.fields,\n });\n }\n\n /** Fail the current scanner session with a display-ready message. */\n sendScannerError(message: string): boolean {\n return this.send({ type: MobileHostResponseType.ScannerResult, message });\n }\n\n /** End the current scanner session; send after the last result, or on cancel. */\n sendScannerClosed(): boolean {\n return this.send({ type: MobileHostResponseType.ScannerClosed });\n }\n\n /** Remove the `message` listener and drop every subscription. Safe to call more than once. */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.hostWindow.removeEventListener(\"message\", this.boundHandler);\n this.listeners.clear();\n this.anyListeners.clear();\n }\n\n private resolveOrigin(): string | undefined {\n if (this.explicitOrigin) return this.explicitOrigin;\n const src = \"src\" in this.frame ? (this.frame as { src?: string }).src : undefined;\n return toOrigin(src);\n }\n\n private handleMessage(event: MessageEvent): void {\n if (this.matchSource && event.source !== this.frame.contentWindow) return;\n const origin = this.resolveOrigin();\n if (origin && origin !== \"*\" && event.origin !== origin) return;\n const request = parseMobileHostRequest(event.data);\n if (!request) return;\n for (const listener of Array.from(this.anyListeners)) {\n listener(request);\n }\n const set = this.listeners.get(request.type);\n if (!set) return;\n for (const listener of Array.from(set)) {\n listener(request);\n }\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AddressSubmissionValue, g as AnyOutboundListener, h as AttachmentValue, B as BusinessCardValue, f as CaptelloWebview, C as CaptelloWebviewOptions, D as DraftSubmissionData, F as FormElementType, I as InboundMessage, i as InboundMessageType, N as NameSubmissionValue, j as OrderCheckboxSubmissionData, k as OrderRadioSubmissionData, l as OutboundListener, d as OutboundMessage, a as OutboundMessageMap, O as OutboundMessageType, P as PrefillInfoItem, S as SubmissionBody, b as SubmissionError, e as SubmissionPrefill, m as SubmissionPrefillDataItem, n as SubmissionQuestionData, c as SubmissionTimeoutError, U as Unsubscribe, V as ValidationTarget, o as VisibleSubmissionDataItem, p as VisibleSubmissionElementType, q as VisibleSubmissionElementValueMap, r as parseOutboundMessage } from './client-cZpygJTD.js';
1
+ export { A as AddressSubmissionValue, g as AnyOutboundListener, h as AttachOptions, i as AttachmentValue, B as BusinessCardValue, f as CaptelloWebview, C as CaptelloWebviewOptions, D as DraftSubmissionData, j as FormElementType, F as FrameLike, I as InboundMessage, k as InboundMessageType, N as NameSubmissionValue, l as OrderCheckboxSubmissionData, m as OrderRadioSubmissionData, n as OutboundListener, d as OutboundMessage, a as OutboundMessageMap, O as OutboundMessageType, P as PrefillInfoItem, S as SubmissionBody, b as SubmissionError, e as SubmissionPrefill, o as SubmissionPrefillDataItem, p as SubmissionQuestionData, c as SubmissionTimeoutError, q as TranscribeScannerReply, T as TranscribeScannerRequestHandler, r as TranscribeScannerRequestMessage, s as TranscribeScannerRequestRef, t as TranscribeScannerResultData, u as TranscribeScannerResultMessage, v as TranscribedScannerField, U as Unsubscribe, V as ValidationTarget, w as VisibleSubmissionDataItem, x as VisibleSubmissionElementType, y as VisibleSubmissionElementValueMap, z as attach, E as parseOutboundMessage } from './client-CAMlFA8s.js';
2
2
 
3
3
  /**
4
4
  * Builder for the Captello capture webview embed URL.
@@ -28,7 +28,14 @@ declare enum EmbedParam {
28
28
  /** Edit mode read-only: locks email-mapped and invitation-code elements. */
29
29
  Emro = "emro",
30
30
  /** Context for filtering form-fill actions (e.g. MMP outbound/inbound/notes). */
31
- UseIn = "useIn"
31
+ UseIn = "useIn",
32
+ /**
33
+ * Show the transcribe button: the form renders a button that emits a
34
+ * `host_processing_request` with `processing_type: "transcribe_scanner_request"`.
35
+ * Only enable it when the host answers those requests (e.g. via
36
+ * `CaptelloWebview.onProcessingRequest`).
37
+ */
38
+ ShowTranscribeButton = "show_transcribe_button"
32
39
  }
33
40
  /** Form render mode (the webview's `FormMode`). */
34
41
  declare enum FormMode {
@@ -100,6 +107,13 @@ interface EmbedUrlOptions {
100
107
  connexionsEmbedMode?: boolean;
101
108
  /** Edit mode read-only. */
102
109
  emro?: boolean;
110
+ /**
111
+ * Show the transcribe button in the form (emits `host_processing_request` with
112
+ * `processing_type: "transcribe_scanner_request"` when pressed). Enable only when
113
+ * the host answers those requests (e.g. via `CaptelloWebview.onProcessingRequest`) —
114
+ * otherwise the button times out with an error for the user.
115
+ */
116
+ showTranscribeButton?: boolean;
103
117
  /**
104
118
  * Extra query params to append verbatim (e.g. prospect tracking params the
105
119
  * webview forwards on submit). Values are stringified; `undefined`/`null` skipped.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl } from './chunk-4E7OW4RJ.js';
2
- export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, parseOutboundMessage } from './chunk-UUEUW2WL.js';
1
+ export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl } from './chunk-ZX4AKPWF.js';
2
+ export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, attach, parseOutboundMessage } from './chunk-ZPVXZW2B.js';
3
3
 
4
4
  // src/submission-data.ts
5
5
  var FormElementType = /* @__PURE__ */ ((FormElementType2) => {
@@ -0,0 +1,176 @@
1
+ import { P as PrefillInfoItem } from './client-CAMlFA8s.js';
2
+
3
+ /**
4
+ * The message protocol between the **Captello mobile app** and an **application it
5
+ * embeds** (the meeting platform, Connexions, …).
6
+ *
7
+ * This is the second of the SDK's two channels, and the roles are the reverse of the
8
+ * capture-webview channel:
9
+ *
10
+ * | Channel | Host | Embedded | Client on each side |
11
+ * | ----------------------------------------- | ------------------- | ------------------- | ---------------------------------------------------- |
12
+ * | A host page embeds the capture webview | your page | capture webview | `CaptelloWebview` (host side) |
13
+ * | The mobile app embeds an application | Captello mobile app | your application | `EmbeddedAppClient` (app side), `MobileHostClient` (embedded side) |
14
+ *
15
+ * Wire format (match it exactly — it differs from the capture-webview channel):
16
+ * - Messages are posted as **plain objects**, not JSON strings.
17
+ * - `type` values are SCREAMING_CASE.
18
+ * - {@link MobileHostRequestType} (embedded application → mobile app) and
19
+ * {@link MobileHostResponseType} (mobile app → embedded application) are disjoint.
20
+ */
21
+
22
+ /** Message `type` values an embedded application sends to the Captello mobile app. */
23
+ declare enum MobileHostRequestType {
24
+ /** The application finished loading; the app hides its loading spinner. */
25
+ AppReady = "APP_READY",
26
+ /** A user-facing error; the app shows `message`. */
27
+ Error = "ERROR",
28
+ /** Close the application (the app dismisses the modal or pops the route). */
29
+ NavigateBack = "NAVIGATE_BACK",
30
+ /** Ask for a magic token; answered with {@link MobileHostResponseType.AuthToken}. */
31
+ RequestAuthToken = "REQUEST_AUTH_TOKEN",
32
+ /**
33
+ * Open the app's badge scanner. Answered with one
34
+ * {@link MobileHostResponseType.ScannerResult} per scanned person, then
35
+ * {@link MobileHostResponseType.ScannerClosed}.
36
+ */
37
+ OpenScanner = "OPEN_ULC_FORM_SCANNER",
38
+ /** Open `url` in the system browser. */
39
+ OpenUrl = "OPEN_URL",
40
+ /** Copy `text` to the clipboard; acknowledged with `COPIED_SUCCESS` / `COPIED_ERROR`. */
41
+ CopyText = "COPY_TEXT",
42
+ /** Open the native share sheet. */
43
+ ShareUrl = "SHARE_URL",
44
+ /** Save a vCard to the contacts; acknowledged with `SAVE_VCARD_SUCCESS` / `SAVE_VCARD_ERROR`. */
45
+ SaveVCard = "SAVE_VCARD",
46
+ /** Add a wallet pass; acknowledged with `ADD_TO_WALLET_SUCCESS` / `ADD_TO_WALLET_ERROR`. */
47
+ AddToWallet = "ADD_TO_WALLET",
48
+ /** Ask the app to synchronize its data. */
49
+ SyncApp = "SYNC_APP"
50
+ }
51
+ interface AppReadyRequest {
52
+ type: MobileHostRequestType.AppReady;
53
+ }
54
+ interface ErrorRequest {
55
+ type: MobileHostRequestType.Error;
56
+ message: string;
57
+ }
58
+ interface NavigateBackRequest {
59
+ type: MobileHostRequestType.NavigateBack;
60
+ }
61
+ interface RequestAuthTokenRequest {
62
+ type: MobileHostRequestType.RequestAuthToken;
63
+ }
64
+ interface OpenScannerRequest {
65
+ type: MobileHostRequestType.OpenScanner;
66
+ }
67
+ interface OpenUrlRequest {
68
+ type: MobileHostRequestType.OpenUrl;
69
+ url: string;
70
+ }
71
+ interface CopyTextRequest {
72
+ type: MobileHostRequestType.CopyText;
73
+ text: string;
74
+ }
75
+ interface ShareUrlRequest {
76
+ type: MobileHostRequestType.ShareUrl;
77
+ title: string;
78
+ text: string;
79
+ }
80
+ interface SaveVCardRequest {
81
+ type: MobileHostRequestType.SaveVCard;
82
+ /** vCard text. */
83
+ text: string;
84
+ }
85
+ interface AddToWalletRequest {
86
+ type: MobileHostRequestType.AddToWallet;
87
+ /** Base64 pass data, with or without a `data:` prefix. */
88
+ text: string;
89
+ }
90
+ interface SyncAppRequest {
91
+ type: MobileHostRequestType.SyncApp;
92
+ }
93
+ /** Discriminated union of every message an embedded application can send to the mobile app. */
94
+ type MobileHostRequest = AppReadyRequest | ErrorRequest | NavigateBackRequest | RequestAuthTokenRequest | OpenScannerRequest | OpenUrlRequest | CopyTextRequest | ShareUrlRequest | SaveVCardRequest | AddToWalletRequest | SyncAppRequest;
95
+ /** Maps each request `type` to its full message shape (used by {@link EmbeddedAppClient.onRequest}). */
96
+ type MobileHostRequestMap = {
97
+ [M in MobileHostRequest as M["type"]]: M;
98
+ };
99
+ /** Message `type` values the Captello mobile app sends to an embedded application. */
100
+ declare enum MobileHostResponseType {
101
+ /** Answer to {@link MobileHostRequestType.RequestAuthToken}. */
102
+ AuthToken = "AUTH_TOKEN",
103
+ /** One scanned person (or a scanner failure) after {@link MobileHostRequestType.OpenScanner}. */
104
+ ScannerResult = "ULC_FORM_SCANNER_RESULT",
105
+ /** The scanner session ended: every result was posted, or the user cancelled. */
106
+ ScannerClosed = "ULC_FORM_SCANNER_CLOSED",
107
+ CopiedSuccess = "COPIED_SUCCESS",
108
+ CopiedError = "COPIED_ERROR",
109
+ SaveVCardSuccess = "SAVE_VCARD_SUCCESS",
110
+ SaveVCardError = "SAVE_VCARD_ERROR",
111
+ AddToWalletSuccess = "ADD_TO_WALLET_SUCCESS",
112
+ AddToWalletError = "ADD_TO_WALLET_ERROR"
113
+ }
114
+ /** Mobile app → embedded application: the magic token requested with {@link MobileHostRequestType.RequestAuthToken}. */
115
+ interface AuthTokenMessage {
116
+ type: MobileHostResponseType.AuthToken;
117
+ token: string;
118
+ }
119
+ /**
120
+ * Mobile app → embedded application: one scanned person, or a scanner failure.
121
+ *
122
+ * The app posts one of these per person the scanner captured — several for a group
123
+ * scan — then a {@link ScannerClosedMessage}. `result` holds the looked-up attendee
124
+ * fields in the same shape {@link PrefillInfoItem} uses, so they can be fed straight to
125
+ * a capture form's `prefill({ info })`. It is empty (not absent) when the badge had no
126
+ * lookup data; the badge can still be linked via `badgeId`.
127
+ *
128
+ * On failure `result` is absent and `message` carries the error text.
129
+ */
130
+ interface ScannerResultMessage {
131
+ type: MobileHostResponseType.ScannerResult;
132
+ /**
133
+ * Badge ID the person was scanned from. Empty for business cards and manual search.
134
+ * Absent altogether on app builds that predate multi-person sessions — those post a
135
+ * single result and never a {@link ScannerClosedMessage}.
136
+ */
137
+ badgeId?: string;
138
+ /** Looked-up attendee fields (success). */
139
+ result?: PrefillInfoItem[];
140
+ /** Error text (failure) — `result` is absent. */
141
+ message?: string;
142
+ }
143
+ /** Mobile app → embedded application: the scanner session ended. Follows the last {@link ScannerResultMessage}. */
144
+ interface ScannerClosedMessage {
145
+ type: MobileHostResponseType.ScannerClosed;
146
+ }
147
+ interface AckMessage<T extends MobileHostResponseType> {
148
+ type: T;
149
+ }
150
+ /** Discriminated union of every message the mobile app can send to an embedded application. */
151
+ type MobileHostResponse = AuthTokenMessage | ScannerResultMessage | ScannerClosedMessage | AckMessage<MobileHostResponseType.CopiedSuccess> | AckMessage<MobileHostResponseType.CopiedError> | AckMessage<MobileHostResponseType.SaveVCardSuccess> | AckMessage<MobileHostResponseType.SaveVCardError> | AckMessage<MobileHostResponseType.AddToWalletSuccess> | AckMessage<MobileHostResponseType.AddToWalletError>;
152
+ /** Maps each response `type` to its full message shape (used by {@link MobileHostClient.on}). */
153
+ type MobileHostResponseMap = {
154
+ [M in MobileHostResponse as M["type"]]: M;
155
+ };
156
+ /** One person captured by the mobile app's scanner. */
157
+ interface ScannedPerson {
158
+ /** Badge ID the person was scanned from; empty for business cards and manual search. */
159
+ badgeId: string;
160
+ /** Looked-up attendee fields; empty when the badge had no lookup data. */
161
+ fields: PrefillInfoItem[];
162
+ }
163
+ /**
164
+ * Parses a raw `MessageEvent.data` value into a typed {@link MobileHostRequest}, or
165
+ * returns `null` if it is not a recognized request. Plain objects are the wire format;
166
+ * a JSON string is accepted too for robustness.
167
+ */
168
+ declare function parseMobileHostRequest(data: unknown): MobileHostRequest | null;
169
+ /**
170
+ * Parses a raw `MessageEvent.data` value into a typed {@link MobileHostResponse}, or
171
+ * returns `null` if it is not a recognized response. Plain objects are the wire format;
172
+ * a JSON string is accepted too for robustness.
173
+ */
174
+ declare function parseMobileHostResponse(data: unknown): MobileHostResponse | null;
175
+
176
+ export { type AuthTokenMessage as A, type MobileHostRequest as M, type ScannedPerson as S, MobileHostRequestType as a, type MobileHostRequestMap as b, type MobileHostResponse as c, type MobileHostResponseMap as d, MobileHostResponseType as e, type ScannerClosedMessage as f, type ScannerResultMessage as g, parseMobileHostResponse as h, parseMobileHostRequest as p };
@@ -0,0 +1,85 @@
1
+ import { U as Unsubscribe } from './client-CAMlFA8s.js';
2
+ import { M as MobileHostRequest, e as MobileHostResponseType, d as MobileHostResponseMap, S as ScannedPerson } from './mobile-app-protocol-Bt-UDbu5.js';
3
+ export { A as AuthTokenMessage, b as MobileHostRequestMap, a as MobileHostRequestType, c as MobileHostResponse, f as ScannerClosedMessage, g as ScannerResultMessage, p as parseMobileHostRequest, h as parseMobileHostResponse } from './mobile-app-protocol-Bt-UDbu5.js';
4
+
5
+ /**
6
+ * `@captello/ulc-webview-sdk/mobile-host` — for an **application embedded inside the
7
+ * Captello mobile app** (the meeting platform, Connexions, …).
8
+ *
9
+ * The mobile app hosts the application in an iframe and offers it native services over
10
+ * `postMessage`: an auth token, the badge scanner, back navigation, sharing.
11
+ * {@link MobileHostClient} is the embedded application's client for that channel; the
12
+ * mobile app's own side is `EmbeddedAppClient` (`@captello/ulc-webview-sdk/embedded-app`).
13
+ *
14
+ * Not to be confused with `CaptelloWebview`, which is for a page that embeds the
15
+ * capture webview — there your page is the host. See {@link MobileHostRequestType} for
16
+ * the wire format, which also differs (plain objects, SCREAMING_CASE).
17
+ *
18
+ * @example
19
+ * import { MobileHostClient } from "@captello/ulc-webview-sdk/mobile-host";
20
+ *
21
+ * const mobileHost = new MobileHostClient();
22
+ * const token = await mobileHost.requestAuthToken();
23
+ * mobileHost.notifyReady();
24
+ * const [person] = await mobileHost.openScanner();
25
+ */
26
+
27
+ /** Rejection reason from {@link MobileHostClient.openScanner} when the app reports a failure. */
28
+ declare class ScannerError extends Error {
29
+ constructor(message: string);
30
+ }
31
+ /** Listener for a specific mobile-app response type. */
32
+ type MobileHostResponseListener<T extends MobileHostResponseType> = (message: MobileHostResponseMap[T]) => void;
33
+ interface MobileHostClientOptions {
34
+ /** Window the mobile app is listening on. Defaults to `window.parent`. */
35
+ mobileHostWindow?: Window;
36
+ /** Window to receive the mobile app's responses on. Defaults to `window`. */
37
+ embeddedWindow?: Window;
38
+ /**
39
+ * `targetOrigin` for outgoing `postMessage` calls. Defaults to `"*"`: the mobile app's
40
+ * webview origin differs per platform (`capacitor://localhost`, `http://localhost`),
41
+ * and an application only uses this channel when it is running inside the app.
42
+ */
43
+ targetOrigin?: string;
44
+ }
45
+ /**
46
+ * Embedded-application-side client for the Captello mobile app that hosts it.
47
+ *
48
+ * Construct one per page and keep it for the page's lifetime. It attaches a single
49
+ * `message` listener on construction; call {@link destroy} to remove it.
50
+ */
51
+ declare class MobileHostClient {
52
+ private readonly mobileHostWindow;
53
+ private readonly embeddedWindow;
54
+ private readonly targetOrigin;
55
+ private readonly listeners;
56
+ private readonly boundHandler;
57
+ private destroyed;
58
+ constructor(options?: MobileHostClientOptions);
59
+ /** Post a request to the mobile app. Prefer the typed methods; this is the escape hatch. */
60
+ send(request: MobileHostRequest): void;
61
+ /** Subscribe to a response type. Returns a handle with `unsubscribe()` (also callable). */
62
+ on<T extends MobileHostResponseType>(type: T, listener: MobileHostResponseListener<T>): Unsubscribe;
63
+ /** Tell the mobile app the application is ready; it hides its loading spinner. */
64
+ notifyReady(): void;
65
+ /** Show a user-facing error in the mobile app. */
66
+ notifyError(message: string): void;
67
+ /** Ask the mobile app to close the application. */
68
+ navigateBack(): void;
69
+ /** Resolves with the magic token the mobile app mints for the current user. */
70
+ requestAuthToken(): Promise<string>;
71
+ /**
72
+ * Opens the mobile app's badge scanner and resolves once the session ends with every
73
+ * person it captured, in scan order — several for a group scan, none if the user
74
+ * cancelled. Rejects with a {@link ScannerError} if the app reports a failure.
75
+ *
76
+ * A badge whose lookup returned nothing is still included, with its `badgeId` and
77
+ * empty `fields`, so it can be linked to the attendee.
78
+ */
79
+ openScanner(): Promise<ScannedPerson[]>;
80
+ /** Remove the `message` listener and drop every subscription. Safe to call more than once. */
81
+ destroy(): void;
82
+ private handleMessage;
83
+ }
84
+
85
+ export { MobileHostClient, type MobileHostClientOptions, MobileHostRequest, type MobileHostResponseListener, MobileHostResponseMap, MobileHostResponseType, ScannedPerson, ScannerError };
@@ -0,0 +1,123 @@
1
+ import { makeSubscription, parseMobileHostResponse } from './chunk-XI5MIDBA.js';
2
+ export { MobileHostRequestType, MobileHostResponseType, parseMobileHostRequest, parseMobileHostResponse } from './chunk-XI5MIDBA.js';
3
+
4
+ // src/mobile-host.ts
5
+ var ScannerError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "ScannerError";
9
+ }
10
+ };
11
+ var MobileHostClient = class {
12
+ constructor(options = {}) {
13
+ this.listeners = /* @__PURE__ */ new Map();
14
+ this.boundHandler = (event) => this.handleMessage(event);
15
+ this.destroyed = false;
16
+ const embeddedWindow = options.embeddedWindow ?? (typeof window !== "undefined" ? window : void 0);
17
+ if (!embeddedWindow) {
18
+ throw new Error(
19
+ "MobileHostClient: no window available. Pass `embeddedWindow` when constructing outside a browser."
20
+ );
21
+ }
22
+ this.embeddedWindow = embeddedWindow;
23
+ this.mobileHostWindow = options.mobileHostWindow ?? embeddedWindow.parent;
24
+ this.targetOrigin = options.targetOrigin ?? "*";
25
+ this.embeddedWindow.addEventListener("message", this.boundHandler);
26
+ }
27
+ /** Post a request to the mobile app. Prefer the typed methods; this is the escape hatch. */
28
+ send(request) {
29
+ if (this.destroyed) return;
30
+ this.mobileHostWindow.postMessage(request, this.targetOrigin);
31
+ }
32
+ /** Subscribe to a response type. Returns a handle with `unsubscribe()` (also callable). */
33
+ on(type, listener) {
34
+ let set = this.listeners.get(type);
35
+ if (!set) {
36
+ set = /* @__PURE__ */ new Set();
37
+ this.listeners.set(type, set);
38
+ }
39
+ set.add(listener);
40
+ return makeSubscription(() => {
41
+ set?.delete(listener);
42
+ });
43
+ }
44
+ /** Tell the mobile app the application is ready; it hides its loading spinner. */
45
+ notifyReady() {
46
+ this.send({ type: "APP_READY" /* AppReady */ });
47
+ }
48
+ /** Show a user-facing error in the mobile app. */
49
+ notifyError(message) {
50
+ this.send({ type: "ERROR" /* Error */, message });
51
+ }
52
+ /** Ask the mobile app to close the application. */
53
+ navigateBack() {
54
+ this.send({ type: "NAVIGATE_BACK" /* NavigateBack */ });
55
+ }
56
+ /** Resolves with the magic token the mobile app mints for the current user. */
57
+ requestAuthToken() {
58
+ return new Promise((resolve) => {
59
+ const off = this.on("AUTH_TOKEN" /* AuthToken */, (message) => {
60
+ off();
61
+ resolve(message.token);
62
+ });
63
+ this.send({ type: "REQUEST_AUTH_TOKEN" /* RequestAuthToken */ });
64
+ });
65
+ }
66
+ /**
67
+ * Opens the mobile app's badge scanner and resolves once the session ends with every
68
+ * person it captured, in scan order — several for a group scan, none if the user
69
+ * cancelled. Rejects with a {@link ScannerError} if the app reports a failure.
70
+ *
71
+ * A badge whose lookup returned nothing is still included, with its `badgeId` and
72
+ * empty `fields`, so it can be linked to the attendee.
73
+ */
74
+ openScanner() {
75
+ return new Promise((resolve, reject) => {
76
+ const people = [];
77
+ const done = () => {
78
+ offResult();
79
+ offClosed();
80
+ };
81
+ const offResult = this.on("ULC_FORM_SCANNER_RESULT" /* ScannerResult */, (message) => {
82
+ if (!Array.isArray(message.result)) {
83
+ done();
84
+ reject(new ScannerError(message.message || "Scan failed."));
85
+ return;
86
+ }
87
+ people.push({
88
+ badgeId: typeof message.badgeId === "string" ? message.badgeId : "",
89
+ fields: message.result
90
+ });
91
+ if (!("badgeId" in message)) {
92
+ done();
93
+ resolve(people);
94
+ }
95
+ });
96
+ const offClosed = this.on("ULC_FORM_SCANNER_CLOSED" /* ScannerClosed */, () => {
97
+ done();
98
+ resolve(people);
99
+ });
100
+ this.send({ type: "OPEN_ULC_FORM_SCANNER" /* OpenScanner */ });
101
+ });
102
+ }
103
+ /** Remove the `message` listener and drop every subscription. Safe to call more than once. */
104
+ destroy() {
105
+ if (this.destroyed) return;
106
+ this.destroyed = true;
107
+ this.embeddedWindow.removeEventListener("message", this.boundHandler);
108
+ this.listeners.clear();
109
+ }
110
+ handleMessage(event) {
111
+ const message = parseMobileHostResponse(event.data);
112
+ if (!message) return;
113
+ const set = this.listeners.get(message.type);
114
+ if (!set) return;
115
+ for (const listener of Array.from(set)) {
116
+ listener(message);
117
+ }
118
+ }
119
+ };
120
+
121
+ export { MobileHostClient, ScannerError };
122
+ //# sourceMappingURL=mobile-host.js.map
123
+ //# sourceMappingURL=mobile-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mobile-host.ts"],"names":[],"mappings":"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","file":"mobile-host.js","sourcesContent":["/**\n * `@captello/ulc-webview-sdk/mobile-host` — for an **application embedded inside the\n * Captello mobile app** (the meeting platform, Connexions, …).\n *\n * The mobile app hosts the application in an iframe and offers it native services over\n * `postMessage`: an auth token, the badge scanner, back navigation, sharing.\n * {@link MobileHostClient} is the embedded application's client for that channel; the\n * mobile app's own side is `EmbeddedAppClient` (`@captello/ulc-webview-sdk/embedded-app`).\n *\n * Not to be confused with `CaptelloWebview`, which is for a page that embeds the\n * capture webview — there your page is the host. See {@link MobileHostRequestType} for\n * the wire format, which also differs (plain objects, SCREAMING_CASE).\n *\n * @example\n * import { MobileHostClient } from \"@captello/ulc-webview-sdk/mobile-host\";\n *\n * const mobileHost = new MobileHostClient();\n * const token = await mobileHost.requestAuthToken();\n * mobileHost.notifyReady();\n * const [person] = await mobileHost.openScanner();\n */\n\nimport type { Unsubscribe } from \"./client\";\nimport {\n makeSubscription,\n MobileHostRequestType,\n MobileHostResponseType,\n parseMobileHostResponse,\n} from \"./mobile-app-protocol\";\nimport type { MobileHostRequest, MobileHostResponseMap, ScannedPerson } from \"./mobile-app-protocol\";\n\nexport {\n MobileHostRequestType,\n MobileHostResponseType,\n parseMobileHostRequest,\n parseMobileHostResponse,\n} from \"./mobile-app-protocol\";\nexport type {\n MobileHostRequest,\n MobileHostRequestMap,\n MobileHostResponse,\n MobileHostResponseMap,\n AuthTokenMessage,\n ScannerResultMessage,\n ScannerClosedMessage,\n ScannedPerson,\n} from \"./mobile-app-protocol\";\n\n/** Rejection reason from {@link MobileHostClient.openScanner} when the app reports a failure. */\nexport class ScannerError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ScannerError\";\n }\n}\n\n/** Listener for a specific mobile-app response type. */\nexport type MobileHostResponseListener<T extends MobileHostResponseType> = (message: MobileHostResponseMap[T]) => void;\n\nexport interface MobileHostClientOptions {\n /** Window the mobile app is listening on. Defaults to `window.parent`. */\n mobileHostWindow?: Window;\n /** Window to receive the mobile app's responses on. Defaults to `window`. */\n embeddedWindow?: Window;\n /**\n * `targetOrigin` for outgoing `postMessage` calls. Defaults to `\"*\"`: the mobile app's\n * webview origin differs per platform (`capacitor://localhost`, `http://localhost`),\n * and an application only uses this channel when it is running inside the app.\n */\n targetOrigin?: string;\n}\n\n/**\n * Embedded-application-side client for the Captello mobile app that hosts it.\n *\n * Construct one per page and keep it for the page's lifetime. It attaches a single\n * `message` listener on construction; call {@link destroy} to remove it.\n */\nexport class MobileHostClient {\n private readonly mobileHostWindow: Window;\n private readonly embeddedWindow: Window;\n private readonly targetOrigin: string;\n private readonly listeners = new Map<\n MobileHostResponseType,\n Set<MobileHostResponseListener<MobileHostResponseType>>\n >();\n private readonly boundHandler = (event: MessageEvent) => this.handleMessage(event);\n private destroyed = false;\n\n constructor(options: MobileHostClientOptions = {}) {\n const embeddedWindow = options.embeddedWindow ?? (typeof window !== \"undefined\" ? window : undefined);\n if (!embeddedWindow) {\n throw new Error(\n \"MobileHostClient: no window available. Pass `embeddedWindow` when constructing outside a browser.\",\n );\n }\n this.embeddedWindow = embeddedWindow;\n this.mobileHostWindow = options.mobileHostWindow ?? embeddedWindow.parent;\n this.targetOrigin = options.targetOrigin ?? \"*\";\n this.embeddedWindow.addEventListener(\"message\", this.boundHandler);\n }\n\n /** Post a request to the mobile app. Prefer the typed methods; this is the escape hatch. */\n send(request: MobileHostRequest): void {\n if (this.destroyed) return;\n this.mobileHostWindow.postMessage(request, this.targetOrigin);\n }\n\n /** Subscribe to a response type. Returns a handle with `unsubscribe()` (also callable). */\n on<T extends MobileHostResponseType>(type: T, listener: MobileHostResponseListener<T>): Unsubscribe {\n let set = this.listeners.get(type);\n if (!set) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener as MobileHostResponseListener<MobileHostResponseType>);\n return makeSubscription(() => {\n set?.delete(listener as MobileHostResponseListener<MobileHostResponseType>);\n });\n }\n\n /** Tell the mobile app the application is ready; it hides its loading spinner. */\n notifyReady(): void {\n this.send({ type: MobileHostRequestType.AppReady });\n }\n\n /** Show a user-facing error in the mobile app. */\n notifyError(message: string): void {\n this.send({ type: MobileHostRequestType.Error, message });\n }\n\n /** Ask the mobile app to close the application. */\n navigateBack(): void {\n this.send({ type: MobileHostRequestType.NavigateBack });\n }\n\n /** Resolves with the magic token the mobile app mints for the current user. */\n requestAuthToken(): Promise<string> {\n return new Promise((resolve) => {\n const off = this.on(MobileHostResponseType.AuthToken, (message) => {\n off();\n resolve(message.token);\n });\n this.send({ type: MobileHostRequestType.RequestAuthToken });\n });\n }\n\n /**\n * Opens the mobile app's badge scanner and resolves once the session ends with every\n * person it captured, in scan order — several for a group scan, none if the user\n * cancelled. Rejects with a {@link ScannerError} if the app reports a failure.\n *\n * A badge whose lookup returned nothing is still included, with its `badgeId` and\n * empty `fields`, so it can be linked to the attendee.\n */\n openScanner(): Promise<ScannedPerson[]> {\n return new Promise((resolve, reject) => {\n const people: ScannedPerson[] = [];\n const done = () => {\n offResult();\n offClosed();\n };\n const offResult = this.on(MobileHostResponseType.ScannerResult, (message) => {\n if (!Array.isArray(message.result)) {\n done();\n reject(new ScannerError(message.message || \"Scan failed.\"));\n return;\n }\n people.push({\n badgeId: typeof message.badgeId === \"string\" ? message.badgeId : \"\",\n fields: message.result,\n });\n // App builds that predate multi-person sessions post one result, without a\n // badgeId, and never a ScannerClosed — settle on it.\n if (!(\"badgeId\" in message)) {\n done();\n resolve(people);\n }\n });\n const offClosed = this.on(MobileHostResponseType.ScannerClosed, () => {\n done();\n resolve(people);\n });\n this.send({ type: MobileHostRequestType.OpenScanner });\n });\n }\n\n /** Remove the `message` listener and drop every subscription. Safe to call more than once. */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.embeddedWindow.removeEventListener(\"message\", this.boundHandler);\n this.listeners.clear();\n }\n\n private handleMessage(event: MessageEvent): void {\n const message = parseMobileHostResponse(event.data);\n if (!message) return;\n const set = this.listeners.get(message.type);\n if (!set) return;\n for (const listener of Array.from(set)) {\n listener(message);\n }\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-cZpygJTD.js';
2
- export { b as SubmissionError, c as SubmissionTimeoutError } from './client-cZpygJTD.js';
1
+ import { C as CaptelloWebviewOptions, F as FrameLike, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-CAMlFA8s.js';
2
+ export { b as SubmissionError, c as SubmissionTimeoutError } from './client-CAMlFA8s.js';
3
3
 
4
4
  /**
5
5
  * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.
@@ -17,9 +17,7 @@ export { b as SubmissionError, c as SubmissionTimeoutError } from './client-cZpy
17
17
  */
18
18
 
19
19
  /** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */
20
- type ElementOrFrame = HTMLIFrameElement | {
21
- contentWindow: Window | null;
22
- };
20
+ type ElementOrFrame = FrameLike;
23
21
  /** Options shared by every promise helper. */
24
22
  interface WaitOptions extends Pick<CaptelloWebviewOptions, "targetOrigin" | "hostWindow" | "matchSource"> {
25
23
  /**
package/dist/promises.js CHANGED
@@ -1,5 +1,5 @@
1
- import { CaptelloWebview } from './chunk-UUEUW2WL.js';
2
- export { SubmissionError, SubmissionTimeoutError } from './chunk-UUEUW2WL.js';
1
+ import { CaptelloWebview } from './chunk-ZPVXZW2B.js';
2
+ export { SubmissionError, SubmissionTimeoutError } from './chunk-ZPVXZW2B.js';
3
3
 
4
4
  // src/promises.ts
5
5
  var DEFAULT_TIMEOUT_MS = 6e4;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/promises.ts"],"names":[],"mappings":";;;;AAkCA,IAAM,kBAAA,GAAqB,GAAA;AAGpB,IAAM,mBAAA,GAAN,cAAkC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACoB,aACA,SAAA,EAClB;AACE,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,gBAAA,EAAmB,WAAW,CAAA,4BAAA,CAA8B,CAAA;AAH9E,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EAChB;AACJ;AAEA,SAAS,cAAc,OAAA,EAA8C;AACjE,EAAA,OAAO;AAAA,IACH,cAAc,OAAA,CAAQ,YAAA;AAAA,IACtB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,aAAa,OAAA,CAAQ,WAAA;AAAA;AAAA;AAAA,IAGrB,eAAA,EAAiB;AAAA,GACrB;AACJ;AASO,SAAS,cAAA,CACZ,KAAA,EACA,IAAA,EACA,OAAA,GAAuB,EAAC,EACM;AAC9B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,IAAI,OAAA,CAA+B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3D,IAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,IAAA,IAAI,KAAA;AAEJ,IAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAmB;AAC/B,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAC3C,MAAA,MAAA,CAAO,OAAA,EAAQ;AACf,MAAA,EAAA,EAAG;AAAA,IACP,CAAA;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,OAAA,KAAY,OAAO,MAAM,OAAA,CAAQ,OAAO,CAAC,CAAC,CAAA;AAE7D,IAAA,IAAI,SAAA,GAAY,CAAA,IAAK,SAAA,KAAc,QAAA,EAAU;AACzC,MAAA,KAAA,GAAQ,UAAA,CAAW,MAAM,MAAA,CAAO,MAAM,MAAA,CAAO,IAAI,mBAAA,CAAoB,IAAA,EAAM,SAAS,CAAC,CAAC,CAAA,EAAG,SAAS,CAAA;AAAA,IACtG;AAAA,EACJ,CAAC,CAAA;AACL;AAUO,SAAS,eAAA,CAAgB,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAAkB;AAC7F,EAAA,OAAO,eAAe,KAAA,EAAA,oBAAA,yBAA6C,OAAO,CAAA,CAAE,IAAA,CAAK,MAAM,MAAS,CAAA;AACpG;AAkBO,SAAS,UAAA,CAAW,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAA4B;AAClG,EAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,MAAA,CAAO,cAAc,SAAS,CAAA,CAAE,QAAQ,MAAM,MAAA,CAAO,SAAS,CAAA;AACzE","file":"promises.js","sourcesContent":["/**\n * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.\n *\n * Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are\n * fire-once-and-await utilities that take an iframe directly: create a short-lived\n * client internally, wait for the relevant message, then tear it down. Ideal for\n * `await`-style code (e.g. \"submit and get the body\", \"wait until the form loads\").\n *\n * @example\n * import { submitForm, waitForFormLoad } from \"@captello/ulc-webview-sdk/promises\";\n *\n * await waitForFormLoad(iframe, { targetOrigin });\n * const body = await submitForm(iframe, { targetOrigin });\n */\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions } from \"./client\";\nimport { OutboundMessageType } from \"./messages\";\nimport type { OutboundMessageMap, SubmissionBody } from \"./messages\";\n\nexport { SubmissionError, SubmissionTimeoutError } from \"./client\";\n\n/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */\ntype ElementOrFrame = HTMLIFrameElement | { contentWindow: Window | null };\n\n/** Options shared by every promise helper. */\nexport interface WaitOptions extends Pick<CaptelloWebviewOptions, \"targetOrigin\" | \"hostWindow\" | \"matchSource\"> {\n /**\n * How long to wait before rejecting. Defaults to 60_000ms. Pass `0` or `Infinity`\n * to wait indefinitely (the caller is then responsible for not leaking the wait).\n */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/** Rejection reason from {@link waitForMessage} / {@link waitForFormLoad} on timeout. */\nexport class MessageTimeoutError extends Error {\n constructor(\n public readonly messageType: string,\n public readonly timeoutMs: number,\n ) {\n super(`Timed out after ${timeoutMs}ms waiting for \"${messageType}\" from the Captello webview.`);\n this.name = \"MessageTimeoutError\";\n }\n}\n\nfunction clientOptions(options: WaitOptions): CaptelloWebviewOptions {\n return {\n targetOrigin: options.targetOrigin,\n hostWindow: options.hostWindow,\n matchSource: options.matchSource,\n // One-shot helpers act on a form assumed already loaded; never buffer their\n // sends waiting for a form_load_complete that may have already fired.\n queueUntilReady: false,\n };\n}\n\n/**\n * Resolves with the next outbound message of `type` from the webview, or rejects with\n * a {@link MessageTimeoutError} if none arrives within the timeout. The internal\n * listener is always removed before settling.\n *\n * @example const msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });\n */\nexport function waitForMessage<T extends OutboundMessageType>(\n frame: ElementOrFrame,\n type: T,\n options: WaitOptions = {},\n): Promise<OutboundMessageMap[T]> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise<OutboundMessageMap[T]>((resolve, reject) => {\n const client = new CaptelloWebview(frame, clientOptions(options));\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = (fn: () => void) => {\n if (timer !== undefined) clearTimeout(timer);\n client.destroy();\n fn();\n };\n\n client.once(type, (message) => settle(() => resolve(message)));\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => settle(() => reject(new MessageTimeoutError(type, timeoutMs))), timeoutMs);\n }\n });\n}\n\n/**\n * Resolves once the webview reports `form_load_complete`, or rejects with a\n * {@link MessageTimeoutError} on timeout.\n *\n * Note: this only catches a *future* load event. If the form may have already loaded\n * before you call this (e.g. you attach late), prefer subscribing with a long-lived\n * {@link CaptelloWebview} created before the iframe navigates.\n */\nexport function waitForFormLoad(frame: ElementOrFrame, options: WaitOptions = {}): Promise<void> {\n return waitForMessage(frame, OutboundMessageType.FormLoadComplete, options).then(() => undefined);\n}\n\n/**\n * Submits the form and awaits the outcome: resolves with the {@link SubmissionBody} on\n * `submission_body`, rejects with a `SubmissionError` (translated message) on\n * `form_error_message`, or a `SubmissionTimeoutError` if neither arrives in time.\n *\n * Standalone equivalent of {@link CaptelloWebview.submitAndWait} for code that doesn't\n * hold a long-lived client — it creates one, submits, and tears it down.\n *\n * @example\n * try {\n * const body = await submitForm(iframeRef.current!, { targetOrigin });\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\nexport function submitForm(frame: ElementOrFrame, options: WaitOptions = {}): Promise<SubmissionBody> {\n const client = new CaptelloWebview(frame, clientOptions(options));\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return client.submitAndWait(timeoutMs).finally(() => client.destroy());\n}\n"]}
1
+ {"version":3,"sources":["../src/promises.ts"],"names":[],"mappings":";;;;AAkCA,IAAM,kBAAA,GAAqB,GAAA;AAGpB,IAAM,mBAAA,GAAN,cAAkC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACoB,aACA,SAAA,EAClB;AACE,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,gBAAA,EAAmB,WAAW,CAAA,4BAAA,CAA8B,CAAA;AAH9E,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EAChB;AACJ;AAEA,SAAS,cAAc,OAAA,EAA8C;AACjE,EAAA,OAAO;AAAA,IACH,cAAc,OAAA,CAAQ,YAAA;AAAA,IACtB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,aAAa,OAAA,CAAQ,WAAA;AAAA;AAAA;AAAA,IAGrB,eAAA,EAAiB;AAAA,GACrB;AACJ;AASO,SAAS,cAAA,CACZ,KAAA,EACA,IAAA,EACA,OAAA,GAAuB,EAAC,EACM;AAC9B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,IAAI,OAAA,CAA+B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3D,IAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,IAAA,IAAI,KAAA;AAEJ,IAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAmB;AAC/B,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAC3C,MAAA,MAAA,CAAO,OAAA,EAAQ;AACf,MAAA,EAAA,EAAG;AAAA,IACP,CAAA;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,OAAA,KAAY,OAAO,MAAM,OAAA,CAAQ,OAAO,CAAC,CAAC,CAAA;AAE7D,IAAA,IAAI,SAAA,GAAY,CAAA,IAAK,SAAA,KAAc,QAAA,EAAU;AACzC,MAAA,KAAA,GAAQ,UAAA,CAAW,MAAM,MAAA,CAAO,MAAM,MAAA,CAAO,IAAI,mBAAA,CAAoB,IAAA,EAAM,SAAS,CAAC,CAAC,CAAA,EAAG,SAAS,CAAA;AAAA,IACtG;AAAA,EACJ,CAAC,CAAA;AACL;AAUO,SAAS,eAAA,CAAgB,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAAkB;AAC7F,EAAA,OAAO,eAAe,KAAA,EAAA,oBAAA,yBAA6C,OAAO,CAAA,CAAE,IAAA,CAAK,MAAM,MAAS,CAAA;AACpG;AAkBO,SAAS,UAAA,CAAW,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAA4B;AAClG,EAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,MAAA,CAAO,cAAc,SAAS,CAAA,CAAE,QAAQ,MAAM,MAAA,CAAO,SAAS,CAAA;AACzE","file":"promises.js","sourcesContent":["/**\n * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.\n *\n * Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are\n * fire-once-and-await utilities that take an iframe directly: create a short-lived\n * client internally, wait for the relevant message, then tear it down. Ideal for\n * `await`-style code (e.g. \"submit and get the body\", \"wait until the form loads\").\n *\n * @example\n * import { submitForm, waitForFormLoad } from \"@captello/ulc-webview-sdk/promises\";\n *\n * await waitForFormLoad(iframe, { targetOrigin });\n * const body = await submitForm(iframe, { targetOrigin });\n */\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions, FrameLike } from \"./client\";\nimport { OutboundMessageType } from \"./messages\";\nimport type { OutboundMessageMap, SubmissionBody } from \"./messages\";\n\nexport { SubmissionError, SubmissionTimeoutError } from \"./client\";\n\n/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */\ntype ElementOrFrame = FrameLike;\n\n/** Options shared by every promise helper. */\nexport interface WaitOptions extends Pick<CaptelloWebviewOptions, \"targetOrigin\" | \"hostWindow\" | \"matchSource\"> {\n /**\n * How long to wait before rejecting. Defaults to 60_000ms. Pass `0` or `Infinity`\n * to wait indefinitely (the caller is then responsible for not leaking the wait).\n */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/** Rejection reason from {@link waitForMessage} / {@link waitForFormLoad} on timeout. */\nexport class MessageTimeoutError extends Error {\n constructor(\n public readonly messageType: string,\n public readonly timeoutMs: number,\n ) {\n super(`Timed out after ${timeoutMs}ms waiting for \"${messageType}\" from the Captello webview.`);\n this.name = \"MessageTimeoutError\";\n }\n}\n\nfunction clientOptions(options: WaitOptions): CaptelloWebviewOptions {\n return {\n targetOrigin: options.targetOrigin,\n hostWindow: options.hostWindow,\n matchSource: options.matchSource,\n // One-shot helpers act on a form assumed already loaded; never buffer their\n // sends waiting for a form_load_complete that may have already fired.\n queueUntilReady: false,\n };\n}\n\n/**\n * Resolves with the next outbound message of `type` from the webview, or rejects with\n * a {@link MessageTimeoutError} if none arrives within the timeout. The internal\n * listener is always removed before settling.\n *\n * @example const msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });\n */\nexport function waitForMessage<T extends OutboundMessageType>(\n frame: ElementOrFrame,\n type: T,\n options: WaitOptions = {},\n): Promise<OutboundMessageMap[T]> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise<OutboundMessageMap[T]>((resolve, reject) => {\n const client = new CaptelloWebview(frame, clientOptions(options));\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = (fn: () => void) => {\n if (timer !== undefined) clearTimeout(timer);\n client.destroy();\n fn();\n };\n\n client.once(type, (message) => settle(() => resolve(message)));\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => settle(() => reject(new MessageTimeoutError(type, timeoutMs))), timeoutMs);\n }\n });\n}\n\n/**\n * Resolves once the webview reports `form_load_complete`, or rejects with a\n * {@link MessageTimeoutError} on timeout.\n *\n * Note: this only catches a *future* load event. If the form may have already loaded\n * before you call this (e.g. you attach late), prefer subscribing with a long-lived\n * {@link CaptelloWebview} created before the iframe navigates.\n */\nexport function waitForFormLoad(frame: ElementOrFrame, options: WaitOptions = {}): Promise<void> {\n return waitForMessage(frame, OutboundMessageType.FormLoadComplete, options).then(() => undefined);\n}\n\n/**\n * Submits the form and awaits the outcome: resolves with the {@link SubmissionBody} on\n * `submission_body`, rejects with a `SubmissionError` (translated message) on\n * `form_error_message`, or a `SubmissionTimeoutError` if neither arrives in time.\n *\n * Standalone equivalent of {@link CaptelloWebview.submitAndWait} for code that doesn't\n * hold a long-lived client — it creates one, submits, and tears it down.\n *\n * @example\n * try {\n * const body = await submitForm(iframeRef.current!, { targetOrigin });\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\nexport function submitForm(frame: ElementOrFrame, options: WaitOptions = {}): Promise<SubmissionBody> {\n const client = new CaptelloWebview(frame, clientOptions(options));\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return client.submitAndWait(timeoutMs).finally(() => client.destroy());\n}\n"]}
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, IframeHTMLAttributes, ReactNode, RefCallback } from 'react';
3
- import { C as CaptelloWebviewOptions, S as SubmissionBody, d as OutboundMessage, e as SubmissionPrefill, P as PrefillInfoItem, f as CaptelloWebview, V as ValidationTarget } from './client-cZpygJTD.js';
4
- export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-cZpygJTD.js';
3
+ import { C as CaptelloWebviewOptions, S as SubmissionBody, d as OutboundMessage, e as SubmissionPrefill, P as PrefillInfoItem, T as TranscribeScannerRequestHandler, f as CaptelloWebview, V as ValidationTarget } from './client-CAMlFA8s.js';
4
+ export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-CAMlFA8s.js';
5
5
  import { EmbedUrlOptions } from './index.js';
6
6
 
7
7
  /**
@@ -80,6 +80,16 @@ interface UseCaptelloWebviewOptions extends Omit<CaptelloWebviewOptions, "target
80
80
  * defaultFormValues={{ info: [{ ll_field_unique_identifier: "Email", value: user.email }] }}
81
81
  */
82
82
  defaultFormValues?: DefaultFormValues;
83
+ /**
84
+ * Answer the form's transcribe requests (its transcribe button, shown when the
85
+ * embed URL sets `showTranscribeButton`). Same semantics as
86
+ * {@link CaptelloWebview.onTranscribeScannerRequest}: return the fields (a promise
87
+ * is fine) and they are sent back as the result, or answer through the `reply`
88
+ * second argument when the work is callback-style; a thrown `Error`'s message is
89
+ * shown to the user. Must be set from the first render (it is wired when the iframe
90
+ * attaches); the latest function is always the one invoked, so inline closures are fine.
91
+ */
92
+ onTranscribeScannerRequest?: TranscribeScannerRequestHandler;
83
93
  }
84
94
  /** Readiness of the embedded form. */
85
95
  type CaptelloWebviewStatus = "loading" | "ready" | "error";
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
- import { buildEmbedUrl } from './chunk-4E7OW4RJ.js';
2
- import { CaptelloWebview, OutboundMessageType } from './chunk-UUEUW2WL.js';
3
- export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-UUEUW2WL.js';
1
+ import { buildEmbedUrl } from './chunk-ZX4AKPWF.js';
2
+ import { CaptelloWebview, OutboundMessageType } from './chunk-ZPVXZW2B.js';
3
+ export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-ZPVXZW2B.js';
4
4
  import { forwardRef, useState, useRef, useCallback, useImperativeHandle, useEffect } from 'react';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
6
 
@@ -77,6 +77,17 @@ function useCaptelloWebview(options) {
77
77
  })
78
78
  );
79
79
  }
80
+ if (optionsRef.current.onTranscribeScannerRequest) {
81
+ offs.push(
82
+ client.onTranscribeScannerRequest((request, reply) => {
83
+ const handler = optionsRef.current.onTranscribeScannerRequest;
84
+ if (!handler) {
85
+ throw new Error("Transcription failed.");
86
+ }
87
+ return handler(request, reply);
88
+ })
89
+ );
90
+ }
80
91
  teardown.current = () => {
81
92
  for (const off of offs) off();
82
93
  client.destroy();