@captello/ulc-webview-sdk 0.4.0 → 0.5.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/README.md +104 -33
- package/dist/react.d.ts +80 -42
- package/dist/react.js +90 -2
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,14 +105,83 @@ try {
|
|
|
105
105
|
|
|
106
106
|
## React — `@captello/ulc-webview-sdk/react`
|
|
107
107
|
|
|
108
|
-
The React adapter is the smoothest way to integrate
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
The React adapter is the smoothest way to integrate, in two flavors:
|
|
109
|
+
|
|
110
|
+
- **`<CaptelloForm>`** — a turnkey component. Drop it in with an `embedUrl` and message
|
|
111
|
+
callbacks; it renders the `<iframe>`, shows your loading / error overlays, and forwards
|
|
112
|
+
the senders on a `ref`. The shortest path.
|
|
113
|
+
- **`useCaptelloWebview`** — the underlying hook, for when you'd rather own the markup.
|
|
114
|
+
|
|
115
|
+
Both own one `CaptelloWebview` for the iframe's lifetime: they build the embed URL, create
|
|
116
|
+
the client when the iframe mounts, wire outbound messages to typed callbacks, track
|
|
117
|
+
readiness, and destroy the client on unmount.
|
|
113
118
|
|
|
114
119
|
`react` is an optional peer dependency (React 18+).
|
|
115
120
|
|
|
121
|
+
### `<CaptelloForm>` — the turnkey component
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { useRef } from "react";
|
|
125
|
+
import { FormMode, LauncherType, ActionButtonPosition, type SubmissionBody } from "@captello/ulc-webview-sdk";
|
|
126
|
+
import { CaptelloForm, type CaptelloFormHandle } from "@captello/ulc-webview-sdk/react";
|
|
127
|
+
|
|
128
|
+
function UlcForm({
|
|
129
|
+
eventWebAccessToken,
|
|
130
|
+
onSubmitted,
|
|
131
|
+
}: {
|
|
132
|
+
eventWebAccessToken: string;
|
|
133
|
+
onSubmitted: (body: SubmissionBody) => void;
|
|
134
|
+
}) {
|
|
135
|
+
const form = useRef<CaptelloFormHandle>(null);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<CaptelloForm
|
|
139
|
+
ref={form}
|
|
140
|
+
style={{ height: 600 }} // an iframe has no intrinsic height — size the form here
|
|
141
|
+
embedUrl={{
|
|
142
|
+
baseUrl: "https://capture.captello.com",
|
|
143
|
+
eventWebAccessToken,
|
|
144
|
+
actionButtonPosition: ActionButtonPosition.Hidden, // b=2
|
|
145
|
+
mode: FormMode.Submit,
|
|
146
|
+
launcher: LauncherType.EventGenWeb,
|
|
147
|
+
}}
|
|
148
|
+
onSubmissionBody={(m) => onSubmitted(m.data)}
|
|
149
|
+
loading={<Spinner />}
|
|
150
|
+
error={(message) => <ErrorBanner>{message}</ErrorBanner>}
|
|
151
|
+
>
|
|
152
|
+
{({ isReady }) => (
|
|
153
|
+
<button disabled={!isReady} onClick={() => form.current?.submit()}>
|
|
154
|
+
Submit
|
|
155
|
+
</button>
|
|
156
|
+
)}
|
|
157
|
+
</CaptelloForm>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Props are the hook options (`embedUrl` or `targetOrigin`, every message callback, and the
|
|
163
|
+
client options) plus a few rendering conveniences:
|
|
164
|
+
|
|
165
|
+
- **`className` / `style` / `id`** — applied to the wrapper element. Size the form here; the
|
|
166
|
+
iframe fills it.
|
|
167
|
+
- **`iframeProps`** — attributes spread onto the `<iframe>` (`title`, `allow`, `sandbox`,
|
|
168
|
+
`name`, …). Defaults: `title="Captello form"`, `allow="camera; microphone; geolocation"`.
|
|
169
|
+
When you drive the URL yourself (no `embedUrl`), set `src` here.
|
|
170
|
+
- **`loading`** — a node shown, centered over the iframe, until `form_load_complete`. The
|
|
171
|
+
iframe stays mounted underneath so it keeps loading.
|
|
172
|
+
- **`error`** — a node (or `(message) => node`) shown when the form reports
|
|
173
|
+
`form_error_message`; the function form receives the translated, display-ready text.
|
|
174
|
+
- **`children`** — inline controls rendered after the form. A function receives the live api
|
|
175
|
+
(status + senders), so a submit button needs no separate `ref`.
|
|
176
|
+
- **`ref`** — a `CaptelloFormHandle`: the senders (`submit`, `reset`, `updateDraft`,
|
|
177
|
+
`triggerValidation`, `prefill`, `submitAndWait`) plus `status` / `isReady`, `getIframe()`,
|
|
178
|
+
and `getClient()`. Use it to drive the form from a parent without lifting state.
|
|
179
|
+
|
|
180
|
+
### `useCaptelloWebview` — the hook
|
|
181
|
+
|
|
182
|
+
Prefer to own the markup? The hook returns `iframeProps` to spread, an `isReady` flag, and
|
|
183
|
+
stable senders — so a typical form is just the hook plus an `<iframe>`.
|
|
184
|
+
|
|
116
185
|
```tsx
|
|
117
186
|
import { FormMode, LauncherType, ActionButtonPosition, type SubmissionBody } from "@captello/ulc-webview-sdk";
|
|
118
187
|
import { useCaptelloWebview } from "@captello/ulc-webview-sdk/react";
|
|
@@ -124,7 +193,7 @@ function UlcForm({
|
|
|
124
193
|
eventWebAccessToken: string;
|
|
125
194
|
onSubmitted: (body: SubmissionBody) => void;
|
|
126
195
|
}) {
|
|
127
|
-
const { iframeProps, isReady, submit,
|
|
196
|
+
const { iframeProps, isReady, submit, prefill } = useCaptelloWebview({
|
|
128
197
|
embedUrl: {
|
|
129
198
|
baseUrl: "https://capture.captello.com",
|
|
130
199
|
eventWebAccessToken,
|
|
@@ -146,7 +215,7 @@ function UlcForm({
|
|
|
146
215
|
}
|
|
147
216
|
```
|
|
148
217
|
|
|
149
|
-
What the hook
|
|
218
|
+
What the hook (and the component built on it) handle for you:
|
|
150
219
|
|
|
151
220
|
- **URL + origin.** Pass `embedUrl: { baseUrl, ...EmbedUrlOptions }` and the hook builds
|
|
152
221
|
the URL, derives `targetOrigin`, and returns it as `iframeProps.src` — no separate
|
|
@@ -155,14 +224,14 @@ What the hook handles for you:
|
|
|
155
224
|
- **Readiness.** `isReady` and `status` (`"loading" | "ready" | "error"`) — no manual
|
|
156
225
|
`useState` + `onFormLoadComplete` for a spinner.
|
|
157
226
|
- **Prefill timing.** Sends made before the form loads are queued and flushed on
|
|
158
|
-
`form_load_complete`, so you can `
|
|
227
|
+
`form_load_complete`, so you can `prefill(...)` as soon as you have data — no
|
|
159
228
|
gating on readiness, and no silently-dropped messages.
|
|
160
229
|
- **No memoization.** Callbacks are read fresh via a ref, so inline arrow functions
|
|
161
230
|
won't re-subscribe or re-create the client. The client is recreated only when
|
|
162
231
|
`targetOrigin` (or `embedUrl`) / `hostWindow` / `matchSource` / `queueUntilReady` change.
|
|
163
|
-
- **Stable senders** (`submit`, `reset`, `updateDraft`, `triggerValidation`,
|
|
164
|
-
`
|
|
165
|
-
|
|
232
|
+
- **Stable senders** (`submit`, `reset`, `updateDraft`, `triggerValidation`, `prefill`,
|
|
233
|
+
`submitAndWait`) — safe in deps or passed to children. `getClient()` returns the live
|
|
234
|
+
client for escape hatches.
|
|
166
235
|
|
|
167
236
|
### Callback props
|
|
168
237
|
|
|
@@ -200,24 +269,26 @@ point of view.
|
|
|
200
269
|
|
|
201
270
|
### Inbound — host → webview (you send)
|
|
202
271
|
|
|
203
|
-
| `type` | Method
|
|
204
|
-
| -------------------- |
|
|
205
|
-
| `submit_form` | `webview.submit()`
|
|
206
|
-
| `reset_form` | `webview.reset()`
|
|
207
|
-
| `update_draft` | `webview.updateDraft()`
|
|
208
|
-
| `trigger_validation` | `webview.triggerValidation(target)`
|
|
209
|
-
| `form_prefill` | `webview.
|
|
272
|
+
| `type` | Method | Notes |
|
|
273
|
+
| -------------------- | ----------------------------------------- | -------------------------------------------------- |
|
|
274
|
+
| `submit_form` | `webview.submit()` | Submit as if the user pressed the button. |
|
|
275
|
+
| `reset_form` | `webview.reset()` | Clear all entered values. |
|
|
276
|
+
| `update_draft` | `webview.updateDraft()` | Switch to draft-update mode. |
|
|
277
|
+
| `trigger_validation` | `webview.triggerValidation(target)` | `target`: `"email" \| "invitation_code" \| "all"`. |
|
|
278
|
+
| `form_prefill` | `webview.prefill({ submission?, info? })` | Submission body, transcription items, or both. |
|
|
279
|
+
|
|
280
|
+
### Prefill (`form_prefill`)
|
|
210
281
|
|
|
211
|
-
|
|
282
|
+
A single `prefill({ submission?, info? })` carries either or both payloads:
|
|
212
283
|
|
|
213
|
-
`
|
|
284
|
+
- `submission` — a `SubmissionPrefill` (a received `SubmissionBody` round-tripped, or a
|
|
285
|
+
partial `{ data?, ... }` you assemble).
|
|
286
|
+
- `info` — a list of `PrefillInfoItem`. The webview matches each item by
|
|
287
|
+
`ll_field_unique_identifier` (e.g. `"FirstName"`, `"Email"`); `ll_field_id` is optional
|
|
288
|
+
metadata (number or string) and `value` may be a string or boolean.
|
|
214
289
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
- `PrefillDataType.Info` → `prefillInfo(items)` — a list of `PrefillInfoItem`. The webview
|
|
218
|
-
matches each item by `ll_field_unique_identifier` (e.g. `"FirstName"`, `"Email"`);
|
|
219
|
-
`ll_field_id` is optional metadata (number or string) and `value` may be a string or boolean.
|
|
220
|
-
- `PrefillDataType.UlcSubmissionAndInfo` → `prefillSubmissionAndInfo({ submission, info })`.
|
|
290
|
+
Both ride the same wire `data_type` (`ulc_submission_and_info`); pass just the key(s) you
|
|
291
|
+
have.
|
|
221
292
|
|
|
222
293
|
## Rendering a submission
|
|
223
294
|
|
|
@@ -310,13 +381,13 @@ new CaptelloWebview(iframe, {
|
|
|
310
381
|
- `onAny(listener) => unsubscribe` — every outbound message.
|
|
311
382
|
- `submit()`, `reset()`, `updateDraft()`, `triggerValidation(target)` — inbound helpers.
|
|
312
383
|
- `submitAndWait(timeoutMs?)` — submit and await `submission_body` / `form_error_message` (see above).
|
|
313
|
-
- `
|
|
384
|
+
- `prefill({ submission?, info? })` — pre-fill from a submission body, transcription items, or both.
|
|
314
385
|
- `send(message)` — low-level escape hatch for any `InboundMessage`.
|
|
315
386
|
- `destroy()` — detach the listener and drop subscriptions (idempotent).
|
|
316
387
|
|
|
317
388
|
**Send queueing.** With `queueUntilReady` (default `true`), any send before the webview
|
|
318
389
|
reports `form_load_complete` is buffered and flushed, in order, on load — so calling
|
|
319
|
-
`
|
|
390
|
+
`prefill(...)` right after mount won't be silently dropped. A client that attaches
|
|
320
391
|
_after_ the form already loaded won't observe `form_load_complete`; either create the
|
|
321
392
|
client with the iframe, or pass `queueUntilReady: false` to send immediately. The
|
|
322
393
|
one-shot `/promises` helpers set `queueUntilReady: false` automatically.
|
|
@@ -375,8 +446,8 @@ short keys, and ad-hoc `window.addEventListener("message")` / `iframe.contentWin
|
|
|
375
446
|
calls. Replace them as follows.
|
|
376
447
|
|
|
377
448
|
**1. Message-type constants → SDK enums.** Delete local copies (e.g. `UlcFormActionTypeSent`,
|
|
378
|
-
`UlcFormActionTypeReceived`, `UlcFormDataType`) and import `InboundMessageType
|
|
379
|
-
`OutboundMessageType
|
|
449
|
+
`UlcFormActionTypeReceived`, `UlcFormDataType`) and import `InboundMessageType` /
|
|
450
|
+
`OutboundMessageType` (the `form_prefill` `data_type` is set for you by `prefill(...)`).
|
|
380
451
|
|
|
381
452
|
**2. Manual URL building → `buildEmbedUrl`.**
|
|
382
453
|
|
|
@@ -408,12 +479,12 @@ const body = await client.submitAndWait(); // throws SubmissionError on form_err
|
|
|
408
479
|
|
|
409
480
|
**5. `document.querySelector("#ulcForm").contentWindow.postMessage(...)` → client methods.**
|
|
410
481
|
Hold the `CaptelloWebview` instance in a ref/context and call `submit()` / `reset()` /
|
|
411
|
-
`
|
|
482
|
+
`prefill()` instead of re-querying the DOM and stringifying messages by hand.
|
|
412
483
|
|
|
413
|
-
**Prefill notes for migrators.** `
|
|
484
|
+
**Prefill notes for migrators.** `prefill({ info })` items are matched by
|
|
414
485
|
`ll_field_unique_identifier`; `ll_field_id` is optional (number or string) and `value`
|
|
415
486
|
may be a string or boolean — so existing payloads with numeric ids and boolean values
|
|
416
|
-
type-check as-is. `
|
|
487
|
+
type-check as-is. `prefill({ submission })` accepts a loose `SubmissionPrefill`, so a
|
|
417
488
|
previously-received `SubmissionBody` (or a partial `{ email?, data?, ... }`) can be passed
|
|
418
489
|
back directly.
|
|
419
490
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,47 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties, IframeHTMLAttributes, ReactNode, RefCallback } from 'react';
|
|
3
|
+
import { C as CaptelloWebviewOptions, a as OutboundMessageMap, O as OutboundMessageType, d as OutboundMessage, e as CaptelloWebview, V as ValidationTarget, f as SubmissionPrefill, P as PrefillInfoItem, S as SubmissionBody } from './client-Dik-Mjid.js';
|
|
3
4
|
export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-Dik-Mjid.js';
|
|
4
5
|
import { EmbedUrlOptions } from './index.js';
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
* React adapter for the Captello webview SDK — `@captello/ulc-webview-sdk/react`.
|
|
8
|
-
*
|
|
9
|
-
* {@link useCaptelloWebview} owns a {@link CaptelloWebview} for the lifetime of an
|
|
10
|
-
* iframe: it creates the client once the iframe mounts, wires the outbound messages
|
|
11
|
-
* you care about to typed callbacks, tracks readiness, and destroys the client on
|
|
12
|
-
* unmount. You get back `iframeProps` to spread onto your `<iframe>` (or a bare `ref`),
|
|
13
|
-
* an `isReady` flag, and stable senders (`submit`, `reset`, `prefill`, …).
|
|
14
|
-
*
|
|
15
|
-
* Sends made before the form loads are queued by the client and flushed on
|
|
16
|
-
* `form_load_complete`, so you can call `prefill(...)` as soon as you have data —
|
|
17
|
-
* no need to gate on readiness yourself.
|
|
18
|
-
*
|
|
19
|
-
* Callbacks are held in a ref and always called fresh, so you do NOT need to memoize
|
|
20
|
-
* them — passing inline arrow functions will not re-subscribe or re-create the client.
|
|
21
|
-
*
|
|
22
|
-
* `react` is an optional peer dependency; importing this entry point requires React 18+.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* function UlcForm({ token, onSubmitted }: { token: string; onSubmitted: (b: SubmissionBody) => void }) {
|
|
26
|
-
* const { iframeProps, isReady, submit } = useCaptelloWebview({
|
|
27
|
-
* embedUrl: {
|
|
28
|
-
* baseUrl: "https://capture.captello.com",
|
|
29
|
-
* eventWebAccessToken: token,
|
|
30
|
-
* mode: FormMode.Submit,
|
|
31
|
-
* launcher: LauncherType.EventGenWeb,
|
|
32
|
-
* },
|
|
33
|
-
* onSubmissionBody: (m) => onSubmitted(m.data),
|
|
34
|
-
* });
|
|
35
|
-
* return (
|
|
36
|
-
* <>
|
|
37
|
-
* {!isReady && <Spinner />}
|
|
38
|
-
* <iframe {...iframeProps} title="UlcForm" allow="camera; microphone" />
|
|
39
|
-
* <button onClick={submit}>Submit</button>
|
|
40
|
-
* </>
|
|
41
|
-
* );
|
|
42
|
-
* }
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
7
|
/** Per-message-type callback props accepted by {@link useCaptelloWebview}. */
|
|
46
8
|
interface CaptelloWebviewCallbacks {
|
|
47
9
|
onFormLoadComplete?: (message: OutboundMessageMap[OutboundMessageType.FormLoadComplete]) => void;
|
|
@@ -106,5 +68,81 @@ interface UseCaptelloWebviewResult {
|
|
|
106
68
|
* Binds a {@link CaptelloWebview} to an iframe's lifecycle. See the module doc for usage.
|
|
107
69
|
*/
|
|
108
70
|
declare function useCaptelloWebview(options: UseCaptelloWebviewOptions): UseCaptelloWebviewResult;
|
|
71
|
+
/**
|
|
72
|
+
* Imperative handle exposed on {@link CaptelloForm}'s `ref` — the same senders the hook
|
|
73
|
+
* returns, plus the current status and the underlying `<iframe>` node. Lets a parent
|
|
74
|
+
* drive the form (e.g. an external submit button) without lifting state.
|
|
75
|
+
*/
|
|
76
|
+
interface CaptelloFormHandle extends Pick<UseCaptelloWebviewResult, "submit" | "reset" | "updateDraft" | "triggerValidation" | "prefill" | "submitAndWait" | "getClient"> {
|
|
77
|
+
/** Current readiness: `"loading" | "ready" | "error"`. */
|
|
78
|
+
readonly status: CaptelloWebviewStatus;
|
|
79
|
+
/** `true` once the form has reported `form_load_complete`. */
|
|
80
|
+
readonly isReady: boolean;
|
|
81
|
+
/** The underlying `<iframe>` DOM node, or `null` before it mounts. */
|
|
82
|
+
getIframe: () => HTMLIFrameElement | null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Props for {@link CaptelloForm}: every {@link useCaptelloWebviewOptions} option (embed
|
|
86
|
+
* config + message callbacks + client options) plus rendering conveniences.
|
|
87
|
+
*/
|
|
88
|
+
interface CaptelloFormProps extends UseCaptelloWebviewOptions {
|
|
89
|
+
/** `className` for the wrapper element. */
|
|
90
|
+
className?: string;
|
|
91
|
+
/** `style` for the wrapper element — size the form here. The component adds `position: relative`; your values win. */
|
|
92
|
+
style?: CSSProperties;
|
|
93
|
+
/** `id` for the wrapper element. */
|
|
94
|
+
id?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Attributes spread onto the `<iframe>` — `title`, `allow`, `sandbox`, `name`, etc.
|
|
97
|
+
* Defaults: `title="Captello form"`, `allow="camera; microphone; geolocation"`. When you
|
|
98
|
+
* drive the URL yourself (no `embedUrl`), set `src` here.
|
|
99
|
+
*/
|
|
100
|
+
iframeProps?: Omit<IframeHTMLAttributes<HTMLIFrameElement>, "ref">;
|
|
101
|
+
/** Rendered, centered over the iframe, while it is loading. The iframe stays mounted underneath. */
|
|
102
|
+
loading?: ReactNode;
|
|
103
|
+
/**
|
|
104
|
+
* Rendered, centered over the iframe, when the form reports `form_error_message`.
|
|
105
|
+
* Pass a function to receive the translated, display-ready error text.
|
|
106
|
+
*/
|
|
107
|
+
error?: ReactNode | ((message: string | undefined) => ReactNode);
|
|
108
|
+
/**
|
|
109
|
+
* Inline controls rendered after the form. A function receives the live api
|
|
110
|
+
* (status + senders), so you can wire a submit button without a `ref`.
|
|
111
|
+
*/
|
|
112
|
+
children?: ReactNode | ((api: UseCaptelloWebviewResult) => ReactNode);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Turnkey component for embedding a Captello capture form — the shortest path to a
|
|
116
|
+
* working integration. Renders the `<iframe>`, wires {@link useCaptelloWebview} to it,
|
|
117
|
+
* shows your `loading` / `error` overlays, and forwards a {@link CaptelloFormHandle} on
|
|
118
|
+
* `ref` so a parent can `submit()` / `prefill()` without lifting state.
|
|
119
|
+
*
|
|
120
|
+
* Pass `embedUrl` and the form fills its wrapper — size the form via `className` / `style`
|
|
121
|
+
* (an iframe has no intrinsic height). Reach for {@link useCaptelloWebview} instead when
|
|
122
|
+
* you need to own the markup.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* function UlcForm({ token }: { token: string }) {
|
|
126
|
+
* const ref = useRef<CaptelloFormHandle>(null);
|
|
127
|
+
* return (
|
|
128
|
+
* <CaptelloForm
|
|
129
|
+
* ref={ref}
|
|
130
|
+
* style={{ height: 600 }}
|
|
131
|
+
* embedUrl={{
|
|
132
|
+
* baseUrl: "https://capture.captello.com",
|
|
133
|
+
* eventWebAccessToken: token,
|
|
134
|
+
* mode: FormMode.Submit,
|
|
135
|
+
* launcher: LauncherType.EventGenWeb,
|
|
136
|
+
* }}
|
|
137
|
+
* onSubmissionBody={(m) => save(m.data)}
|
|
138
|
+
* loading={<Spinner />}
|
|
139
|
+
* error={(msg) => <ErrorBanner>{msg}</ErrorBanner>}
|
|
140
|
+
* >
|
|
141
|
+
* {({ isReady }) => <button disabled={!isReady} onClick={() => ref.current?.submit()}>Submit</button>}
|
|
142
|
+
* </CaptelloForm>
|
|
143
|
+
* );
|
|
144
|
+
* }
|
|
145
|
+
*/
|
|
146
|
+
declare const CaptelloForm: react.ForwardRefExoticComponent<CaptelloFormProps & react.RefAttributes<CaptelloFormHandle>>;
|
|
109
147
|
|
|
110
|
-
export { type CaptelloIframeProps, CaptelloWebview, type CaptelloWebviewCallbacks, type CaptelloWebviewStatus, type EmbedUrlConfig, type UseCaptelloWebviewOptions, type UseCaptelloWebviewResult, useCaptelloWebview };
|
|
148
|
+
export { CaptelloForm, type CaptelloFormHandle, type CaptelloFormProps, type CaptelloIframeProps, CaptelloWebview, type CaptelloWebviewCallbacks, type CaptelloWebviewStatus, type EmbedUrlConfig, type UseCaptelloWebviewOptions, type UseCaptelloWebviewResult, useCaptelloWebview };
|
package/dist/react.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { buildEmbedUrl } from './chunk-4E7OW4RJ.js';
|
|
2
2
|
import { CaptelloWebview, OutboundMessageType } from './chunk-PFFBCSJ2.js';
|
|
3
3
|
export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-PFFBCSJ2.js';
|
|
4
|
-
import {
|
|
4
|
+
import { forwardRef, useState, useRef, useCallback, useImperativeHandle, useEffect } from 'react';
|
|
5
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
5
6
|
|
|
6
7
|
var CALLBACK_BY_TYPE = {
|
|
7
8
|
["form_load_complete" /* FormLoadComplete */]: "onFormLoadComplete",
|
|
@@ -99,7 +100,94 @@ function useCaptelloWebview(options) {
|
|
|
99
100
|
submitAndWait
|
|
100
101
|
};
|
|
101
102
|
}
|
|
103
|
+
var DEFAULT_ALLOW = "camera; microphone; geolocation";
|
|
104
|
+
var WRAPPER_STYLE = { position: "relative" };
|
|
105
|
+
var IFRAME_STYLE = { display: "block", width: "100%", height: "100%", border: 0 };
|
|
106
|
+
var OVERLAY_STYLE = {
|
|
107
|
+
position: "absolute",
|
|
108
|
+
inset: 0,
|
|
109
|
+
display: "flex",
|
|
110
|
+
alignItems: "center",
|
|
111
|
+
justifyContent: "center"
|
|
112
|
+
};
|
|
113
|
+
function CaptelloFormImpl(props, ref) {
|
|
114
|
+
const {
|
|
115
|
+
className,
|
|
116
|
+
style,
|
|
117
|
+
id,
|
|
118
|
+
iframeProps,
|
|
119
|
+
loading,
|
|
120
|
+
error,
|
|
121
|
+
children,
|
|
122
|
+
onFormLoadComplete,
|
|
123
|
+
onFormErrorMessage,
|
|
124
|
+
...options
|
|
125
|
+
} = props;
|
|
126
|
+
const [errorMessage, setErrorMessage] = useState(void 0);
|
|
127
|
+
const api = useCaptelloWebview({
|
|
128
|
+
...options,
|
|
129
|
+
// Wrap the two status-bearing callbacks to track the error text, then forward to
|
|
130
|
+
// the caller's handler. The hook reads callbacks fresh, so these inline wrappers
|
|
131
|
+
// don't re-subscribe or re-create the client.
|
|
132
|
+
onFormLoadComplete: (message) => {
|
|
133
|
+
setErrorMessage(void 0);
|
|
134
|
+
onFormLoadComplete?.(message);
|
|
135
|
+
},
|
|
136
|
+
onFormErrorMessage: (message) => {
|
|
137
|
+
setErrorMessage(message.data);
|
|
138
|
+
onFormErrorMessage?.(message);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const nodeRef = useRef(null);
|
|
142
|
+
const hookRef = api.iframeProps.ref;
|
|
143
|
+
const setIframe = useCallback(
|
|
144
|
+
(node) => {
|
|
145
|
+
nodeRef.current = node;
|
|
146
|
+
hookRef(node);
|
|
147
|
+
},
|
|
148
|
+
[hookRef]
|
|
149
|
+
);
|
|
150
|
+
useImperativeHandle(
|
|
151
|
+
ref,
|
|
152
|
+
() => ({
|
|
153
|
+
submit: api.submit,
|
|
154
|
+
reset: api.reset,
|
|
155
|
+
updateDraft: api.updateDraft,
|
|
156
|
+
triggerValidation: api.triggerValidation,
|
|
157
|
+
prefill: api.prefill,
|
|
158
|
+
submitAndWait: api.submitAndWait,
|
|
159
|
+
getClient: api.getClient,
|
|
160
|
+
getIframe: () => nodeRef.current,
|
|
161
|
+
status: api.status,
|
|
162
|
+
isReady: api.isReady
|
|
163
|
+
}),
|
|
164
|
+
[api]
|
|
165
|
+
);
|
|
166
|
+
const src = api.iframeProps.src ?? iframeProps?.src;
|
|
167
|
+
const showLoading = api.status === "loading" && loading != null;
|
|
168
|
+
const showError = api.status === "error" && error != null;
|
|
169
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("div", { className, id, style: { ...WRAPPER_STYLE, ...style }, children: [
|
|
171
|
+
/* @__PURE__ */ jsx(
|
|
172
|
+
"iframe",
|
|
173
|
+
{
|
|
174
|
+
title: "Captello form",
|
|
175
|
+
allow: DEFAULT_ALLOW,
|
|
176
|
+
...iframeProps,
|
|
177
|
+
ref: setIframe,
|
|
178
|
+
src,
|
|
179
|
+
style: { ...IFRAME_STYLE, ...iframeProps?.style }
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
showLoading ? /* @__PURE__ */ jsx("div", { style: OVERLAY_STYLE, children: loading }) : null,
|
|
183
|
+
showError ? /* @__PURE__ */ jsx("div", { style: OVERLAY_STYLE, children: typeof error === "function" ? error(errorMessage) : error }) : null
|
|
184
|
+
] }),
|
|
185
|
+
typeof children === "function" ? children(api) : children
|
|
186
|
+
] });
|
|
187
|
+
}
|
|
188
|
+
var CaptelloForm = forwardRef(CaptelloFormImpl);
|
|
189
|
+
CaptelloForm.displayName = "CaptelloForm";
|
|
102
190
|
|
|
103
|
-
export { useCaptelloWebview };
|
|
191
|
+
export { CaptelloForm, useCaptelloWebview };
|
|
104
192
|
//# sourceMappingURL=react.js.map
|
|
105
193
|
//# sourceMappingURL=react.js.map
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.ts"],"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 * {@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.\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: (m) => onSubmitted(m.data),\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 { useCallback, useEffect, useRef, useState, type RefCallback } 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 OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/** Per-message-type callback props accepted by {@link useCaptelloWebview}. */\nexport interface CaptelloWebviewCallbacks {\n onFormLoadComplete?: (message: OutboundMessageMap[OutboundMessageType.FormLoadComplete]) => void;\n onFormErrorMessage?: (message: OutboundMessageMap[OutboundMessageType.FormErrorMessage]) => void;\n onSubmissionBody?: (message: OutboundMessageMap[OutboundMessageType.SubmissionBody]) => void;\n onFormSubmitSuccess?: (message: OutboundMessageMap[OutboundMessageType.FormSubmitSuccess]) => void;\n onConnexionsProfileRedirect?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsProfileRedirect]) => void;\n onConnexionsDownloadVcard?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsDownloadVcard]) => void;\n /** Catch-all: called for every outbound message, 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 * Options for {@link useCaptelloWebview}.\n *\n * Provide **either** `embedUrl` (the hook builds the URL and derives `targetOrigin`,\n * returning `iframeProps.src`) **or** your own `targetOrigin` (you set the iframe `src`\n * yourself). Plus message callbacks and the usual client options.\n */\nexport interface UseCaptelloWebviewOptions extends CaptelloWebviewOptions, CaptelloWebviewCallbacks {\n /** Build the iframe URL and derive `targetOrigin` from it. Sets `iframeProps.src`. */\n embedUrl?: EmbedUrlConfig;\n}\n\n/** Readiness of the embedded form. */\nexport type CaptelloWebviewStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Props to spread onto the `<iframe>`. `src` is present only when `embedUrl` is given. */\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} />`. Includes `src` if `embedUrl` was given. */\n iframeProps: CaptelloIframeProps;\n /** The iframe ref callback (same as `iframeProps.ref`), if you'd rather set `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\nconst CALLBACK_BY_TYPE: Record<OutboundMessageType, keyof CaptelloWebviewCallbacks> = {\n [OutboundMessageType.FormLoadComplete]: \"onFormLoadComplete\",\n [OutboundMessageType.FormErrorMessage]: \"onFormErrorMessage\",\n [OutboundMessageType.SubmissionBody]: \"onSubmissionBody\",\n [OutboundMessageType.FormSubmitSuccess]: \"onFormSubmitSuccess\",\n [OutboundMessageType.ConnexionsProfileRedirect]: \"onConnexionsProfileRedirect\",\n [OutboundMessageType.ConnexionsDownloadVcard]: \"onConnexionsDownloadVcard\",\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 // Resolve the URL + the targetOrigin to use. embedUrl wins; otherwise use the\n // explicit targetOrigin. Recompute only when the URL-affecting inputs change.\n const src = embedUrl ? buildEmbedUrl(embedUrl.baseUrl, embedUrl) : undefined;\n const targetOrigin = src ? new URL(src).origin : options.targetOrigin;\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 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 const handler = optionsRef.current[CALLBACK_BY_TYPE[type]] as\n | ((m: typeof message) => void)\n | undefined;\n handler?.(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[] }) =>\n 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 = src ? { ref: attach, src } : { ref: attach };\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\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.\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: (m) => onSubmitted(m.data),\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 OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/** Per-message-type callback props accepted by {@link useCaptelloWebview}. */\nexport interface CaptelloWebviewCallbacks {\n onFormLoadComplete?: (message: OutboundMessageMap[OutboundMessageType.FormLoadComplete]) => void;\n onFormErrorMessage?: (message: OutboundMessageMap[OutboundMessageType.FormErrorMessage]) => void;\n onSubmissionBody?: (message: OutboundMessageMap[OutboundMessageType.SubmissionBody]) => void;\n onFormSubmitSuccess?: (message: OutboundMessageMap[OutboundMessageType.FormSubmitSuccess]) => void;\n onConnexionsProfileRedirect?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsProfileRedirect]) => void;\n onConnexionsDownloadVcard?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsDownloadVcard]) => void;\n /** Catch-all: called for every outbound message, 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 * Options for {@link useCaptelloWebview}.\n *\n * Provide **either** `embedUrl` (the hook builds the URL and derives `targetOrigin`,\n * returning `iframeProps.src`) **or** your own `targetOrigin` (you set the iframe `src`\n * yourself). Plus message callbacks and the usual client options.\n */\nexport interface UseCaptelloWebviewOptions extends CaptelloWebviewOptions, CaptelloWebviewCallbacks {\n /** Build the iframe URL and derive `targetOrigin` from it. Sets `iframeProps.src`. */\n embedUrl?: EmbedUrlConfig;\n}\n\n/** Readiness of the embedded form. */\nexport type CaptelloWebviewStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Props to spread onto the `<iframe>`. `src` is present only when `embedUrl` is given. */\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} />`. Includes `src` if `embedUrl` was given. */\n iframeProps: CaptelloIframeProps;\n /** The iframe ref callback (same as `iframeProps.ref`), if you'd rather set `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\nconst CALLBACK_BY_TYPE: Record<OutboundMessageType, keyof CaptelloWebviewCallbacks> = {\n [OutboundMessageType.FormLoadComplete]: \"onFormLoadComplete\",\n [OutboundMessageType.FormErrorMessage]: \"onFormErrorMessage\",\n [OutboundMessageType.SubmissionBody]: \"onSubmissionBody\",\n [OutboundMessageType.FormSubmitSuccess]: \"onFormSubmitSuccess\",\n [OutboundMessageType.ConnexionsProfileRedirect]: \"onConnexionsProfileRedirect\",\n [OutboundMessageType.ConnexionsDownloadVcard]: \"onConnexionsDownloadVcard\",\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 // Resolve the URL + the targetOrigin to use. embedUrl wins; otherwise use the\n // explicit targetOrigin. Recompute only when the URL-affecting inputs change.\n const src = embedUrl ? buildEmbedUrl(embedUrl.baseUrl, embedUrl) : undefined;\n const targetOrigin = src ? new URL(src).origin : options.targetOrigin;\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 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 const handler = optionsRef.current[CALLBACK_BY_TYPE[type]] as\n | ((m: typeof message) => void)\n | undefined;\n handler?.(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 = src ? { ref: attach, src } : { ref: attach };\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\"`. When you\n * drive the URL yourself (no `embedUrl`), set `src` here.\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: (message) => {\n setErrorMessage(undefined);\n onFormLoadComplete?.(message);\n },\n onFormErrorMessage: (message) => {\n setErrorMessage(message.data);\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 // embedUrl-derived src wins; otherwise use a src the caller passed via iframeProps.\n const src = api.iframeProps.src ?? iframeProps?.src;\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={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 * Pass `embedUrl` and the form fills its wrapper — size the form via `className` / `style`\n * (an iframe has no intrinsic height). Reach for {@link useCaptelloWebview} instead when\n * you need to own the markup.\n *\n * @example\n * function UlcForm({ token }: { token: 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 * onSubmissionBody={(m) => save(m.data)}\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