@captello/ulc-webview-sdk 1.0.0 → 1.1.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.
- package/CHANGELOG.md +18 -0
- package/README.md +11 -4
- package/dist/{chunk-PFFBCSJ2.js → chunk-UUEUW2WL.js} +2 -2
- package/dist/chunk-UUEUW2WL.js.map +1 -0
- package/dist/{client-CalIoKT6.d.ts → client-cZpygJTD.d.ts} +40 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/promises.d.ts +2 -2
- package/dist/promises.js +2 -2
- package/dist/react.d.ts +9 -4
- package/dist/react.js +2 -2
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PFFBCSJ2.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 20f91de: `prefill({ submission })` now accepts either submission `data` shape, and the webview normalizes
|
|
8
|
+
whichever it receives:
|
|
9
|
+
|
|
10
|
+
- `SubmissionPrefillDataItem[]` — the array the submissions API returns for a submitted
|
|
11
|
+
submission, so a fetched `submission.data` passes through as-is.
|
|
12
|
+
- `DraftSubmissionData` — values flat-keyed by element / sub-element id. This is what a received
|
|
13
|
+
`SubmissionBody` carries and how a draft is stored, so an `onSubmissionBody` payload
|
|
14
|
+
round-trips back into `prefill()` directly.
|
|
15
|
+
|
|
16
|
+
Previously the webview called `.forEach()` on `submission.data` unconditionally, so the flat
|
|
17
|
+
record — the shape the 1.0.0 docs and `defaultFormValues` examples showed — failed at runtime with
|
|
18
|
+
"data.forEach is not a function". Both `SubmissionPrefillDataItem` and `DraftSubmissionData` are
|
|
19
|
+
exported for callers that want to name either shape.
|
|
20
|
+
|
|
3
21
|
## 1.0.0
|
|
4
22
|
|
|
5
23
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -334,8 +334,13 @@ point of view.
|
|
|
334
334
|
|
|
335
335
|
A single `prefill({ submission?, info? })` carries either or both payloads:
|
|
336
336
|
|
|
337
|
-
- `submission` — a `SubmissionPrefill` (a
|
|
338
|
-
|
|
337
|
+
- `submission` — a `SubmissionPrefill` (a partial `{ data?, ... }` you assemble). Its `data`
|
|
338
|
+
accepts either shape you might be holding, and the webview normalizes whichever it gets:
|
|
339
|
+
- a `SubmissionPrefillDataItem[]` — the array returned by the submissions API, so a
|
|
340
|
+
`submission.data` fetched from there passes through as-is;
|
|
341
|
+
- a `DraftSubmissionData` — values flat-keyed by element / sub-element id. This is the shape
|
|
342
|
+
a received `SubmissionBody` carries and the shape a draft is stored in, so an
|
|
343
|
+
`onSubmissionBody` payload round-trips directly.
|
|
339
344
|
- `info` — a list of `PrefillInfoItem`. The webview matches each item by
|
|
340
345
|
`ll_field_unique_identifier` (e.g. `"FirstName"`, `"Email"`); `ll_field_id` is optional
|
|
341
346
|
metadata (number or string) and `value` may be a string or boolean.
|
|
@@ -548,8 +553,10 @@ Hold the `CaptelloWebview` instance in a ref/context and call `submit()` / `rese
|
|
|
548
553
|
`ll_field_unique_identifier`; `ll_field_id` is optional (number or string) and `value`
|
|
549
554
|
may be a string or boolean — so existing payloads with numeric ids and boolean values
|
|
550
555
|
type-check as-is. `prefill({ submission })` accepts a loose `SubmissionPrefill`, so a
|
|
551
|
-
|
|
552
|
-
|
|
556
|
+
partial `{ email?, data?, ... }` you assemble type-checks as-is, and `data` may be either a
|
|
557
|
+
`SubmissionPrefillDataItem[]` (the submissions API array) or a flat `DraftSubmissionData`
|
|
558
|
+
record (the shape a received `SubmissionBody` carries) — a previously-received body can be
|
|
559
|
+
passed back directly.
|
|
553
560
|
|
|
554
561
|
**Out of scope.** If your app is _itself_ embedded inside the webview shell and talks to
|
|
555
562
|
_its_ parent via `window.parent.postMessage` (e.g. relaying `email`/`clientId`, or custom
|
|
@@ -307,5 +307,5 @@ var CaptelloWebview = class {
|
|
|
307
307
|
};
|
|
308
308
|
|
|
309
309
|
export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, parseOutboundMessage };
|
|
310
|
-
//# sourceMappingURL=chunk-
|
|
311
|
-
//# sourceMappingURL=chunk-
|
|
310
|
+
//# sourceMappingURL=chunk-UUEUW2WL.js.map
|
|
311
|
+
//# sourceMappingURL=chunk-UUEUW2WL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/client.ts"],"names":["OutboundMessageType","InboundMessageType"],"mappings":"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","file":"chunk-UUEUW2WL.js","sourcesContent":["/**\n * The message protocol exchanged between the Captello capture webview (the iframe)\n * and its host page.\n *\n * Wire format (this is the contract — match it exactly):\n * - Every message is a JSON **string**. The webview sends outbound messages with\n * `JSON.stringify(message)` and reads inbound messages with `JSON.parse(event.data)`.\n * A host that posts a raw object instead of a string will be ignored, because the\n * webview's parser produces a non-object and bails.\n * - Every message is an object with a `type` discriminator. Inbound and outbound\n * types are disjoint string enums.\n *\n * Direction is named from the **webview's** point of view:\n * - {@link OutboundMessageType}: webview → host (the host listens for these).\n * - {@link InboundMessageType}: host → webview (the host sends these).\n */\n\nimport type { VisibleSubmissionDataItem } from \"./submission-data\";\n\n/* ------------------------------------------------------------------ *\n * Outbound: webview → host\n * ------------------------------------------------------------------ */\n\n/** Message `type` values the webview emits to its host. */\nexport enum OutboundMessageType {\n /** The form finished loading and rendering. Safe to interact with it after this. */\n FormLoadComplete = \"form_load_complete\",\n /** A user-facing error occurred; `data` is the translated, display-ready message. */\n FormErrorMessage = \"form_error_message\",\n /**\n * Emitted for embedded forms instead of submitting directly: `data` is the full\n * submission body for the host to persist/forward.\n */\n SubmissionBody = \"submission_body\",\n /** The form was submitted successfully. `action` indicates whether it was a new submission or an update. */\n FormSubmitSuccess = \"form_submit_success\",\n /** Connexions: the host should perform the profile redirect (embed mode). */\n ConnexionsProfileRedirect = \"connexions_profile_redirect\",\n /** Connexions: the host should trigger the vCard download (embed mode). */\n ConnexionsDownloadVcard = \"connexions_download_vcard\",\n}\n\n/**\n * Opaque submission payload carried by {@link OutboundMessageType.SubmissionBody}.\n *\n * This mirrors the webview's internal `FormSubmission` model. It is intentionally\n * typed as an open record here so the SDK stays decoupled from the app's full model\n * graph; the documented fields below are stable, the rest are passed through as-is.\n * Host code that needs the deep element-value types should treat `data` as untyped\n * and key it by element id (e.g. `\"element_12\"`, `\"element_12_3\"`).\n */\nexport interface SubmissionBody {\n id: number;\n form_id: number;\n prospect_id: number;\n email: string;\n first_name: string;\n last_name: string;\n full_name: string;\n company: string;\n phone: string;\n /** Submitted values keyed by element id / sub-element id. */\n data: Record<string, unknown>;\n /**\n * Visible, filled elements ready to render as key/value rows — one item per\n * element, discriminated by `element_type` (narrow on it for a precisely-typed\n * `element_value`). See {@link VisibleSubmissionDataItem}. May be absent on older\n * webview builds.\n */\n visible_submissions_data?: VisibleSubmissionDataItem[];\n submission_date: string;\n /** Query-string params the webview was loaded with, echoed back on submit. */\n query_parameters?: Record<string, string>;\n /** Additional fields from the webview's submission model are passed through verbatim. */\n [key: string]: unknown;\n}\n\n/**\n * Submitted values flat-keyed by element / sub-element id (e.g. `\"element_12\"`, `\"element_12_3\"`).\n *\n * This is the webview's own internal shape rather than the submissions API's: it is what a\n * received {@link SubmissionBody} carries, and what the backend stores verbatim for a draft and\n * hands back unchanged — hence the name. Either can be passed to\n * {@link SubmissionPrefill.data} as-is, so an `onSubmissionBody` payload round-trips.\n *\n * Contrast {@link SubmissionPrefillDataItem}, the array shape the submissions API returns.\n */\nexport type DraftSubmissionData = Record<string, unknown>;\n\n/**\n * One submitted value in {@link SubmissionPrefill.data}. Mirrors the submissions API's\n * `SubmissionDataResponse` shape, so a `submission.data` array fetched from that API can\n * be passed straight through as-is.\n */\nexport interface SubmissionPrefillDataItem {\n element_id: string;\n element_title: string;\n value: string;\n /** Present only when the element has sub-elements (e.g. a simple name or address). */\n value_splitted?: Record<string, string>;\n}\n\n/**\n * Loose submission shape accepted when **pre-filling** the form (host → webview).\n *\n * Distinct from {@link SubmissionBody} in that every field is optional — assemble a partial\n * object from your own data rather than populating a whole body.\n *\n * `data` accepts either shape a host is likely to be holding, and the webview normalizes\n * whichever it receives:\n * - {@link SubmissionPrefillDataItem}`[]` — one entry per element, as the submissions API\n * returns it for a submitted submission. Pass a fetched `submission.data` array straight\n * through.\n * - {@link DraftSubmissionData} — values flat-keyed by element / sub-element id. This is the\n * shape a received {@link SubmissionBody} carries, and the shape a draft is stored and\n * returned in, so `onSubmissionBody` payloads round-trip directly.\n */\nexport interface SubmissionPrefill {\n /**\n * Submitted values, as either the submissions API's array (see\n * {@link SubmissionPrefillDataItem}) or a flat {@link DraftSubmissionData} record.\n */\n data?: SubmissionPrefillDataItem[] | DraftSubmissionData;\n [key: string]: unknown;\n}\n\ninterface FormLoadCompleteMessage {\n type: OutboundMessageType.FormLoadComplete;\n}\ninterface FormSubmitSuccessMessage {\n type: OutboundMessageType.FormSubmitSuccess;\n action: \"create\" | \"update\";\n}\ninterface FormErrorMessageMessage {\n type: OutboundMessageType.FormErrorMessage;\n /** Translated, display-ready error text. */\n data: string;\n}\ninterface SubmissionBodyMessage {\n type: OutboundMessageType.SubmissionBody;\n data: SubmissionBody;\n}\ninterface ConnexionsProfileRedirectMessage {\n type: OutboundMessageType.ConnexionsProfileRedirect;\n}\ninterface ConnexionsDownloadVcardMessage {\n type: OutboundMessageType.ConnexionsDownloadVcard;\n}\n\n/** Discriminated union of every message the webview can emit to its host. */\nexport type OutboundMessage =\n | FormLoadCompleteMessage\n | FormSubmitSuccessMessage\n | FormErrorMessageMessage\n | SubmissionBodyMessage\n | ConnexionsProfileRedirectMessage\n | ConnexionsDownloadVcardMessage;\n\n/** Maps each outbound `type` to its full message shape (used by the client's `.on`). */\nexport type OutboundMessageMap = {\n [M in OutboundMessage as M[\"type\"]]: M;\n};\n\n/* ------------------------------------------------------------------ *\n * Inbound: host → webview\n * ------------------------------------------------------------------ */\n\n/** Message `type` values the host sends into the webview. */\nexport enum InboundMessageType {\n /** Programmatically trigger form submission (as if the user pressed submit). */\n Submit = \"submit_form\",\n /** Reset the form, clearing all entered values. */\n Reset = \"reset_form\",\n /** Pre-fill the form with existing data. */\n FormPrefill = \"form_prefill\",\n /** Switch the current submission into draft-update mode. */\n UpdateDraft = \"update_draft\",\n /** Run validation against a target field (or the whole form). */\n TriggerValidation = \"trigger_validation\",\n}\n\n/** Shape selector for {@link InboundMessageType.FormPrefill} payloads. */\nexport enum PrefillDataType {\n UlcSubmissionAndInfo = \"ulc_submission_and_info\",\n}\n\n/** Targets for {@link InboundMessageType.TriggerValidation}. */\nexport type ValidationTarget = \"invitation_code\" | \"email\" | \"all\";\n\n/**\n * A single transcription field/value item for the `info` prefill array.\n *\n * The webview matches each item to a form element by `ll_field_unique_identifier`\n * alone (e.g. `\"FirstName\"`, `\"Email\"`); `ll_field_id` is catalog metadata and is not\n * used for matching, so it is accepted as either a number or a string. `value` is\n * typically a string but may be a boolean (e.g. the PII opt-out field).\n */\nexport interface PrefillInfoItem {\n ll_field_unique_identifier: string;\n ll_field_id?: string | number;\n value: string | boolean;\n}\n\ninterface SubmitMessage {\n type: InboundMessageType.Submit;\n}\ninterface ResetMessage {\n type: InboundMessageType.Reset;\n}\ninterface UpdateDraftMessage {\n type: InboundMessageType.UpdateDraft;\n}\ninterface TriggerValidationMessage {\n type: InboundMessageType.TriggerValidation;\n target: ValidationTarget;\n}\ninterface PrefillMessage {\n type: InboundMessageType.FormPrefill;\n data_type: PrefillDataType.UlcSubmissionAndInfo;\n data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] };\n}\n\n/** Discriminated union of every message the host can send into the webview. */\nexport type InboundMessage =\n | SubmitMessage\n | ResetMessage\n | UpdateDraftMessage\n | TriggerValidationMessage\n | PrefillMessage;\n\n/* ------------------------------------------------------------------ *\n * Runtime guards / parsing\n * ------------------------------------------------------------------ */\n\nconst OUTBOUND_TYPES: ReadonlySet<string> = new Set(Object.values(OutboundMessageType));\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parses a raw `MessageEvent.data` value into a typed {@link OutboundMessage}, or\n * returns `null` if it is not a recognized Captello webview message.\n *\n * Accepts either a JSON string (the webview always sends strings) or an\n * already-parsed object, so it is robust to hosts/proxies that pre-parse.\n */\nexport function parseOutboundMessage(data: unknown): OutboundMessage | null {\n let value: unknown = data;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n return null;\n }\n }\n if (!isPlainObject(value)) return null;\n if (typeof value[\"type\"] !== \"string\" || !OUTBOUND_TYPES.has(value[\"type\"])) return null;\n return value as unknown as OutboundMessage;\n}\n","import { InboundMessageType, OutboundMessageType, parseOutboundMessage, PrefillDataType } from \"./messages\";\nimport type {\n InboundMessage,\n OutboundMessage,\n OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/** Listener for a specific outbound message type. */\nexport type OutboundListener<T extends OutboundMessageType> = (message: OutboundMessageMap[T]) => void;\n\n/** Listener for every outbound message (used by {@link CaptelloWebview.onAny}). */\nexport type AnyOutboundListener = (message: OutboundMessage) => void;\n\n/** Unsubscribe handle returned by every `on*` method. Calling it removes the listener. */\nexport type Unsubscribe = () => void;\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when the webview reports\n * a `form_error_message`. `message` is the translated, display-ready text.\n */\nexport class SubmissionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SubmissionError\";\n }\n}\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when no `submission_body`\n * or `form_error_message` arrives within the timeout.\n */\nexport class SubmissionTimeoutError extends Error {\n constructor(public readonly timeoutMs: number) {\n super(`Captello webview did not respond to submit within ${timeoutMs}ms.`);\n this.name = \"SubmissionTimeoutError\";\n }\n}\n\nexport interface CaptelloWebviewOptions {\n /**\n * Origin to validate incoming messages against and to target outgoing messages.\n * Strongly recommended — set it to the webview's origin (e.g.\n * `\"https://capture.captello.com\"`), e.g. `new URL(embedUrl).origin`.\n *\n * Defaults to `\"*\"`, which accepts messages from any origin and posts without an\n * origin check. Only acceptable for trusted/local development.\n */\n targetOrigin?: string;\n /**\n * The window to attach the `message` listener to. Defaults to the global `window`.\n * Override for testing or non-standard host environments.\n */\n hostWindow?: Window;\n /**\n * If `true` (default), incoming messages are accepted only when they originate\n * from the bound iframe's `contentWindow`. Set `false` only if the webview relays\n * messages through an intermediate window and source matching is impossible.\n */\n matchSource?: boolean;\n /**\n * If `true` (default), messages sent before the webview reports\n * `form_load_complete` are buffered and flushed, in order, once it's ready. This\n * removes a common footgun: calling `prefill(...)` right after mount would\n * otherwise post to a form that isn't listening yet and be silently dropped.\n *\n * Set `false` to send immediately (the legacy behavior). Note: a client that\n * attaches *after* the form already loaded will not have seen `form_load_complete`,\n * so its queued messages won't flush — create the client with the iframe.\n */\n queueUntilReady?: boolean;\n}\n\n/** Emits a console warning in development builds only. No-op in production / no bundler. */\nfunction devWarn(message: string): void {\n try {\n if (typeof process !== \"undefined\" && process.env && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(message);\n }\n } catch {\n /* `process` not defined (pure browser, no bundler define) → stay silent */\n }\n}\n\ntype ElementOrFrame = HTMLIFrameElement | { contentWindow: Window | null };\n\n/**\n * Host-side controller for an embedded Captello capture webview.\n *\n * Wraps a single `<iframe>` and encodes the full message protocol:\n * - **Receiving** (webview → host): subscribe with {@link on} / {@link onAny}.\n * - **Sending** (host → webview): use {@link submit}, {@link reset}, {@link prefill},\n * {@link triggerValidation}, {@link updateDraft}, or the lower-level {@link send}.\n *\n * Wire details handled for you: outgoing messages are `JSON.stringify`'d (the webview\n * parses inbound data with `JSON.parse`, so a raw object would be ignored), and\n * incoming messages are validated by origin + source before being parsed.\n *\n * @example\n * ```ts\n * const iframe = document.querySelector(\"iframe\")!;\n * const webview = new CaptelloWebview(iframe, {\n * targetOrigin: \"https://capture.captello.com\",\n * });\n *\n * webview.on(OutboundMessageType.FormLoadComplete, () => console.log(\"ready\"));\n * webview.on(OutboundMessageType.SubmissionBody, (msg) => save(msg.data));\n *\n * // later, drive the form:\n * webview.submit();\n *\n * // on teardown:\n * webview.destroy();\n * ```\n */\nexport class CaptelloWebview {\n private readonly frame: ElementOrFrame;\n private readonly targetOrigin: string;\n private readonly hostWindow: Window;\n private readonly matchSource: boolean;\n\n private readonly listeners = new Map<OutboundMessageType, Set<OutboundListener<OutboundMessageType>>>();\n private readonly anyListeners = new Set<AnyOutboundListener>();\n private readonly boundHandler: (event: MessageEvent) => void;\n private destroyed = false;\n\n private readonly queueUntilReady: boolean;\n /** True once `form_load_complete` has been observed. */\n private ready = false;\n /** Messages sent before ready, flushed in order on load. */\n private readonly outbox: InboundMessage[] = [];\n\n constructor(frame: ElementOrFrame, options: CaptelloWebviewOptions = {}) {\n if (!frame) {\n throw new Error(\"CaptelloWebview: an iframe element (or { contentWindow }) is required.\");\n }\n this.frame = frame;\n this.targetOrigin = options.targetOrigin ?? \"*\";\n this.matchSource = options.matchSource ?? true;\n this.queueUntilReady = options.queueUntilReady ?? true;\n\n // Nudge (dev only) when running without origin scoping. \"*\" accepts inbound\n // messages from any origin and posts outbound without an origin check — fine\n // for local/trusted dev, unsafe in production. Set targetOrigin to the\n // webview's origin, e.g. `new URL(embedUrl).origin`.\n if (this.targetOrigin === \"*\") {\n devWarn(\n '[captello-sdk] No targetOrigin set — defaulting to \"*\", which accepts messages ' +\n \"from any origin and posts without an origin check. Set targetOrigin to the webview's \" +\n \"origin (e.g. new URL(embedUrl).origin) in production.\",\n );\n }\n\n const hostWindow = options.hostWindow ?? (typeof window !== \"undefined\" ? window : undefined);\n if (!hostWindow) {\n throw new Error(\n \"CaptelloWebview: no host window available. Pass `hostWindow` when constructing outside a browser.\",\n );\n }\n this.hostWindow = hostWindow;\n\n this.boundHandler = (event: MessageEvent) => this.handleMessage(event);\n this.hostWindow.addEventListener(\"message\", this.boundHandler);\n }\n\n /** `true` once the webview has reported `form_load_complete`. */\n get isReady(): boolean {\n return this.ready;\n }\n\n /* -------------------------------------------------------------- *\n * Receiving (webview → host)\n * -------------------------------------------------------------- */\n\n /**\n * Subscribe to a single outbound message type. Returns an unsubscribe function.\n *\n * @example webview.on(OutboundMessageType.FormErrorMessage, (m) => toast(m.data));\n */\n on<T extends OutboundMessageType>(type: T, listener: OutboundListener<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 OutboundListener<OutboundMessageType>);\n return () => {\n set?.delete(listener as OutboundListener<OutboundMessageType>);\n };\n }\n\n /**\n * Subscribe once: the listener is removed automatically after it fires the first\n * time for `type`. Returns an unsubscribe function for cancelling early.\n */\n once<T extends OutboundMessageType>(type: T, listener: OutboundListener<T>): Unsubscribe {\n const off = this.on(type, (message) => {\n off();\n listener(message);\n });\n return off;\n }\n\n /** Subscribe to every outbound message regardless of type. Returns an unsubscribe function. */\n onAny(listener: AnyOutboundListener): Unsubscribe {\n this.anyListeners.add(listener);\n return () => {\n this.anyListeners.delete(listener);\n };\n }\n\n /* -------------------------------------------------------------- *\n * Sending (host → webview)\n * -------------------------------------------------------------- */\n\n /**\n * Low-level send: posts any inbound message to the webview as a JSON string.\n * Prefer the typed helpers below; use this only for forward-compatibility.\n *\n * When `queueUntilReady` is enabled (the default) and the form hasn't reported\n * `form_load_complete` yet, the message is buffered and flushed on load instead of\n * posted immediately.\n *\n * @throws if the iframe's `contentWindow` is not available (not yet loaded /\n * detached) and the message can't be queued.\n */\n send(message: InboundMessage): void {\n if (this.destroyed) {\n throw new Error(\"CaptelloWebview: cannot send after destroy().\");\n }\n if (this.queueUntilReady && !this.ready) {\n this.outbox.push(message);\n return;\n }\n this.postNow(message);\n }\n\n /** Posts a message immediately, bypassing the ready-queue. */\n private postNow(message: InboundMessage): void {\n const target = this.frame.contentWindow;\n if (!target) {\n throw new Error(\n \"CaptelloWebview: iframe.contentWindow is null. Wait for the iframe to load before sending.\",\n );\n }\n // The webview reads inbound data with JSON.parse(event.data), so it must be a string.\n target.postMessage(JSON.stringify(message), this.targetOrigin);\n }\n\n /** Marks the client ready and flushes any queued messages, in order. */\n private markReadyAndFlush(): void {\n if (this.ready) return;\n this.ready = true;\n const queued = this.outbox.splice(0);\n for (const message of queued) {\n try {\n this.postNow(message);\n } catch {\n /* iframe detached between load and flush — drop silently */\n }\n }\n }\n\n /** Programmatically submit the form (fire-and-forget). */\n submit(): void {\n this.send({ type: InboundMessageType.Submit });\n }\n\n /**\n * Submit the form and await the outcome.\n *\n * Sends `submit_form`, then resolves with the {@link SubmissionBody} when the\n * webview emits `submission_body`, or rejects with a {@link SubmissionError}\n * (carrying the translated message) when it emits `form_error_message`. Rejects\n * with a {@link SubmissionTimeoutError} if neither arrives within `timeoutMs`.\n *\n * This is the typed, leak-free version of the common \"click submit, wait for the\n * result\" flow — listeners are always cleaned up, including on timeout.\n *\n * @param timeoutMs how long to wait before giving up. Defaults to 60_000.\n * @example\n * try {\n * const body = await webview.submitAndWait();\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\n submitAndWait(timeoutMs = 60_000): Promise<SubmissionBody> {\n return new Promise<SubmissionBody>((resolve, reject) => {\n let settled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const cleanup = () => {\n settled = true;\n offSuccess();\n offError();\n if (timer !== undefined) clearTimeout(timer);\n };\n\n const offSuccess = this.on(OutboundMessageType.SubmissionBody, (message) => {\n if (settled) return;\n cleanup();\n resolve(message.data);\n });\n const offError = this.on(OutboundMessageType.FormErrorMessage, (message) => {\n if (settled) return;\n cleanup();\n reject(new SubmissionError(message.data));\n });\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => {\n if (settled) return;\n cleanup();\n reject(new SubmissionTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n\n try {\n this.send({ type: InboundMessageType.Submit });\n } catch (err) {\n if (!settled) {\n cleanup();\n reject(err);\n }\n }\n });\n }\n\n /** Reset the form, clearing all entered values. */\n reset(): void {\n this.send({ type: InboundMessageType.Reset });\n }\n\n /** Switch the current submission into draft-update mode. */\n updateDraft(): void {\n this.send({ type: InboundMessageType.UpdateDraft });\n }\n\n /** Run validation against a target field, or `\"all\"` for the whole form. */\n triggerValidation(target: ValidationTarget): void {\n this.send({ type: InboundMessageType.TriggerValidation, target });\n }\n\n /** Pre-fill form fields from a submission body, transcription items, or both. */\n prefill(data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }): void {\n this.send({\n type: InboundMessageType.FormPrefill,\n data_type: PrefillDataType.UlcSubmissionAndInfo,\n data,\n });\n }\n\n /* -------------------------------------------------------------- *\n * Lifecycle\n * -------------------------------------------------------------- */\n\n /** Remove the `message` listener and drop all subscriptions. Idempotent. */\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 this.outbox.length = 0;\n }\n\n /* -------------------------------------------------------------- *\n * Internals\n * -------------------------------------------------------------- */\n\n private handleMessage(event: MessageEvent): void {\n if (this.destroyed) return;\n\n // Origin check: skip when targetOrigin is the wildcard. Warn (dev only) if a\n // message that *looks* like ours is dropped on origin — a common \"why isn't my\n // listener firing?\" cause.\n if (this.targetOrigin !== \"*\" && event.origin !== this.targetOrigin) {\n if (parseOutboundMessage(event.data)) {\n devWarn(\n `[captello-sdk] Ignored a Captello message from origin \"${event.origin}\" ` +\n `(expected \"${this.targetOrigin}\"). Check the targetOrigin you passed.`,\n );\n }\n return;\n }\n\n // Source check: only accept messages from the bound iframe's window.\n if (this.matchSource) {\n const expected = this.frame.contentWindow;\n if (expected && event.source !== expected) {\n if (parseOutboundMessage(event.data)) {\n devWarn(\n \"[captello-sdk] Ignored a Captello message from an unexpected source window \" +\n \"(not the bound iframe). If the webview relays through another window, set matchSource: false.\",\n );\n }\n return;\n }\n }\n\n const message = parseOutboundMessage(event.data);\n if (!message) return;\n\n // Flip to ready (and flush queued sends) the moment the form loads, before\n // dispatching to listeners — so a listener can send and have it post immediately.\n if (message.type === OutboundMessageType.FormLoadComplete) {\n this.markReadyAndFlush();\n }\n\n const set = this.listeners.get(message.type);\n if (set) {\n // Copy to a snapshot so a listener that unsubscribes mid-dispatch is safe.\n for (const listener of [...set]) listener(message);\n }\n if (this.anyListeners.size) {\n for (const listener of [...this.anyListeners]) listener(message);\n }\n }\n}\n"]}
|
|
@@ -238,17 +238,50 @@ interface SubmissionBody {
|
|
|
238
238
|
/** Additional fields from the webview's submission model are passed through verbatim. */
|
|
239
239
|
[key: string]: unknown;
|
|
240
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Submitted values flat-keyed by element / sub-element id (e.g. `"element_12"`, `"element_12_3"`).
|
|
243
|
+
*
|
|
244
|
+
* This is the webview's own internal shape rather than the submissions API's: it is what a
|
|
245
|
+
* received {@link SubmissionBody} carries, and what the backend stores verbatim for a draft and
|
|
246
|
+
* hands back unchanged — hence the name. Either can be passed to
|
|
247
|
+
* {@link SubmissionPrefill.data} as-is, so an `onSubmissionBody` payload round-trips.
|
|
248
|
+
*
|
|
249
|
+
* Contrast {@link SubmissionPrefillDataItem}, the array shape the submissions API returns.
|
|
250
|
+
*/
|
|
251
|
+
type DraftSubmissionData = Record<string, unknown>;
|
|
252
|
+
/**
|
|
253
|
+
* One submitted value in {@link SubmissionPrefill.data}. Mirrors the submissions API's
|
|
254
|
+
* `SubmissionDataResponse` shape, so a `submission.data` array fetched from that API can
|
|
255
|
+
* be passed straight through as-is.
|
|
256
|
+
*/
|
|
257
|
+
interface SubmissionPrefillDataItem {
|
|
258
|
+
element_id: string;
|
|
259
|
+
element_title: string;
|
|
260
|
+
value: string;
|
|
261
|
+
/** Present only when the element has sub-elements (e.g. a simple name or address). */
|
|
262
|
+
value_splitted?: Record<string, string>;
|
|
263
|
+
}
|
|
241
264
|
/**
|
|
242
265
|
* Loose submission shape accepted when **pre-filling** the form (host → webview).
|
|
243
266
|
*
|
|
244
|
-
* Distinct from {@link SubmissionBody}
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* is
|
|
267
|
+
* Distinct from {@link SubmissionBody} in that every field is optional — assemble a partial
|
|
268
|
+
* object from your own data rather than populating a whole body.
|
|
269
|
+
*
|
|
270
|
+
* `data` accepts either shape a host is likely to be holding, and the webview normalizes
|
|
271
|
+
* whichever it receives:
|
|
272
|
+
* - {@link SubmissionPrefillDataItem}`[]` — one entry per element, as the submissions API
|
|
273
|
+
* returns it for a submitted submission. Pass a fetched `submission.data` array straight
|
|
274
|
+
* through.
|
|
275
|
+
* - {@link DraftSubmissionData} — values flat-keyed by element / sub-element id. This is the
|
|
276
|
+
* shape a received {@link SubmissionBody} carries, and the shape a draft is stored and
|
|
277
|
+
* returned in, so `onSubmissionBody` payloads round-trip directly.
|
|
248
278
|
*/
|
|
249
279
|
interface SubmissionPrefill {
|
|
250
|
-
/**
|
|
251
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Submitted values, as either the submissions API's array (see
|
|
282
|
+
* {@link SubmissionPrefillDataItem}) or a flat {@link DraftSubmissionData} record.
|
|
283
|
+
*/
|
|
284
|
+
data?: SubmissionPrefillDataItem[] | DraftSubmissionData;
|
|
252
285
|
[key: string]: unknown;
|
|
253
286
|
}
|
|
254
287
|
interface FormLoadCompleteMessage {
|
|
@@ -514,4 +547,4 @@ declare class CaptelloWebview {
|
|
|
514
547
|
private handleMessage;
|
|
515
548
|
}
|
|
516
549
|
|
|
517
|
-
export { type AddressSubmissionValue as A, type BusinessCardValue as B, type CaptelloWebviewOptions as C, FormElementType as F, type InboundMessage as I, type NameSubmissionValue as N, OutboundMessageType as O, type PrefillInfoItem as P, type SubmissionBody as S, type Unsubscribe as U, type ValidationTarget as V, type OutboundMessageMap as a, SubmissionError as b, SubmissionTimeoutError as c, type OutboundMessage as d, type SubmissionPrefill as e, CaptelloWebview as f, type AnyOutboundListener as g, type AttachmentValue as h, InboundMessageType as i, type OrderCheckboxSubmissionData as j, type OrderRadioSubmissionData as k, type OutboundListener as l, type
|
|
550
|
+
export { type AddressSubmissionValue as A, type BusinessCardValue as B, type CaptelloWebviewOptions as C, type DraftSubmissionData as D, FormElementType as F, type InboundMessage as I, type NameSubmissionValue as N, OutboundMessageType as O, type PrefillInfoItem as P, type SubmissionBody as S, type Unsubscribe as U, type ValidationTarget as V, type OutboundMessageMap as a, SubmissionError as b, SubmissionTimeoutError as c, type OutboundMessage as d, type SubmissionPrefill as e, CaptelloWebview as f, type AnyOutboundListener as g, type AttachmentValue as h, InboundMessageType as i, type OrderCheckboxSubmissionData as j, type OrderRadioSubmissionData as k, type OutboundListener as l, type SubmissionPrefillDataItem as m, type SubmissionQuestionData as n, type VisibleSubmissionDataItem as o, type VisibleSubmissionElementType as p, type VisibleSubmissionElementValueMap as q, parseOutboundMessage as r };
|
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, 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 SubmissionQuestionData, c as SubmissionTimeoutError, U as Unsubscribe, V as ValidationTarget,
|
|
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';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Builder for the Captello capture webview embed URL.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl } from './chunk-4E7OW4RJ.js';
|
|
2
|
-
export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, parseOutboundMessage } from './chunk-
|
|
2
|
+
export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, parseOutboundMessage } from './chunk-UUEUW2WL.js';
|
|
3
3
|
|
|
4
4
|
// src/submission-data.ts
|
|
5
5
|
var FormElementType = /* @__PURE__ */ ((FormElementType2) => {
|
package/dist/promises.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-
|
|
2
|
-
export { b as SubmissionError, c as SubmissionTimeoutError } from './client-
|
|
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';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.
|
package/dist/promises.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CaptelloWebview } from './chunk-
|
|
2
|
-
export { SubmissionError, SubmissionTimeoutError } from './chunk-
|
|
1
|
+
import { CaptelloWebview } from './chunk-UUEUW2WL.js';
|
|
2
|
+
export { SubmissionError, SubmissionTimeoutError } from './chunk-UUEUW2WL.js';
|
|
3
3
|
|
|
4
4
|
// src/promises.ts
|
|
5
5
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
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-
|
|
4
|
-
export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-
|
|
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';
|
|
5
5
|
import { EmbedUrlOptions } from './index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -43,10 +43,15 @@ interface EmbedUrlConfig extends EmbedUrlOptions {
|
|
|
43
43
|
* Values to seed a form with on load — see {@link UseCaptelloWebviewOptions.defaultFormValues}.
|
|
44
44
|
*
|
|
45
45
|
* `submission` is typed as {@link SubmissionPrefill} (every field optional) so a partial
|
|
46
|
-
*
|
|
46
|
+
* object assembled from your own data is valid, as is a `submission.data` array fetched
|
|
47
|
+
* from the submissions API or a whole {@link SubmissionBody} echoed back from
|
|
48
|
+
* `onSubmissionBody`.
|
|
47
49
|
*/
|
|
48
50
|
interface DefaultFormValues {
|
|
49
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Submitted values under `data`, as either the submissions API's array (see
|
|
53
|
+
* `SubmissionPrefillDataItem`) or a flat `DraftSubmissionData` record.
|
|
54
|
+
*/
|
|
50
55
|
submission?: SubmissionPrefill;
|
|
51
56
|
/** Field values matched by `ll_field_unique_identifier` (e.g. `"Email"`). */
|
|
52
57
|
info?: PrefillInfoItem[];
|
package/dist/react.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildEmbedUrl } from './chunk-4E7OW4RJ.js';
|
|
2
|
-
import { CaptelloWebview, OutboundMessageType } from './chunk-
|
|
3
|
-
export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-
|
|
2
|
+
import { CaptelloWebview, OutboundMessageType } from './chunk-UUEUW2WL.js';
|
|
3
|
+
export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-UUEUW2WL.js';
|
|
4
4
|
import { forwardRef, useState, useRef, useCallback, useImperativeHandle, useEffect } from 'react';
|
|
5
5
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["/**\n * React adapter for the Captello webview SDK — `@captello/ulc-webview-sdk/react`.\n *\n * Two entry points, same engine:\n * - {@link CaptelloForm} — a turnkey `<iframe>` component. Drop it in with an `embedUrl`\n * and message callbacks; it renders the frame, shows your `loading` / `error` overlays,\n * and exposes the senders via an imperative `ref`. This is the shortest path.\n * - {@link useCaptelloWebview} — the underlying hook, for when you want to own the markup.\n *\n * {@link useCaptelloWebview} owns a {@link CaptelloWebview} for the lifetime of an\n * iframe: it creates the client once the iframe mounts, wires the outbound messages\n * you care about to typed callbacks, tracks readiness, and destroys the client on\n * unmount. You get back `iframeProps` to spread onto your `<iframe>` (or a bare `ref`),\n * an `isReady` flag, and stable senders (`submit`, `reset`, `prefill`, …).\n *\n * Sends made before the form loads are queued by the client and flushed on\n * `form_load_complete`, so you can call `prefill(...)` as soon as you have data —\n * no need to gate on readiness yourself. To seed a form declaratively, pass\n * `defaultFormValues` instead and skip the `prefill(...)` wiring entirely.\n *\n * Callbacks are held in a ref and always called fresh, so you do NOT need to memoize\n * them — passing inline arrow functions will not re-subscribe or re-create the client.\n *\n * `react` is an optional peer dependency; importing this entry point requires React 18+.\n *\n * @example\n * function UlcForm({ token, onSubmitted }: { token: string; onSubmitted: (b: SubmissionBody) => void }) {\n * const { iframeProps, isReady, submit } = useCaptelloWebview({\n * embedUrl: {\n * baseUrl: \"https://capture.captello.com\",\n * eventWebAccessToken: token,\n * mode: FormMode.Submit,\n * launcher: LauncherType.EventGenWeb,\n * },\n * onSubmissionBody: onSubmitted,\n * });\n * return (\n * <>\n * {!isReady && <Spinner />}\n * <iframe {...iframeProps} title=\"UlcForm\" allow=\"camera; microphone\" />\n * <button onClick={submit}>Submit</button>\n * </>\n * );\n * }\n */\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type IframeHTMLAttributes,\n type ReactElement,\n type ReactNode,\n type Ref,\n type RefCallback,\n} from \"react\";\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions, Unsubscribe } from \"./client\";\nimport { buildEmbedUrl } from \"./embed-url\";\nimport type { EmbedUrlOptions } from \"./embed-url\";\nimport { OutboundMessageType } from \"./messages\";\nimport type {\n OutboundMessage,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/**\n * Per-message-type callback props accepted by {@link useCaptelloWebview}.\n *\n * Each callback receives the message's **payload**, not the message envelope — the\n * callback name already carries the `type`, so there is nothing to discriminate on.\n * Messages that carry no payload take no argument.\n *\n * {@link CaptelloWebviewCallbacks.onAnyMessage} is the exception: it fires for every\n * type, so it gets the whole message including the `type` discriminator.\n */\nexport interface CaptelloWebviewCallbacks {\n /** The form finished loading and rendering. Safe to interact with it after this. */\n onFormLoadComplete?: () => void;\n /** A user-facing error occurred. Receives the translated, display-ready text. */\n onFormErrorMessage?: (message: string) => void;\n /** Receives the full submission body, for the host to persist / forward. */\n onSubmissionBody?: (body: SubmissionBody) => void;\n /** The form was submitted successfully. Receives whether it created or updated. */\n onFormSubmitSuccess?: (action: \"create\" | \"update\") => void;\n /** Connexions: the host should perform the profile redirect (embed mode). */\n onConnexionsProfileRedirect?: () => void;\n /** Connexions: the host should trigger the vCard download (embed mode). */\n onConnexionsDownloadVcard?: () => void;\n /** Catch-all: the full message, including `type`. Called after the specific handler above. */\n onAnyMessage?: (message: OutboundMessage) => void;\n}\n\n/** Embed-URL config: a base URL plus {@link EmbedUrlOptions}. */\nexport interface EmbedUrlConfig extends EmbedUrlOptions {\n /**\n * The capture **origin**, e.g. `\"https://capture.captello.com\"`. The SDK appends the\n * capture path for you, so the origin, a trailing slash, or the full\n * `…/capture/submission` URL all work — see {@link buildEmbedUrl}.\n */\n baseUrl: string;\n}\n\n/**\n * Values to seed a form with on load — see {@link UseCaptelloWebviewOptions.defaultFormValues}.\n *\n * `submission` is typed as {@link SubmissionPrefill} (every field optional) so a partial\n * is valid; a whole {@link SubmissionBody} echoed back from `onSubmissionBody` also fits.\n */\nexport interface DefaultFormValues {\n /** Values keyed by element id / sub-element id, under `data`. */\n submission?: SubmissionPrefill;\n /** Field values matched by `ll_field_unique_identifier` (e.g. `\"Email\"`). */\n info?: PrefillInfoItem[];\n}\n\n/**\n * Options for {@link useCaptelloWebview}: the embed config, message callbacks, and the\n * usual client options.\n *\n * `embedUrl` is required — the hook builds the URL from it, derives `targetOrigin`, and\n * returns it as `iframeProps.src`. Any `targetOrigin` you pass is ignored; drop to\n * {@link CaptelloWebview} directly if you need to own both the URL and the origin.\n */\nexport interface UseCaptelloWebviewOptions extends Omit<CaptelloWebviewOptions, \"targetOrigin\">, CaptelloWebviewCallbacks {\n /** Build the iframe URL and derive `targetOrigin` from it. Sets `iframeProps.src`. */\n embedUrl: EmbedUrlConfig;\n /**\n * Values to populate the form with as soon as it is ready. Saves you from wiring a\n * `ref` and calling `prefill(...)` from an effect just to seed the form.\n *\n * Sent as the *first* outbound message, so a later explicit `prefill(...)` wins.\n * Read once when the client attaches — changing the value afterwards does **not**\n * re-populate the form (these are defaults, not controlled values); call `prefill(...)`\n * for that. No memoization needed: an inline object literal is fine.\n *\n * @example\n * defaultFormValues={{ info: [{ ll_field_unique_identifier: \"Email\", value: user.email }] }}\n */\n defaultFormValues?: DefaultFormValues;\n}\n\n/** Readiness of the embedded form. */\nexport type CaptelloWebviewStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Props to spread onto the `<iframe>` — the ref plus the `embedUrl`-derived `src`. */\nexport interface CaptelloIframeProps {\n ref: RefCallback<HTMLIFrameElement | null>;\n src: string;\n}\n\n/** What {@link useCaptelloWebview} returns. */\nexport interface UseCaptelloWebviewResult {\n /** Spread onto your iframe: `<iframe {...iframeProps} />`. Carries the `embedUrl`-derived `src`. */\n iframeProps: CaptelloIframeProps;\n /** The iframe ref callback (same as `iframeProps.ref`), if you'd rather wire `src` yourself. */\n ref: RefCallback<HTMLIFrameElement | null>;\n /** `true` once the form has reported `form_load_complete`. */\n isReady: boolean;\n /** `\"loading\"` → `\"ready\"`; flips to `\"error\"` if a `form_error_message` arrives. */\n status: CaptelloWebviewStatus;\n /** The live client, or `null` before the iframe mounts. For escape-hatch use. */\n getClient: () => CaptelloWebview | null;\n submit: () => void;\n reset: () => void;\n updateDraft: () => void;\n triggerValidation: (target: ValidationTarget) => void;\n prefill: (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) => void;\n submitAndWait: (timeoutMs?: number) => Promise<SubmissionBody>;\n}\n\n/**\n * Unwraps `message` to its payload and calls the matching callback.\n *\n * Exhaustive over {@link OutboundMessageType}: adding a message type without handling it\n * here is a compile error, so a new type can't silently go undelivered.\n */\nfunction dispatchToCallback(callbacks: CaptelloWebviewCallbacks, message: OutboundMessage): void {\n switch (message.type) {\n case OutboundMessageType.FormLoadComplete:\n callbacks.onFormLoadComplete?.();\n break;\n case OutboundMessageType.FormErrorMessage:\n callbacks.onFormErrorMessage?.(message.data);\n break;\n case OutboundMessageType.SubmissionBody:\n callbacks.onSubmissionBody?.(message.data);\n break;\n case OutboundMessageType.FormSubmitSuccess:\n callbacks.onFormSubmitSuccess?.(message.action);\n break;\n case OutboundMessageType.ConnexionsProfileRedirect:\n callbacks.onConnexionsProfileRedirect?.();\n break;\n case OutboundMessageType.ConnexionsDownloadVcard:\n callbacks.onConnexionsDownloadVcard?.();\n break;\n default: {\n const exhaustive: never = message;\n void exhaustive;\n }\n }\n}\n\n/**\n * Binds a {@link CaptelloWebview} to an iframe's lifecycle. See the module doc for usage.\n */\nexport function useCaptelloWebview(options: UseCaptelloWebviewOptions): UseCaptelloWebviewResult {\n const { embedUrl, matchSource, queueUntilReady, hostWindow } = options;\n\n // Build the iframe URL and scope messaging to its origin. Recomputed on every render\n // (cheap), but only the derived origin feeds `attach`'s deps, so a same-origin URL\n // change doesn't tear the client down.\n const src = buildEmbedUrl(embedUrl.baseUrl, embedUrl);\n const targetOrigin = new URL(src).origin;\n\n // Latest options/callbacks, read fresh inside listeners so callers needn't memoize.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const clientRef = useRef<CaptelloWebview | null>(null);\n const frameRef = useRef<HTMLIFrameElement | null>(null);\n const teardown = useRef<(() => void) | null>(null);\n\n const [status, setStatus] = useState<CaptelloWebviewStatus>(\"loading\");\n\n const attach = useCallback(\n (frame: HTMLIFrameElement | null) => {\n // Tear down any previous client (ref changed or unmounting).\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n frameRef.current = frame;\n setStatus(\"loading\");\n\n if (!frame) return;\n\n const client = new CaptelloWebview(frame, {\n targetOrigin,\n hostWindow: optionsRef.current.hostWindow,\n matchSource: optionsRef.current.matchSource,\n queueUntilReady: optionsRef.current.queueUntilReady,\n });\n clientRef.current = client;\n\n const offs: Unsubscribe[] = [];\n\n // Seed the form with `defaultFormValues`. With `queueUntilReady` (the default)\n // this lands first in the outbox and flushes on load, so an explicit prefill()\n // made later still wins. With queueing off there's no outbox to ride in on, so\n // wait for the form to report in — subscribed before the callback loop below,\n // to seed before the caller's onFormLoadComplete runs.\n const defaults = optionsRef.current.defaultFormValues;\n if (defaults && (defaults.submission != null || defaults.info != null)) {\n if (optionsRef.current.queueUntilReady === false) {\n offs.push(\n client.once(OutboundMessageType.FormLoadComplete, () => {\n try {\n client.prefill(defaults);\n } catch {\n /* iframe detached between load and seed — drop silently */\n }\n }),\n );\n } else {\n client.prefill(defaults);\n }\n }\n\n for (const type of Object.values(OutboundMessageType)) {\n offs.push(\n client.on(type, (message) => {\n if (type === OutboundMessageType.FormLoadComplete) setStatus(\"ready\");\n else if (type === OutboundMessageType.FormErrorMessage) setStatus(\"error\");\n\n dispatchToCallback(optionsRef.current, message);\n optionsRef.current.onAnyMessage?.(message);\n }),\n );\n }\n\n teardown.current = () => {\n for (const off of offs) off();\n client.destroy();\n };\n },\n // Re-create the client only when connection-level inputs change.\n // Callbacks are read via optionsRef, so they intentionally aren't deps.\n [targetOrigin, matchSource, queueUntilReady, hostWindow],\n );\n\n useEffect(() => {\n if (frameRef.current) attach(frameRef.current);\n return () => {\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n };\n }, [attach]);\n\n const getClient = useCallback(() => clientRef.current, []);\n\n const submit = useCallback(() => clientRef.current?.submit(), []);\n const reset = useCallback(() => clientRef.current?.reset(), []);\n const updateDraft = useCallback(() => clientRef.current?.updateDraft(), []);\n const triggerValidation = useCallback(\n (target: ValidationTarget) => clientRef.current?.triggerValidation(target),\n [],\n );\n const prefill = useCallback(\n (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) => clientRef.current?.prefill(data),\n [],\n );\n const submitAndWait = useCallback((timeoutMs?: number) => {\n const client = clientRef.current;\n if (!client) {\n return Promise.reject(new Error(\"CaptelloWebview: iframe is not mounted yet.\"));\n }\n return client.submitAndWait(timeoutMs);\n }, []);\n\n const iframeProps: CaptelloIframeProps = { ref: attach, src };\n\n return {\n iframeProps,\n ref: attach,\n isReady: status === \"ready\",\n status,\n getClient,\n submit,\n reset,\n updateDraft,\n triggerValidation,\n prefill,\n submitAndWait,\n };\n}\n\n/* ------------------------------------------------------------------ *\n * <CaptelloForm /> — the turnkey component\n * ------------------------------------------------------------------ */\n\n/** Default iframe permissions for a capture form (business-card camera scan, mic, geo). */\nconst DEFAULT_ALLOW = \"camera; microphone; geolocation\";\n\n/** Wrapper is the positioning context for the loading / error overlays. */\nconst WRAPPER_STYLE: CSSProperties = { position: \"relative\" };\n\n/** The iframe fills the wrapper; size the component, not this. */\nconst IFRAME_STYLE: CSSProperties = { display: \"block\", width: \"100%\", height: \"100%\", border: 0 };\n\n/** Centers the `loading` / `error` node over the iframe. */\nconst OVERLAY_STYLE: CSSProperties = {\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n};\n\n/**\n * Imperative handle exposed on {@link CaptelloForm}'s `ref` — the same senders the hook\n * returns, plus the current status and the underlying `<iframe>` node. Lets a parent\n * drive the form (e.g. an external submit button) without lifting state.\n */\nexport interface CaptelloFormHandle\n extends Pick<\n UseCaptelloWebviewResult,\n \"submit\" | \"reset\" | \"updateDraft\" | \"triggerValidation\" | \"prefill\" | \"submitAndWait\" | \"getClient\"\n > {\n /** Current readiness: `\"loading\" | \"ready\" | \"error\"`. */\n readonly status: CaptelloWebviewStatus;\n /** `true` once the form has reported `form_load_complete`. */\n readonly isReady: boolean;\n /** The underlying `<iframe>` DOM node, or `null` before it mounts. */\n getIframe: () => HTMLIFrameElement | null;\n}\n\n/**\n * Props for {@link CaptelloForm}: every {@link UseCaptelloWebviewOptions} option (embed\n * config + message callbacks + client options) plus rendering conveniences.\n */\nexport interface CaptelloFormProps extends UseCaptelloWebviewOptions {\n /** `className` for the wrapper element. */\n className?: string;\n /** `style` for the wrapper element — size the form here. The component adds `position: relative`; your values win. */\n style?: CSSProperties;\n /** `id` for the wrapper element. */\n id?: string;\n /**\n * Attributes spread onto the `<iframe>` — `title`, `allow`, `sandbox`, `name`, etc.\n * Defaults: `title=\"Captello form\"`, `allow=\"camera; microphone; geolocation\"`.\n * `src` is ignored: it comes from `embedUrl`.\n */\n iframeProps?: Omit<IframeHTMLAttributes<HTMLIFrameElement>, \"ref\">;\n /** Rendered, centered over the iframe, while it is loading. The iframe stays mounted underneath. */\n loading?: ReactNode;\n /**\n * Rendered, centered over the iframe, when the form reports `form_error_message`.\n * Pass a function to receive the translated, display-ready error text.\n */\n error?: ReactNode | ((message: string | undefined) => ReactNode);\n /**\n * Inline controls rendered after the form. A function receives the live api\n * (status + senders), so you can wire a submit button without a `ref`.\n */\n children?: ReactNode | ((api: UseCaptelloWebviewResult) => ReactNode);\n}\n\nfunction CaptelloFormImpl(props: CaptelloFormProps, ref: Ref<CaptelloFormHandle>): ReactElement {\n const {\n className,\n style,\n id,\n iframeProps,\n loading,\n error,\n children,\n onFormLoadComplete,\n onFormErrorMessage,\n ...options\n } = props;\n\n // The translated error text from the last form_error_message, for the `error` render.\n const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);\n\n const api = useCaptelloWebview({\n ...options,\n // Wrap the two status-bearing callbacks to track the error text, then forward to\n // the caller's handler. The hook reads callbacks fresh, so these inline wrappers\n // don't re-subscribe or re-create the client.\n onFormLoadComplete: () => {\n setErrorMessage(undefined);\n onFormLoadComplete?.();\n },\n onFormErrorMessage: (message) => {\n setErrorMessage(message);\n onFormErrorMessage?.(message);\n },\n });\n\n // Merge the hook's iframe ref with our own node ref so getIframe() can return the DOM node.\n const nodeRef = useRef<HTMLIFrameElement | null>(null);\n const hookRef = api.iframeProps.ref;\n const setIframe = useCallback<RefCallback<HTMLIFrameElement | null>>(\n (node) => {\n nodeRef.current = node;\n hookRef(node);\n },\n [hookRef],\n );\n\n useImperativeHandle(\n ref,\n () => ({\n submit: api.submit,\n reset: api.reset,\n updateDraft: api.updateDraft,\n triggerValidation: api.triggerValidation,\n prefill: api.prefill,\n submitAndWait: api.submitAndWait,\n getClient: api.getClient,\n getIframe: () => nodeRef.current,\n status: api.status,\n isReady: api.isReady,\n }),\n [api],\n );\n\n const showLoading = api.status === \"loading\" && loading != null;\n const showError = api.status === \"error\" && error != null;\n\n return (\n <>\n <div className={className} id={id} style={{ ...WRAPPER_STYLE, ...style }}>\n <iframe\n title=\"Captello form\"\n allow={DEFAULT_ALLOW}\n {...iframeProps}\n ref={setIframe}\n src={api.iframeProps.src}\n style={{ ...IFRAME_STYLE, ...iframeProps?.style }}\n />\n {showLoading ? <div style={OVERLAY_STYLE}>{loading}</div> : null}\n {showError ? (\n <div style={OVERLAY_STYLE}>{typeof error === \"function\" ? error(errorMessage) : error}</div>\n ) : null}\n </div>\n {typeof children === \"function\" ? children(api) : children}\n </>\n );\n}\n\n/**\n * Turnkey component for embedding a Captello capture form — the shortest path to a\n * working integration. Renders the `<iframe>`, wires {@link useCaptelloWebview} to it,\n * shows your `loading` / `error` overlays, and forwards a {@link CaptelloFormHandle} on\n * `ref` so a parent can `submit()` / `prefill()` without lifting state.\n *\n * `embedUrl` is required and the form fills its wrapper — size the form via `className` /\n * `style` (an iframe has no intrinsic height). Reach for {@link useCaptelloWebview} instead\n * when you need to own the markup.\n *\n * @example\n * function UlcForm({ token, email }: { token: string; email: string }) {\n * const ref = useRef<CaptelloFormHandle>(null);\n * return (\n * <CaptelloForm\n * ref={ref}\n * style={{ height: 600 }}\n * embedUrl={{\n * baseUrl: \"https://capture.captello.com\",\n * eventWebAccessToken: token,\n * mode: FormMode.Submit,\n * launcher: LauncherType.EventGenWeb,\n * }}\n * defaultFormValues={{ info: [{ ll_field_unique_identifier: \"Email\", value: email }] }}\n * onSubmissionBody={save}\n * loading={<Spinner />}\n * error={(msg) => <ErrorBanner>{msg}</ErrorBanner>}\n * >\n * {({ isReady }) => <button disabled={!isReady} onClick={() => ref.current?.submit()}>Submit</button>}\n * </CaptelloForm>\n * );\n * }\n */\nexport const CaptelloForm = forwardRef(CaptelloFormImpl);\nCaptelloForm.displayName = \"CaptelloForm\";\n\nexport { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from \"./client\";\nexport type { Unsubscribe } from \"./client\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["/**\n * React adapter for the Captello webview SDK — `@captello/ulc-webview-sdk/react`.\n *\n * Two entry points, same engine:\n * - {@link CaptelloForm} — a turnkey `<iframe>` component. Drop it in with an `embedUrl`\n * and message callbacks; it renders the frame, shows your `loading` / `error` overlays,\n * and exposes the senders via an imperative `ref`. This is the shortest path.\n * - {@link useCaptelloWebview} — the underlying hook, for when you want to own the markup.\n *\n * {@link useCaptelloWebview} owns a {@link CaptelloWebview} for the lifetime of an\n * iframe: it creates the client once the iframe mounts, wires the outbound messages\n * you care about to typed callbacks, tracks readiness, and destroys the client on\n * unmount. You get back `iframeProps` to spread onto your `<iframe>` (or a bare `ref`),\n * an `isReady` flag, and stable senders (`submit`, `reset`, `prefill`, …).\n *\n * Sends made before the form loads are queued by the client and flushed on\n * `form_load_complete`, so you can call `prefill(...)` as soon as you have data —\n * no need to gate on readiness yourself. To seed a form declaratively, pass\n * `defaultFormValues` instead and skip the `prefill(...)` wiring entirely.\n *\n * Callbacks are held in a ref and always called fresh, so you do NOT need to memoize\n * them — passing inline arrow functions will not re-subscribe or re-create the client.\n *\n * `react` is an optional peer dependency; importing this entry point requires React 18+.\n *\n * @example\n * function UlcForm({ token, onSubmitted }: { token: string; onSubmitted: (b: SubmissionBody) => void }) {\n * const { iframeProps, isReady, submit } = useCaptelloWebview({\n * embedUrl: {\n * baseUrl: \"https://capture.captello.com\",\n * eventWebAccessToken: token,\n * mode: FormMode.Submit,\n * launcher: LauncherType.EventGenWeb,\n * },\n * onSubmissionBody: onSubmitted,\n * });\n * return (\n * <>\n * {!isReady && <Spinner />}\n * <iframe {...iframeProps} title=\"UlcForm\" allow=\"camera; microphone\" />\n * <button onClick={submit}>Submit</button>\n * </>\n * );\n * }\n */\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type IframeHTMLAttributes,\n type ReactElement,\n type ReactNode,\n type Ref,\n type RefCallback,\n} from \"react\";\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions, Unsubscribe } from \"./client\";\nimport { buildEmbedUrl } from \"./embed-url\";\nimport type { EmbedUrlOptions } from \"./embed-url\";\nimport { OutboundMessageType } from \"./messages\";\nimport type {\n OutboundMessage,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/**\n * Per-message-type callback props accepted by {@link useCaptelloWebview}.\n *\n * Each callback receives the message's **payload**, not the message envelope — the\n * callback name already carries the `type`, so there is nothing to discriminate on.\n * Messages that carry no payload take no argument.\n *\n * {@link CaptelloWebviewCallbacks.onAnyMessage} is the exception: it fires for every\n * type, so it gets the whole message including the `type` discriminator.\n */\nexport interface CaptelloWebviewCallbacks {\n /** The form finished loading and rendering. Safe to interact with it after this. */\n onFormLoadComplete?: () => void;\n /** A user-facing error occurred. Receives the translated, display-ready text. */\n onFormErrorMessage?: (message: string) => void;\n /** Receives the full submission body, for the host to persist / forward. */\n onSubmissionBody?: (body: SubmissionBody) => void;\n /** The form was submitted successfully. Receives whether it created or updated. */\n onFormSubmitSuccess?: (action: \"create\" | \"update\") => void;\n /** Connexions: the host should perform the profile redirect (embed mode). */\n onConnexionsProfileRedirect?: () => void;\n /** Connexions: the host should trigger the vCard download (embed mode). */\n onConnexionsDownloadVcard?: () => void;\n /** Catch-all: the full message, including `type`. Called after the specific handler above. */\n onAnyMessage?: (message: OutboundMessage) => void;\n}\n\n/** Embed-URL config: a base URL plus {@link EmbedUrlOptions}. */\nexport interface EmbedUrlConfig extends EmbedUrlOptions {\n /**\n * The capture **origin**, e.g. `\"https://capture.captello.com\"`. The SDK appends the\n * capture path for you, so the origin, a trailing slash, or the full\n * `…/capture/submission` URL all work — see {@link buildEmbedUrl}.\n */\n baseUrl: string;\n}\n\n/**\n * Values to seed a form with on load — see {@link UseCaptelloWebviewOptions.defaultFormValues}.\n *\n * `submission` is typed as {@link SubmissionPrefill} (every field optional) so a partial\n * object assembled from your own data is valid, as is a `submission.data` array fetched\n * from the submissions API or a whole {@link SubmissionBody} echoed back from\n * `onSubmissionBody`.\n */\nexport interface DefaultFormValues {\n /**\n * Submitted values under `data`, as either the submissions API's array (see\n * `SubmissionPrefillDataItem`) or a flat `DraftSubmissionData` record.\n */\n submission?: SubmissionPrefill;\n /** Field values matched by `ll_field_unique_identifier` (e.g. `\"Email\"`). */\n info?: PrefillInfoItem[];\n}\n\n/**\n * Options for {@link useCaptelloWebview}: the embed config, message callbacks, and the\n * usual client options.\n *\n * `embedUrl` is required — the hook builds the URL from it, derives `targetOrigin`, and\n * returns it as `iframeProps.src`. Any `targetOrigin` you pass is ignored; drop to\n * {@link CaptelloWebview} directly if you need to own both the URL and the origin.\n */\nexport interface UseCaptelloWebviewOptions extends Omit<CaptelloWebviewOptions, \"targetOrigin\">, CaptelloWebviewCallbacks {\n /** Build the iframe URL and derive `targetOrigin` from it. Sets `iframeProps.src`. */\n embedUrl: EmbedUrlConfig;\n /**\n * Values to populate the form with as soon as it is ready. Saves you from wiring a\n * `ref` and calling `prefill(...)` from an effect just to seed the form.\n *\n * Sent as the *first* outbound message, so a later explicit `prefill(...)` wins.\n * Read once when the client attaches — changing the value afterwards does **not**\n * re-populate the form (these are defaults, not controlled values); call `prefill(...)`\n * for that. No memoization needed: an inline object literal is fine.\n *\n * @example\n * defaultFormValues={{ info: [{ ll_field_unique_identifier: \"Email\", value: user.email }] }}\n */\n defaultFormValues?: DefaultFormValues;\n}\n\n/** Readiness of the embedded form. */\nexport type CaptelloWebviewStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Props to spread onto the `<iframe>` — the ref plus the `embedUrl`-derived `src`. */\nexport interface CaptelloIframeProps {\n ref: RefCallback<HTMLIFrameElement | null>;\n src: string;\n}\n\n/** What {@link useCaptelloWebview} returns. */\nexport interface UseCaptelloWebviewResult {\n /** Spread onto your iframe: `<iframe {...iframeProps} />`. Carries the `embedUrl`-derived `src`. */\n iframeProps: CaptelloIframeProps;\n /** The iframe ref callback (same as `iframeProps.ref`), if you'd rather wire `src` yourself. */\n ref: RefCallback<HTMLIFrameElement | null>;\n /** `true` once the form has reported `form_load_complete`. */\n isReady: boolean;\n /** `\"loading\"` → `\"ready\"`; flips to `\"error\"` if a `form_error_message` arrives. */\n status: CaptelloWebviewStatus;\n /** The live client, or `null` before the iframe mounts. For escape-hatch use. */\n getClient: () => CaptelloWebview | null;\n submit: () => void;\n reset: () => void;\n updateDraft: () => void;\n triggerValidation: (target: ValidationTarget) => void;\n prefill: (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) => void;\n submitAndWait: (timeoutMs?: number) => Promise<SubmissionBody>;\n}\n\n/**\n * Unwraps `message` to its payload and calls the matching callback.\n *\n * Exhaustive over {@link OutboundMessageType}: adding a message type without handling it\n * here is a compile error, so a new type can't silently go undelivered.\n */\nfunction dispatchToCallback(callbacks: CaptelloWebviewCallbacks, message: OutboundMessage): void {\n switch (message.type) {\n case OutboundMessageType.FormLoadComplete:\n callbacks.onFormLoadComplete?.();\n break;\n case OutboundMessageType.FormErrorMessage:\n callbacks.onFormErrorMessage?.(message.data);\n break;\n case OutboundMessageType.SubmissionBody:\n callbacks.onSubmissionBody?.(message.data);\n break;\n case OutboundMessageType.FormSubmitSuccess:\n callbacks.onFormSubmitSuccess?.(message.action);\n break;\n case OutboundMessageType.ConnexionsProfileRedirect:\n callbacks.onConnexionsProfileRedirect?.();\n break;\n case OutboundMessageType.ConnexionsDownloadVcard:\n callbacks.onConnexionsDownloadVcard?.();\n break;\n default: {\n const exhaustive: never = message;\n void exhaustive;\n }\n }\n}\n\n/**\n * Binds a {@link CaptelloWebview} to an iframe's lifecycle. See the module doc for usage.\n */\nexport function useCaptelloWebview(options: UseCaptelloWebviewOptions): UseCaptelloWebviewResult {\n const { embedUrl, matchSource, queueUntilReady, hostWindow } = options;\n\n // Build the iframe URL and scope messaging to its origin. Recomputed on every render\n // (cheap), but only the derived origin feeds `attach`'s deps, so a same-origin URL\n // change doesn't tear the client down.\n const src = buildEmbedUrl(embedUrl.baseUrl, embedUrl);\n const targetOrigin = new URL(src).origin;\n\n // Latest options/callbacks, read fresh inside listeners so callers needn't memoize.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const clientRef = useRef<CaptelloWebview | null>(null);\n const frameRef = useRef<HTMLIFrameElement | null>(null);\n const teardown = useRef<(() => void) | null>(null);\n\n const [status, setStatus] = useState<CaptelloWebviewStatus>(\"loading\");\n\n const attach = useCallback(\n (frame: HTMLIFrameElement | null) => {\n // Tear down any previous client (ref changed or unmounting).\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n frameRef.current = frame;\n setStatus(\"loading\");\n\n if (!frame) return;\n\n const client = new CaptelloWebview(frame, {\n targetOrigin,\n hostWindow: optionsRef.current.hostWindow,\n matchSource: optionsRef.current.matchSource,\n queueUntilReady: optionsRef.current.queueUntilReady,\n });\n clientRef.current = client;\n\n const offs: Unsubscribe[] = [];\n\n // Seed the form with `defaultFormValues`. With `queueUntilReady` (the default)\n // this lands first in the outbox and flushes on load, so an explicit prefill()\n // made later still wins. With queueing off there's no outbox to ride in on, so\n // wait for the form to report in — subscribed before the callback loop below,\n // to seed before the caller's onFormLoadComplete runs.\n const defaults = optionsRef.current.defaultFormValues;\n if (defaults && (defaults.submission != null || defaults.info != null)) {\n if (optionsRef.current.queueUntilReady === false) {\n offs.push(\n client.once(OutboundMessageType.FormLoadComplete, () => {\n try {\n client.prefill(defaults);\n } catch {\n /* iframe detached between load and seed — drop silently */\n }\n }),\n );\n } else {\n client.prefill(defaults);\n }\n }\n\n for (const type of Object.values(OutboundMessageType)) {\n offs.push(\n client.on(type, (message) => {\n if (type === OutboundMessageType.FormLoadComplete) setStatus(\"ready\");\n else if (type === OutboundMessageType.FormErrorMessage) setStatus(\"error\");\n\n dispatchToCallback(optionsRef.current, message);\n optionsRef.current.onAnyMessage?.(message);\n }),\n );\n }\n\n teardown.current = () => {\n for (const off of offs) off();\n client.destroy();\n };\n },\n // Re-create the client only when connection-level inputs change.\n // Callbacks are read via optionsRef, so they intentionally aren't deps.\n [targetOrigin, matchSource, queueUntilReady, hostWindow],\n );\n\n useEffect(() => {\n if (frameRef.current) attach(frameRef.current);\n return () => {\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n };\n }, [attach]);\n\n const getClient = useCallback(() => clientRef.current, []);\n\n const submit = useCallback(() => clientRef.current?.submit(), []);\n const reset = useCallback(() => clientRef.current?.reset(), []);\n const updateDraft = useCallback(() => clientRef.current?.updateDraft(), []);\n const triggerValidation = useCallback(\n (target: ValidationTarget) => clientRef.current?.triggerValidation(target),\n [],\n );\n const prefill = useCallback(\n (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) => clientRef.current?.prefill(data),\n [],\n );\n const submitAndWait = useCallback((timeoutMs?: number) => {\n const client = clientRef.current;\n if (!client) {\n return Promise.reject(new Error(\"CaptelloWebview: iframe is not mounted yet.\"));\n }\n return client.submitAndWait(timeoutMs);\n }, []);\n\n const iframeProps: CaptelloIframeProps = { ref: attach, src };\n\n return {\n iframeProps,\n ref: attach,\n isReady: status === \"ready\",\n status,\n getClient,\n submit,\n reset,\n updateDraft,\n triggerValidation,\n prefill,\n submitAndWait,\n };\n}\n\n/* ------------------------------------------------------------------ *\n * <CaptelloForm /> — the turnkey component\n * ------------------------------------------------------------------ */\n\n/** Default iframe permissions for a capture form (business-card camera scan, mic, geo). */\nconst DEFAULT_ALLOW = \"camera; microphone; geolocation\";\n\n/** Wrapper is the positioning context for the loading / error overlays. */\nconst WRAPPER_STYLE: CSSProperties = { position: \"relative\" };\n\n/** The iframe fills the wrapper; size the component, not this. */\nconst IFRAME_STYLE: CSSProperties = { display: \"block\", width: \"100%\", height: \"100%\", border: 0 };\n\n/** Centers the `loading` / `error` node over the iframe. */\nconst OVERLAY_STYLE: CSSProperties = {\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n};\n\n/**\n * Imperative handle exposed on {@link CaptelloForm}'s `ref` — the same senders the hook\n * returns, plus the current status and the underlying `<iframe>` node. Lets a parent\n * drive the form (e.g. an external submit button) without lifting state.\n */\nexport interface CaptelloFormHandle\n extends Pick<\n UseCaptelloWebviewResult,\n \"submit\" | \"reset\" | \"updateDraft\" | \"triggerValidation\" | \"prefill\" | \"submitAndWait\" | \"getClient\"\n > {\n /** Current readiness: `\"loading\" | \"ready\" | \"error\"`. */\n readonly status: CaptelloWebviewStatus;\n /** `true` once the form has reported `form_load_complete`. */\n readonly isReady: boolean;\n /** The underlying `<iframe>` DOM node, or `null` before it mounts. */\n getIframe: () => HTMLIFrameElement | null;\n}\n\n/**\n * Props for {@link CaptelloForm}: every {@link UseCaptelloWebviewOptions} option (embed\n * config + message callbacks + client options) plus rendering conveniences.\n */\nexport interface CaptelloFormProps extends UseCaptelloWebviewOptions {\n /** `className` for the wrapper element. */\n className?: string;\n /** `style` for the wrapper element — size the form here. The component adds `position: relative`; your values win. */\n style?: CSSProperties;\n /** `id` for the wrapper element. */\n id?: string;\n /**\n * Attributes spread onto the `<iframe>` — `title`, `allow`, `sandbox`, `name`, etc.\n * Defaults: `title=\"Captello form\"`, `allow=\"camera; microphone; geolocation\"`.\n * `src` is ignored: it comes from `embedUrl`.\n */\n iframeProps?: Omit<IframeHTMLAttributes<HTMLIFrameElement>, \"ref\">;\n /** Rendered, centered over the iframe, while it is loading. The iframe stays mounted underneath. */\n loading?: ReactNode;\n /**\n * Rendered, centered over the iframe, when the form reports `form_error_message`.\n * Pass a function to receive the translated, display-ready error text.\n */\n error?: ReactNode | ((message: string | undefined) => ReactNode);\n /**\n * Inline controls rendered after the form. A function receives the live api\n * (status + senders), so you can wire a submit button without a `ref`.\n */\n children?: ReactNode | ((api: UseCaptelloWebviewResult) => ReactNode);\n}\n\nfunction CaptelloFormImpl(props: CaptelloFormProps, ref: Ref<CaptelloFormHandle>): ReactElement {\n const {\n className,\n style,\n id,\n iframeProps,\n loading,\n error,\n children,\n onFormLoadComplete,\n onFormErrorMessage,\n ...options\n } = props;\n\n // The translated error text from the last form_error_message, for the `error` render.\n const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);\n\n const api = useCaptelloWebview({\n ...options,\n // Wrap the two status-bearing callbacks to track the error text, then forward to\n // the caller's handler. The hook reads callbacks fresh, so these inline wrappers\n // don't re-subscribe or re-create the client.\n onFormLoadComplete: () => {\n setErrorMessage(undefined);\n onFormLoadComplete?.();\n },\n onFormErrorMessage: (message) => {\n setErrorMessage(message);\n onFormErrorMessage?.(message);\n },\n });\n\n // Merge the hook's iframe ref with our own node ref so getIframe() can return the DOM node.\n const nodeRef = useRef<HTMLIFrameElement | null>(null);\n const hookRef = api.iframeProps.ref;\n const setIframe = useCallback<RefCallback<HTMLIFrameElement | null>>(\n (node) => {\n nodeRef.current = node;\n hookRef(node);\n },\n [hookRef],\n );\n\n useImperativeHandle(\n ref,\n () => ({\n submit: api.submit,\n reset: api.reset,\n updateDraft: api.updateDraft,\n triggerValidation: api.triggerValidation,\n prefill: api.prefill,\n submitAndWait: api.submitAndWait,\n getClient: api.getClient,\n getIframe: () => nodeRef.current,\n status: api.status,\n isReady: api.isReady,\n }),\n [api],\n );\n\n const showLoading = api.status === \"loading\" && loading != null;\n const showError = api.status === \"error\" && error != null;\n\n return (\n <>\n <div className={className} id={id} style={{ ...WRAPPER_STYLE, ...style }}>\n <iframe\n title=\"Captello form\"\n allow={DEFAULT_ALLOW}\n {...iframeProps}\n ref={setIframe}\n src={api.iframeProps.src}\n style={{ ...IFRAME_STYLE, ...iframeProps?.style }}\n />\n {showLoading ? <div style={OVERLAY_STYLE}>{loading}</div> : null}\n {showError ? (\n <div style={OVERLAY_STYLE}>{typeof error === \"function\" ? error(errorMessage) : error}</div>\n ) : null}\n </div>\n {typeof children === \"function\" ? children(api) : children}\n </>\n );\n}\n\n/**\n * Turnkey component for embedding a Captello capture form — the shortest path to a\n * working integration. Renders the `<iframe>`, wires {@link useCaptelloWebview} to it,\n * shows your `loading` / `error` overlays, and forwards a {@link CaptelloFormHandle} on\n * `ref` so a parent can `submit()` / `prefill()` without lifting state.\n *\n * `embedUrl` is required and the form fills its wrapper — size the form via `className` /\n * `style` (an iframe has no intrinsic height). Reach for {@link useCaptelloWebview} instead\n * when you need to own the markup.\n *\n * @example\n * function UlcForm({ token, email }: { token: string; email: string }) {\n * const ref = useRef<CaptelloFormHandle>(null);\n * return (\n * <CaptelloForm\n * ref={ref}\n * style={{ height: 600 }}\n * embedUrl={{\n * baseUrl: \"https://capture.captello.com\",\n * eventWebAccessToken: token,\n * mode: FormMode.Submit,\n * launcher: LauncherType.EventGenWeb,\n * }}\n * defaultFormValues={{ info: [{ ll_field_unique_identifier: \"Email\", value: email }] }}\n * onSubmissionBody={save}\n * loading={<Spinner />}\n * error={(msg) => <ErrorBanner>{msg}</ErrorBanner>}\n * >\n * {({ isReady }) => <button disabled={!isReady} onClick={() => ref.current?.submit()}>Submit</button>}\n * </CaptelloForm>\n * );\n * }\n */\nexport const CaptelloForm = forwardRef(CaptelloFormImpl);\nCaptelloForm.displayName = \"CaptelloForm\";\n\nexport { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from \"./client\";\nexport type { Unsubscribe } from \"./client\";\n"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/messages.ts","../src/client.ts"],"names":["OutboundMessageType","InboundMessageType"],"mappings":"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","file":"chunk-PFFBCSJ2.js","sourcesContent":["/**\n * The message protocol exchanged between the Captello capture webview (the iframe)\n * and its host page.\n *\n * Wire format (this is the contract — match it exactly):\n * - Every message is a JSON **string**. The webview sends outbound messages with\n * `JSON.stringify(message)` and reads inbound messages with `JSON.parse(event.data)`.\n * A host that posts a raw object instead of a string will be ignored, because the\n * webview's parser produces a non-object and bails.\n * - Every message is an object with a `type` discriminator. Inbound and outbound\n * types are disjoint string enums.\n *\n * Direction is named from the **webview's** point of view:\n * - {@link OutboundMessageType}: webview → host (the host listens for these).\n * - {@link InboundMessageType}: host → webview (the host sends these).\n */\n\nimport type { VisibleSubmissionDataItem } from \"./submission-data\";\n\n/* ------------------------------------------------------------------ *\n * Outbound: webview → host\n * ------------------------------------------------------------------ */\n\n/** Message `type` values the webview emits to its host. */\nexport enum OutboundMessageType {\n /** The form finished loading and rendering. Safe to interact with it after this. */\n FormLoadComplete = \"form_load_complete\",\n /** A user-facing error occurred; `data` is the translated, display-ready message. */\n FormErrorMessage = \"form_error_message\",\n /**\n * Emitted for embedded forms instead of submitting directly: `data` is the full\n * submission body for the host to persist/forward.\n */\n SubmissionBody = \"submission_body\",\n /** The form was submitted successfully. `action` indicates whether it was a new submission or an update. */\n FormSubmitSuccess = \"form_submit_success\",\n /** Connexions: the host should perform the profile redirect (embed mode). */\n ConnexionsProfileRedirect = \"connexions_profile_redirect\",\n /** Connexions: the host should trigger the vCard download (embed mode). */\n ConnexionsDownloadVcard = \"connexions_download_vcard\",\n}\n\n/**\n * Opaque submission payload carried by {@link OutboundMessageType.SubmissionBody}.\n *\n * This mirrors the webview's internal `FormSubmission` model. It is intentionally\n * typed as an open record here so the SDK stays decoupled from the app's full model\n * graph; the documented fields below are stable, the rest are passed through as-is.\n * Host code that needs the deep element-value types should treat `data` as untyped\n * and key it by element id (e.g. `\"element_12\"`, `\"element_12_3\"`).\n */\nexport interface SubmissionBody {\n id: number;\n form_id: number;\n prospect_id: number;\n email: string;\n first_name: string;\n last_name: string;\n full_name: string;\n company: string;\n phone: string;\n /** Submitted values keyed by element id / sub-element id. */\n data: Record<string, unknown>;\n /**\n * Visible, filled elements ready to render as key/value rows — one item per\n * element, discriminated by `element_type` (narrow on it for a precisely-typed\n * `element_value`). See {@link VisibleSubmissionDataItem}. May be absent on older\n * webview builds.\n */\n visible_submissions_data?: VisibleSubmissionDataItem[];\n submission_date: string;\n /** Query-string params the webview was loaded with, echoed back on submit. */\n query_parameters?: Record<string, string>;\n /** Additional fields from the webview's submission model are passed through verbatim. */\n [key: string]: unknown;\n}\n\n/**\n * Loose submission shape accepted when **pre-filling** the form (host → webview).\n *\n * Distinct from {@link SubmissionBody}: a received `submission_body` is always fully\n * populated, but when pre-filling you typically either round-trip a previously-received\n * body or pass a partial object assembled from your own data. A {@link SubmissionBody}\n * is assignable to this, so round-tripping just works.\n */\nexport interface SubmissionPrefill {\n /** Submitted values keyed by element id / sub-element id. */\n data?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface FormLoadCompleteMessage {\n type: OutboundMessageType.FormLoadComplete;\n}\ninterface FormSubmitSuccessMessage {\n type: OutboundMessageType.FormSubmitSuccess;\n action: \"create\" | \"update\";\n}\ninterface FormErrorMessageMessage {\n type: OutboundMessageType.FormErrorMessage;\n /** Translated, display-ready error text. */\n data: string;\n}\ninterface SubmissionBodyMessage {\n type: OutboundMessageType.SubmissionBody;\n data: SubmissionBody;\n}\ninterface ConnexionsProfileRedirectMessage {\n type: OutboundMessageType.ConnexionsProfileRedirect;\n}\ninterface ConnexionsDownloadVcardMessage {\n type: OutboundMessageType.ConnexionsDownloadVcard;\n}\n\n/** Discriminated union of every message the webview can emit to its host. */\nexport type OutboundMessage =\n | FormLoadCompleteMessage\n | FormSubmitSuccessMessage\n | FormErrorMessageMessage\n | SubmissionBodyMessage\n | ConnexionsProfileRedirectMessage\n | ConnexionsDownloadVcardMessage;\n\n/** Maps each outbound `type` to its full message shape (used by the client's `.on`). */\nexport type OutboundMessageMap = {\n [M in OutboundMessage as M[\"type\"]]: M;\n};\n\n/* ------------------------------------------------------------------ *\n * Inbound: host → webview\n * ------------------------------------------------------------------ */\n\n/** Message `type` values the host sends into the webview. */\nexport enum InboundMessageType {\n /** Programmatically trigger form submission (as if the user pressed submit). */\n Submit = \"submit_form\",\n /** Reset the form, clearing all entered values. */\n Reset = \"reset_form\",\n /** Pre-fill the form with existing data. */\n FormPrefill = \"form_prefill\",\n /** Switch the current submission into draft-update mode. */\n UpdateDraft = \"update_draft\",\n /** Run validation against a target field (or the whole form). */\n TriggerValidation = \"trigger_validation\",\n}\n\n/** Shape selector for {@link InboundMessageType.FormPrefill} payloads. */\nexport enum PrefillDataType {\n UlcSubmissionAndInfo = \"ulc_submission_and_info\",\n}\n\n/** Targets for {@link InboundMessageType.TriggerValidation}. */\nexport type ValidationTarget = \"invitation_code\" | \"email\" | \"all\";\n\n/**\n * A single transcription field/value item for the `info` prefill array.\n *\n * The webview matches each item to a form element by `ll_field_unique_identifier`\n * alone (e.g. `\"FirstName\"`, `\"Email\"`); `ll_field_id` is catalog metadata and is not\n * used for matching, so it is accepted as either a number or a string. `value` is\n * typically a string but may be a boolean (e.g. the PII opt-out field).\n */\nexport interface PrefillInfoItem {\n ll_field_unique_identifier: string;\n ll_field_id?: string | number;\n value: string | boolean;\n}\n\ninterface SubmitMessage {\n type: InboundMessageType.Submit;\n}\ninterface ResetMessage {\n type: InboundMessageType.Reset;\n}\ninterface UpdateDraftMessage {\n type: InboundMessageType.UpdateDraft;\n}\ninterface TriggerValidationMessage {\n type: InboundMessageType.TriggerValidation;\n target: ValidationTarget;\n}\ninterface PrefillMessage {\n type: InboundMessageType.FormPrefill;\n data_type: PrefillDataType.UlcSubmissionAndInfo;\n data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] };\n}\n\n/** Discriminated union of every message the host can send into the webview. */\nexport type InboundMessage =\n | SubmitMessage\n | ResetMessage\n | UpdateDraftMessage\n | TriggerValidationMessage\n | PrefillMessage;\n\n/* ------------------------------------------------------------------ *\n * Runtime guards / parsing\n * ------------------------------------------------------------------ */\n\nconst OUTBOUND_TYPES: ReadonlySet<string> = new Set(Object.values(OutboundMessageType));\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parses a raw `MessageEvent.data` value into a typed {@link OutboundMessage}, or\n * returns `null` if it is not a recognized Captello webview message.\n *\n * Accepts either a JSON string (the webview always sends strings) or an\n * already-parsed object, so it is robust to hosts/proxies that pre-parse.\n */\nexport function parseOutboundMessage(data: unknown): OutboundMessage | null {\n let value: unknown = data;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n return null;\n }\n }\n if (!isPlainObject(value)) return null;\n if (typeof value[\"type\"] !== \"string\" || !OUTBOUND_TYPES.has(value[\"type\"])) return null;\n return value as unknown as OutboundMessage;\n}\n","import { InboundMessageType, OutboundMessageType, parseOutboundMessage, PrefillDataType } from \"./messages\";\nimport type {\n InboundMessage,\n OutboundMessage,\n OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/** Listener for a specific outbound message type. */\nexport type OutboundListener<T extends OutboundMessageType> = (message: OutboundMessageMap[T]) => void;\n\n/** Listener for every outbound message (used by {@link CaptelloWebview.onAny}). */\nexport type AnyOutboundListener = (message: OutboundMessage) => void;\n\n/** Unsubscribe handle returned by every `on*` method. Calling it removes the listener. */\nexport type Unsubscribe = () => void;\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when the webview reports\n * a `form_error_message`. `message` is the translated, display-ready text.\n */\nexport class SubmissionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SubmissionError\";\n }\n}\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when no `submission_body`\n * or `form_error_message` arrives within the timeout.\n */\nexport class SubmissionTimeoutError extends Error {\n constructor(public readonly timeoutMs: number) {\n super(`Captello webview did not respond to submit within ${timeoutMs}ms.`);\n this.name = \"SubmissionTimeoutError\";\n }\n}\n\nexport interface CaptelloWebviewOptions {\n /**\n * Origin to validate incoming messages against and to target outgoing messages.\n * Strongly recommended — set it to the webview's origin (e.g.\n * `\"https://capture.captello.com\"`), e.g. `new URL(embedUrl).origin`.\n *\n * Defaults to `\"*\"`, which accepts messages from any origin and posts without an\n * origin check. Only acceptable for trusted/local development.\n */\n targetOrigin?: string;\n /**\n * The window to attach the `message` listener to. Defaults to the global `window`.\n * Override for testing or non-standard host environments.\n */\n hostWindow?: Window;\n /**\n * If `true` (default), incoming messages are accepted only when they originate\n * from the bound iframe's `contentWindow`. Set `false` only if the webview relays\n * messages through an intermediate window and source matching is impossible.\n */\n matchSource?: boolean;\n /**\n * If `true` (default), messages sent before the webview reports\n * `form_load_complete` are buffered and flushed, in order, once it's ready. This\n * removes a common footgun: calling `prefill(...)` right after mount would\n * otherwise post to a form that isn't listening yet and be silently dropped.\n *\n * Set `false` to send immediately (the legacy behavior). Note: a client that\n * attaches *after* the form already loaded will not have seen `form_load_complete`,\n * so its queued messages won't flush — create the client with the iframe.\n */\n queueUntilReady?: boolean;\n}\n\n/** Emits a console warning in development builds only. No-op in production / no bundler. */\nfunction devWarn(message: string): void {\n try {\n if (typeof process !== \"undefined\" && process.env && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(message);\n }\n } catch {\n /* `process` not defined (pure browser, no bundler define) → stay silent */\n }\n}\n\ntype ElementOrFrame = HTMLIFrameElement | { contentWindow: Window | null };\n\n/**\n * Host-side controller for an embedded Captello capture webview.\n *\n * Wraps a single `<iframe>` and encodes the full message protocol:\n * - **Receiving** (webview → host): subscribe with {@link on} / {@link onAny}.\n * - **Sending** (host → webview): use {@link submit}, {@link reset}, {@link prefill},\n * {@link triggerValidation}, {@link updateDraft}, or the lower-level {@link send}.\n *\n * Wire details handled for you: outgoing messages are `JSON.stringify`'d (the webview\n * parses inbound data with `JSON.parse`, so a raw object would be ignored), and\n * incoming messages are validated by origin + source before being parsed.\n *\n * @example\n * ```ts\n * const iframe = document.querySelector(\"iframe\")!;\n * const webview = new CaptelloWebview(iframe, {\n * targetOrigin: \"https://capture.captello.com\",\n * });\n *\n * webview.on(OutboundMessageType.FormLoadComplete, () => console.log(\"ready\"));\n * webview.on(OutboundMessageType.SubmissionBody, (msg) => save(msg.data));\n *\n * // later, drive the form:\n * webview.submit();\n *\n * // on teardown:\n * webview.destroy();\n * ```\n */\nexport class CaptelloWebview {\n private readonly frame: ElementOrFrame;\n private readonly targetOrigin: string;\n private readonly hostWindow: Window;\n private readonly matchSource: boolean;\n\n private readonly listeners = new Map<OutboundMessageType, Set<OutboundListener<OutboundMessageType>>>();\n private readonly anyListeners = new Set<AnyOutboundListener>();\n private readonly boundHandler: (event: MessageEvent) => void;\n private destroyed = false;\n\n private readonly queueUntilReady: boolean;\n /** True once `form_load_complete` has been observed. */\n private ready = false;\n /** Messages sent before ready, flushed in order on load. */\n private readonly outbox: InboundMessage[] = [];\n\n constructor(frame: ElementOrFrame, options: CaptelloWebviewOptions = {}) {\n if (!frame) {\n throw new Error(\"CaptelloWebview: an iframe element (or { contentWindow }) is required.\");\n }\n this.frame = frame;\n this.targetOrigin = options.targetOrigin ?? \"*\";\n this.matchSource = options.matchSource ?? true;\n this.queueUntilReady = options.queueUntilReady ?? true;\n\n // Nudge (dev only) when running without origin scoping. \"*\" accepts inbound\n // messages from any origin and posts outbound without an origin check — fine\n // for local/trusted dev, unsafe in production. Set targetOrigin to the\n // webview's origin, e.g. `new URL(embedUrl).origin`.\n if (this.targetOrigin === \"*\") {\n devWarn(\n '[captello-sdk] No targetOrigin set — defaulting to \"*\", which accepts messages ' +\n \"from any origin and posts without an origin check. Set targetOrigin to the webview's \" +\n \"origin (e.g. new URL(embedUrl).origin) in production.\",\n );\n }\n\n const hostWindow = options.hostWindow ?? (typeof window !== \"undefined\" ? window : undefined);\n if (!hostWindow) {\n throw new Error(\n \"CaptelloWebview: no host window available. Pass `hostWindow` when constructing outside a browser.\",\n );\n }\n this.hostWindow = hostWindow;\n\n this.boundHandler = (event: MessageEvent) => this.handleMessage(event);\n this.hostWindow.addEventListener(\"message\", this.boundHandler);\n }\n\n /** `true` once the webview has reported `form_load_complete`. */\n get isReady(): boolean {\n return this.ready;\n }\n\n /* -------------------------------------------------------------- *\n * Receiving (webview → host)\n * -------------------------------------------------------------- */\n\n /**\n * Subscribe to a single outbound message type. Returns an unsubscribe function.\n *\n * @example webview.on(OutboundMessageType.FormErrorMessage, (m) => toast(m.data));\n */\n on<T extends OutboundMessageType>(type: T, listener: OutboundListener<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 OutboundListener<OutboundMessageType>);\n return () => {\n set?.delete(listener as OutboundListener<OutboundMessageType>);\n };\n }\n\n /**\n * Subscribe once: the listener is removed automatically after it fires the first\n * time for `type`. Returns an unsubscribe function for cancelling early.\n */\n once<T extends OutboundMessageType>(type: T, listener: OutboundListener<T>): Unsubscribe {\n const off = this.on(type, (message) => {\n off();\n listener(message);\n });\n return off;\n }\n\n /** Subscribe to every outbound message regardless of type. Returns an unsubscribe function. */\n onAny(listener: AnyOutboundListener): Unsubscribe {\n this.anyListeners.add(listener);\n return () => {\n this.anyListeners.delete(listener);\n };\n }\n\n /* -------------------------------------------------------------- *\n * Sending (host → webview)\n * -------------------------------------------------------------- */\n\n /**\n * Low-level send: posts any inbound message to the webview as a JSON string.\n * Prefer the typed helpers below; use this only for forward-compatibility.\n *\n * When `queueUntilReady` is enabled (the default) and the form hasn't reported\n * `form_load_complete` yet, the message is buffered and flushed on load instead of\n * posted immediately.\n *\n * @throws if the iframe's `contentWindow` is not available (not yet loaded /\n * detached) and the message can't be queued.\n */\n send(message: InboundMessage): void {\n if (this.destroyed) {\n throw new Error(\"CaptelloWebview: cannot send after destroy().\");\n }\n if (this.queueUntilReady && !this.ready) {\n this.outbox.push(message);\n return;\n }\n this.postNow(message);\n }\n\n /** Posts a message immediately, bypassing the ready-queue. */\n private postNow(message: InboundMessage): void {\n const target = this.frame.contentWindow;\n if (!target) {\n throw new Error(\n \"CaptelloWebview: iframe.contentWindow is null. Wait for the iframe to load before sending.\",\n );\n }\n // The webview reads inbound data with JSON.parse(event.data), so it must be a string.\n target.postMessage(JSON.stringify(message), this.targetOrigin);\n }\n\n /** Marks the client ready and flushes any queued messages, in order. */\n private markReadyAndFlush(): void {\n if (this.ready) return;\n this.ready = true;\n const queued = this.outbox.splice(0);\n for (const message of queued) {\n try {\n this.postNow(message);\n } catch {\n /* iframe detached between load and flush — drop silently */\n }\n }\n }\n\n /** Programmatically submit the form (fire-and-forget). */\n submit(): void {\n this.send({ type: InboundMessageType.Submit });\n }\n\n /**\n * Submit the form and await the outcome.\n *\n * Sends `submit_form`, then resolves with the {@link SubmissionBody} when the\n * webview emits `submission_body`, or rejects with a {@link SubmissionError}\n * (carrying the translated message) when it emits `form_error_message`. Rejects\n * with a {@link SubmissionTimeoutError} if neither arrives within `timeoutMs`.\n *\n * This is the typed, leak-free version of the common \"click submit, wait for the\n * result\" flow — listeners are always cleaned up, including on timeout.\n *\n * @param timeoutMs how long to wait before giving up. Defaults to 60_000.\n * @example\n * try {\n * const body = await webview.submitAndWait();\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\n submitAndWait(timeoutMs = 60_000): Promise<SubmissionBody> {\n return new Promise<SubmissionBody>((resolve, reject) => {\n let settled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const cleanup = () => {\n settled = true;\n offSuccess();\n offError();\n if (timer !== undefined) clearTimeout(timer);\n };\n\n const offSuccess = this.on(OutboundMessageType.SubmissionBody, (message) => {\n if (settled) return;\n cleanup();\n resolve(message.data);\n });\n const offError = this.on(OutboundMessageType.FormErrorMessage, (message) => {\n if (settled) return;\n cleanup();\n reject(new SubmissionError(message.data));\n });\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => {\n if (settled) return;\n cleanup();\n reject(new SubmissionTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n\n try {\n this.send({ type: InboundMessageType.Submit });\n } catch (err) {\n if (!settled) {\n cleanup();\n reject(err);\n }\n }\n });\n }\n\n /** Reset the form, clearing all entered values. */\n reset(): void {\n this.send({ type: InboundMessageType.Reset });\n }\n\n /** Switch the current submission into draft-update mode. */\n updateDraft(): void {\n this.send({ type: InboundMessageType.UpdateDraft });\n }\n\n /** Run validation against a target field, or `\"all\"` for the whole form. */\n triggerValidation(target: ValidationTarget): void {\n this.send({ type: InboundMessageType.TriggerValidation, target });\n }\n\n /** Pre-fill form fields from a submission body, transcription items, or both. */\n prefill(data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }): void {\n this.send({\n type: InboundMessageType.FormPrefill,\n data_type: PrefillDataType.UlcSubmissionAndInfo,\n data,\n });\n }\n\n /* -------------------------------------------------------------- *\n * Lifecycle\n * -------------------------------------------------------------- */\n\n /** Remove the `message` listener and drop all subscriptions. Idempotent. */\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 this.outbox.length = 0;\n }\n\n /* -------------------------------------------------------------- *\n * Internals\n * -------------------------------------------------------------- */\n\n private handleMessage(event: MessageEvent): void {\n if (this.destroyed) return;\n\n // Origin check: skip when targetOrigin is the wildcard. Warn (dev only) if a\n // message that *looks* like ours is dropped on origin — a common \"why isn't my\n // listener firing?\" cause.\n if (this.targetOrigin !== \"*\" && event.origin !== this.targetOrigin) {\n if (parseOutboundMessage(event.data)) {\n devWarn(\n `[captello-sdk] Ignored a Captello message from origin \"${event.origin}\" ` +\n `(expected \"${this.targetOrigin}\"). Check the targetOrigin you passed.`,\n );\n }\n return;\n }\n\n // Source check: only accept messages from the bound iframe's window.\n if (this.matchSource) {\n const expected = this.frame.contentWindow;\n if (expected && event.source !== expected) {\n if (parseOutboundMessage(event.data)) {\n devWarn(\n \"[captello-sdk] Ignored a Captello message from an unexpected source window \" +\n \"(not the bound iframe). If the webview relays through another window, set matchSource: false.\",\n );\n }\n return;\n }\n }\n\n const message = parseOutboundMessage(event.data);\n if (!message) return;\n\n // Flip to ready (and flush queued sends) the moment the form loads, before\n // dispatching to listeners — so a listener can send and have it post immediately.\n if (message.type === OutboundMessageType.FormLoadComplete) {\n this.markReadyAndFlush();\n }\n\n const set = this.listeners.get(message.type);\n if (set) {\n // Copy to a snapshot so a listener that unsubscribes mid-dispatch is safe.\n for (const listener of [...set]) listener(message);\n }\n if (this.anyListeners.size) {\n for (const listener of [...this.anyListeners]) listener(message);\n }\n }\n}\n"]}
|