@captello/ulc-webview-sdk 1.0.0 → 1.2.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 +123 -0
- package/README.md +352 -5
- package/dist/app-host.d.ts +228 -0
- package/dist/app-host.js +170 -0
- package/dist/app-host.js.map +1 -0
- package/dist/{chunk-PFFBCSJ2.js → chunk-ZPVXZW2B.js} +235 -19
- package/dist/chunk-ZPVXZW2B.js.map +1 -0
- package/dist/{chunk-4E7OW4RJ.js → chunk-ZX4AKPWF.js} +5 -3
- package/dist/chunk-ZX4AKPWF.js.map +1 -0
- package/dist/client-CAMlFA8s.d.ts +898 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +2 -2
- package/dist/promises.d.ts +3 -5
- package/dist/promises.js +2 -2
- package/dist/promises.js.map +1 -1
- package/dist/react.d.ts +19 -4
- package/dist/react.js +14 -3
- package/dist/react.js.map +1 -1
- package/package.json +83 -73
- package/dist/chunk-4E7OW4RJ.js.map +0 -1
- package/dist/chunk-PFFBCSJ2.js.map +0 -1
- package/dist/client-CalIoKT6.d.ts +0 -517
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,
|
|
1
|
+
export { A as AddressSubmissionValue, g as AnyOutboundListener, h as AttachOptions, i as AttachmentValue, B as BusinessCardValue, f as CaptelloWebview, C as CaptelloWebviewOptions, D as DraftSubmissionData, j as FormElementType, F as FrameLike, I as InboundMessage, k as InboundMessageType, N as NameSubmissionValue, l as OrderCheckboxSubmissionData, m as OrderRadioSubmissionData, n as OutboundListener, d as OutboundMessage, a as OutboundMessageMap, O as OutboundMessageType, P as PrefillInfoItem, S as SubmissionBody, b as SubmissionError, e as SubmissionPrefill, o as SubmissionPrefillDataItem, p as SubmissionQuestionData, c as SubmissionTimeoutError, q as TranscribeScannerReply, T as TranscribeScannerRequestHandler, r as TranscribeScannerRequestMessage, s as TranscribeScannerRequestRef, t as TranscribeScannerResultData, u as TranscribeScannerResultMessage, v as TranscribedScannerField, U as Unsubscribe, V as ValidationTarget, w as VisibleSubmissionDataItem, x as VisibleSubmissionElementType, y as VisibleSubmissionElementValueMap, z as attach, E as parseOutboundMessage } from './client-CAMlFA8s.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Builder for the Captello capture webview embed URL.
|
|
@@ -28,7 +28,14 @@ declare enum EmbedParam {
|
|
|
28
28
|
/** Edit mode read-only: locks email-mapped and invitation-code elements. */
|
|
29
29
|
Emro = "emro",
|
|
30
30
|
/** Context for filtering form-fill actions (e.g. MMP outbound/inbound/notes). */
|
|
31
|
-
UseIn = "useIn"
|
|
31
|
+
UseIn = "useIn",
|
|
32
|
+
/**
|
|
33
|
+
* Show the transcribe button: the form renders a button that emits a
|
|
34
|
+
* `host_processing_request` with `processing_type: "transcribe_scanner_request"`.
|
|
35
|
+
* Only enable it when the host answers those requests (e.g. via
|
|
36
|
+
* `CaptelloWebview.onProcessingRequest`).
|
|
37
|
+
*/
|
|
38
|
+
ShowTranscribeButton = "show_transcribe_button"
|
|
32
39
|
}
|
|
33
40
|
/** Form render mode (the webview's `FormMode`). */
|
|
34
41
|
declare enum FormMode {
|
|
@@ -100,6 +107,13 @@ interface EmbedUrlOptions {
|
|
|
100
107
|
connexionsEmbedMode?: boolean;
|
|
101
108
|
/** Edit mode read-only. */
|
|
102
109
|
emro?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Show the transcribe button in the form (emits `host_processing_request` with
|
|
112
|
+
* `processing_type: "transcribe_scanner_request"` when pressed). Enable only when
|
|
113
|
+
* the host answers those requests (e.g. via `CaptelloWebview.onProcessingRequest`) —
|
|
114
|
+
* otherwise the button times out with an error for the user.
|
|
115
|
+
*/
|
|
116
|
+
showTranscribeButton?: boolean;
|
|
103
117
|
/**
|
|
104
118
|
* Extra query params to append verbatim (e.g. prospect tracking params the
|
|
105
119
|
* webview forwards on submit). Values are stringified; `undefined`/`null` skipped.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl } from './chunk-
|
|
2
|
-
export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, parseOutboundMessage } from './chunk-
|
|
1
|
+
export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl } from './chunk-ZX4AKPWF.js';
|
|
2
|
+
export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, attach, parseOutboundMessage } from './chunk-ZPVXZW2B.js';
|
|
3
3
|
|
|
4
4
|
// src/submission-data.ts
|
|
5
5
|
var FormElementType = /* @__PURE__ */ ((FormElementType2) => {
|
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, F as FrameLike, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-CAMlFA8s.js';
|
|
2
|
+
export { b as SubmissionError, c as SubmissionTimeoutError } from './client-CAMlFA8s.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.
|
|
@@ -17,9 +17,7 @@ export { b as SubmissionError, c as SubmissionTimeoutError } from './client-CalI
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */
|
|
20
|
-
type ElementOrFrame =
|
|
21
|
-
contentWindow: Window | null;
|
|
22
|
-
};
|
|
20
|
+
type ElementOrFrame = FrameLike;
|
|
23
21
|
/** Options shared by every promise helper. */
|
|
24
22
|
interface WaitOptions extends Pick<CaptelloWebviewOptions, "targetOrigin" | "hostWindow" | "matchSource"> {
|
|
25
23
|
/**
|
package/dist/promises.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CaptelloWebview } from './chunk-
|
|
2
|
-
export { SubmissionError, SubmissionTimeoutError } from './chunk-
|
|
1
|
+
import { CaptelloWebview } from './chunk-ZPVXZW2B.js';
|
|
2
|
+
export { SubmissionError, SubmissionTimeoutError } from './chunk-ZPVXZW2B.js';
|
|
3
3
|
|
|
4
4
|
// src/promises.ts
|
|
5
5
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
package/dist/promises.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/promises.ts"],"names":[],"mappings":";;;;AAkCA,IAAM,kBAAA,GAAqB,GAAA;AAGpB,IAAM,mBAAA,GAAN,cAAkC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACoB,aACA,SAAA,EAClB;AACE,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,gBAAA,EAAmB,WAAW,CAAA,4BAAA,CAA8B,CAAA;AAH9E,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EAChB;AACJ;AAEA,SAAS,cAAc,OAAA,EAA8C;AACjE,EAAA,OAAO;AAAA,IACH,cAAc,OAAA,CAAQ,YAAA;AAAA,IACtB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,aAAa,OAAA,CAAQ,WAAA;AAAA;AAAA;AAAA,IAGrB,eAAA,EAAiB;AAAA,GACrB;AACJ;AASO,SAAS,cAAA,CACZ,KAAA,EACA,IAAA,EACA,OAAA,GAAuB,EAAC,EACM;AAC9B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,IAAI,OAAA,CAA+B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3D,IAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,IAAA,IAAI,KAAA;AAEJ,IAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAmB;AAC/B,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAC3C,MAAA,MAAA,CAAO,OAAA,EAAQ;AACf,MAAA,EAAA,EAAG;AAAA,IACP,CAAA;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,OAAA,KAAY,OAAO,MAAM,OAAA,CAAQ,OAAO,CAAC,CAAC,CAAA;AAE7D,IAAA,IAAI,SAAA,GAAY,CAAA,IAAK,SAAA,KAAc,QAAA,EAAU;AACzC,MAAA,KAAA,GAAQ,UAAA,CAAW,MAAM,MAAA,CAAO,MAAM,MAAA,CAAO,IAAI,mBAAA,CAAoB,IAAA,EAAM,SAAS,CAAC,CAAC,CAAA,EAAG,SAAS,CAAA;AAAA,IACtG;AAAA,EACJ,CAAC,CAAA;AACL;AAUO,SAAS,eAAA,CAAgB,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAAkB;AAC7F,EAAA,OAAO,eAAe,KAAA,EAAA,oBAAA,yBAA6C,OAAO,CAAA,CAAE,IAAA,CAAK,MAAM,MAAS,CAAA;AACpG;AAkBO,SAAS,UAAA,CAAW,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAA4B;AAClG,EAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,MAAA,CAAO,cAAc,SAAS,CAAA,CAAE,QAAQ,MAAM,MAAA,CAAO,SAAS,CAAA;AACzE","file":"promises.js","sourcesContent":["/**\n * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.\n *\n * Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are\n * fire-once-and-await utilities that take an iframe directly: create a short-lived\n * client internally, wait for the relevant message, then tear it down. Ideal for\n * `await`-style code (e.g. \"submit and get the body\", \"wait until the form loads\").\n *\n * @example\n * import { submitForm, waitForFormLoad } from \"@captello/ulc-webview-sdk/promises\";\n *\n * await waitForFormLoad(iframe, { targetOrigin });\n * const body = await submitForm(iframe, { targetOrigin });\n */\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions } from \"./client\";\nimport { OutboundMessageType } from \"./messages\";\nimport type { OutboundMessageMap, SubmissionBody } from \"./messages\";\n\nexport { SubmissionError, SubmissionTimeoutError } from \"./client\";\n\n/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */\ntype ElementOrFrame =
|
|
1
|
+
{"version":3,"sources":["../src/promises.ts"],"names":[],"mappings":";;;;AAkCA,IAAM,kBAAA,GAAqB,GAAA;AAGpB,IAAM,mBAAA,GAAN,cAAkC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACoB,aACA,SAAA,EAClB;AACE,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,gBAAA,EAAmB,WAAW,CAAA,4BAAA,CAA8B,CAAA;AAH9E,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EAChB;AACJ;AAEA,SAAS,cAAc,OAAA,EAA8C;AACjE,EAAA,OAAO;AAAA,IACH,cAAc,OAAA,CAAQ,YAAA;AAAA,IACtB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,aAAa,OAAA,CAAQ,WAAA;AAAA;AAAA;AAAA,IAGrB,eAAA,EAAiB;AAAA,GACrB;AACJ;AASO,SAAS,cAAA,CACZ,KAAA,EACA,IAAA,EACA,OAAA,GAAuB,EAAC,EACM;AAC9B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,IAAI,OAAA,CAA+B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3D,IAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,IAAA,IAAI,KAAA;AAEJ,IAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAmB;AAC/B,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAC3C,MAAA,MAAA,CAAO,OAAA,EAAQ;AACf,MAAA,EAAA,EAAG;AAAA,IACP,CAAA;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,OAAA,KAAY,OAAO,MAAM,OAAA,CAAQ,OAAO,CAAC,CAAC,CAAA;AAE7D,IAAA,IAAI,SAAA,GAAY,CAAA,IAAK,SAAA,KAAc,QAAA,EAAU;AACzC,MAAA,KAAA,GAAQ,UAAA,CAAW,MAAM,MAAA,CAAO,MAAM,MAAA,CAAO,IAAI,mBAAA,CAAoB,IAAA,EAAM,SAAS,CAAC,CAAC,CAAA,EAAG,SAAS,CAAA;AAAA,IACtG;AAAA,EACJ,CAAC,CAAA;AACL;AAUO,SAAS,eAAA,CAAgB,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAAkB;AAC7F,EAAA,OAAO,eAAe,KAAA,EAAA,oBAAA,yBAA6C,OAAO,CAAA,CAAE,IAAA,CAAK,MAAM,MAAS,CAAA;AACpG;AAkBO,SAAS,UAAA,CAAW,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAA4B;AAClG,EAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,MAAA,CAAO,cAAc,SAAS,CAAA,CAAE,QAAQ,MAAM,MAAA,CAAO,SAAS,CAAA;AACzE","file":"promises.js","sourcesContent":["/**\n * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.\n *\n * Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are\n * fire-once-and-await utilities that take an iframe directly: create a short-lived\n * client internally, wait for the relevant message, then tear it down. Ideal for\n * `await`-style code (e.g. \"submit and get the body\", \"wait until the form loads\").\n *\n * @example\n * import { submitForm, waitForFormLoad } from \"@captello/ulc-webview-sdk/promises\";\n *\n * await waitForFormLoad(iframe, { targetOrigin });\n * const body = await submitForm(iframe, { targetOrigin });\n */\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions, FrameLike } from \"./client\";\nimport { OutboundMessageType } from \"./messages\";\nimport type { OutboundMessageMap, SubmissionBody } from \"./messages\";\n\nexport { SubmissionError, SubmissionTimeoutError } from \"./client\";\n\n/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */\ntype ElementOrFrame = FrameLike;\n\n/** Options shared by every promise helper. */\nexport interface WaitOptions extends Pick<CaptelloWebviewOptions, \"targetOrigin\" | \"hostWindow\" | \"matchSource\"> {\n /**\n * How long to wait before rejecting. Defaults to 60_000ms. Pass `0` or `Infinity`\n * to wait indefinitely (the caller is then responsible for not leaking the wait).\n */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/** Rejection reason from {@link waitForMessage} / {@link waitForFormLoad} on timeout. */\nexport class MessageTimeoutError extends Error {\n constructor(\n public readonly messageType: string,\n public readonly timeoutMs: number,\n ) {\n super(`Timed out after ${timeoutMs}ms waiting for \"${messageType}\" from the Captello webview.`);\n this.name = \"MessageTimeoutError\";\n }\n}\n\nfunction clientOptions(options: WaitOptions): CaptelloWebviewOptions {\n return {\n targetOrigin: options.targetOrigin,\n hostWindow: options.hostWindow,\n matchSource: options.matchSource,\n // One-shot helpers act on a form assumed already loaded; never buffer their\n // sends waiting for a form_load_complete that may have already fired.\n queueUntilReady: false,\n };\n}\n\n/**\n * Resolves with the next outbound message of `type` from the webview, or rejects with\n * a {@link MessageTimeoutError} if none arrives within the timeout. The internal\n * listener is always removed before settling.\n *\n * @example const msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });\n */\nexport function waitForMessage<T extends OutboundMessageType>(\n frame: ElementOrFrame,\n type: T,\n options: WaitOptions = {},\n): Promise<OutboundMessageMap[T]> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise<OutboundMessageMap[T]>((resolve, reject) => {\n const client = new CaptelloWebview(frame, clientOptions(options));\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = (fn: () => void) => {\n if (timer !== undefined) clearTimeout(timer);\n client.destroy();\n fn();\n };\n\n client.once(type, (message) => settle(() => resolve(message)));\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => settle(() => reject(new MessageTimeoutError(type, timeoutMs))), timeoutMs);\n }\n });\n}\n\n/**\n * Resolves once the webview reports `form_load_complete`, or rejects with a\n * {@link MessageTimeoutError} on timeout.\n *\n * Note: this only catches a *future* load event. If the form may have already loaded\n * before you call this (e.g. you attach late), prefer subscribing with a long-lived\n * {@link CaptelloWebview} created before the iframe navigates.\n */\nexport function waitForFormLoad(frame: ElementOrFrame, options: WaitOptions = {}): Promise<void> {\n return waitForMessage(frame, OutboundMessageType.FormLoadComplete, options).then(() => undefined);\n}\n\n/**\n * Submits the form and awaits the outcome: resolves with the {@link SubmissionBody} on\n * `submission_body`, rejects with a `SubmissionError` (translated message) on\n * `form_error_message`, or a `SubmissionTimeoutError` if neither arrives in time.\n *\n * Standalone equivalent of {@link CaptelloWebview.submitAndWait} for code that doesn't\n * hold a long-lived client — it creates one, submits, and tears it down.\n *\n * @example\n * try {\n * const body = await submitForm(iframeRef.current!, { targetOrigin });\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\nexport function submitForm(frame: ElementOrFrame, options: WaitOptions = {}): Promise<SubmissionBody> {\n const client = new CaptelloWebview(frame, clientOptions(options));\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return client.submitAndWait(timeoutMs).finally(() => client.destroy());\n}\n"]}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties, IframeHTMLAttributes, ReactNode, RefCallback } from 'react';
|
|
3
|
-
import { C as CaptelloWebviewOptions, S as SubmissionBody, d as OutboundMessage, e as SubmissionPrefill, P as PrefillInfoItem, f as CaptelloWebview, V as ValidationTarget } from './client-
|
|
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, T as TranscribeScannerRequestHandler, f as CaptelloWebview, V as ValidationTarget } from './client-CAMlFA8s.js';
|
|
4
|
+
export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-CAMlFA8s.js';
|
|
5
5
|
import { EmbedUrlOptions } from './index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -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[];
|
|
@@ -75,6 +80,16 @@ interface UseCaptelloWebviewOptions extends Omit<CaptelloWebviewOptions, "target
|
|
|
75
80
|
* defaultFormValues={{ info: [{ ll_field_unique_identifier: "Email", value: user.email }] }}
|
|
76
81
|
*/
|
|
77
82
|
defaultFormValues?: DefaultFormValues;
|
|
83
|
+
/**
|
|
84
|
+
* Answer the form's transcribe requests (its transcribe button, shown when the
|
|
85
|
+
* embed URL sets `showTranscribeButton`). Same semantics as
|
|
86
|
+
* {@link CaptelloWebview.onTranscribeScannerRequest}: return the fields (a promise
|
|
87
|
+
* is fine) and they are sent back as the result, or answer through the `reply`
|
|
88
|
+
* second argument when the work is callback-style; a thrown `Error`'s message is
|
|
89
|
+
* shown to the user. Must be set from the first render (it is wired when the iframe
|
|
90
|
+
* attaches); the latest function is always the one invoked, so inline closures are fine.
|
|
91
|
+
*/
|
|
92
|
+
onTranscribeScannerRequest?: TranscribeScannerRequestHandler;
|
|
78
93
|
}
|
|
79
94
|
/** Readiness of the embedded form. */
|
|
80
95
|
type CaptelloWebviewStatus = "loading" | "ready" | "error";
|
package/dist/react.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { buildEmbedUrl } from './chunk-
|
|
2
|
-
import { CaptelloWebview, OutboundMessageType } from './chunk-
|
|
3
|
-
export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-
|
|
1
|
+
import { buildEmbedUrl } from './chunk-ZX4AKPWF.js';
|
|
2
|
+
import { CaptelloWebview, OutboundMessageType } from './chunk-ZPVXZW2B.js';
|
|
3
|
+
export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-ZPVXZW2B.js';
|
|
4
4
|
import { forwardRef, useState, useRef, useCallback, useImperativeHandle, useEffect } from 'react';
|
|
5
5
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -77,6 +77,17 @@ function useCaptelloWebview(options) {
|
|
|
77
77
|
})
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
|
+
if (optionsRef.current.onTranscribeScannerRequest) {
|
|
81
|
+
offs.push(
|
|
82
|
+
client.onTranscribeScannerRequest((request, reply) => {
|
|
83
|
+
const handler = optionsRef.current.onTranscribeScannerRequest;
|
|
84
|
+
if (!handler) {
|
|
85
|
+
throw new Error("Transcription failed.");
|
|
86
|
+
}
|
|
87
|
+
return handler(request, reply);
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}
|
|
80
91
|
teardown.current = () => {
|
|
81
92
|
for (const off of offs) off();
|
|
82
93
|
client.destroy();
|
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, TranscribeScannerRequestHandler, 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 * Answer the form's transcribe requests (its transcribe button, shown when the\n * embed URL sets `showTranscribeButton`). Same semantics as\n * {@link CaptelloWebview.onTranscribeScannerRequest}: return the fields (a promise\n * is fine) and they are sent back as the result, or answer through the `reply`\n * second argument when the work is callback-style; a thrown `Error`'s message is\n * shown to the user. Must be set from the first render (it is wired when the iframe\n * attaches); the latest function is always the one invoked, so inline closures are fine.\n */\n onTranscribeScannerRequest?: TranscribeScannerRequestHandler;\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 case OutboundMessageType.TranscribeScannerRequest:\n // Not a fire-and-forget callback: requests are answered through the\n // request/response path registered from `onTranscribeScannerRequest` (see\n // `useCaptelloWebview`). `onAnyMessage` still observes them.\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 // Wire the transcribe request/response path. Registered only when a handler\n // is configured at attach time; the latest handler is read per request so\n // inline closures stay fresh.\n if (optionsRef.current.onTranscribeScannerRequest) {\n offs.push(\n client.onTranscribeScannerRequest((request, reply) => {\n const handler = optionsRef.current.onTranscribeScannerRequest;\n if (!handler) {\n // Handler removed mid-flight — fail the request rather than\n // leaving the form's button hanging until its timeout.\n throw new Error(\"Transcription failed.\");\n }\n // `reply` is forwarded so a callback-style handler can answer\n // through it instead of returning a promise.\n return handler(request, reply);\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,79 +1,89 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"import": "./dist/promises.js"
|
|
29
|
-
},
|
|
30
|
-
"./react": {
|
|
31
|
-
"types": "./dist/react.d.ts",
|
|
32
|
-
"import": "./dist/react.js"
|
|
33
|
-
},
|
|
34
|
-
"./package.json": "./package.json"
|
|
35
|
-
},
|
|
36
|
-
"files": [
|
|
37
|
-
"dist",
|
|
38
|
-
"LICENSE",
|
|
39
|
-
"CHANGELOG.md"
|
|
40
|
-
],
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "tsup",
|
|
43
|
-
"dev": "tsup --watch",
|
|
44
|
-
"typecheck": "tsc --noEmit",
|
|
45
|
-
"typecheck:test": "tsc -p tsconfig.test.json",
|
|
46
|
-
"test": "vitest run",
|
|
47
|
-
"test:watch": "vitest",
|
|
48
|
-
"clean": "rm -rf dist"
|
|
2
|
+
"name": "@captello/ulc-webview-sdk",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.",
|
|
5
|
+
"author": "Lead Liaison",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"typesVersions": {
|
|
12
|
+
"*": {
|
|
13
|
+
"promises": [
|
|
14
|
+
"./dist/promises.d.ts"
|
|
15
|
+
],
|
|
16
|
+
"react": [
|
|
17
|
+
"./dist/react.d.ts"
|
|
18
|
+
],
|
|
19
|
+
"app": [
|
|
20
|
+
"./dist/app-host.d.ts"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
49
28
|
},
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"iframe",
|
|
54
|
-
"postmessage",
|
|
55
|
-
"embed",
|
|
56
|
-
"sdk"
|
|
57
|
-
],
|
|
58
|
-
"peerDependencies": {
|
|
59
|
-
"react": ">=18"
|
|
29
|
+
"./promises": {
|
|
30
|
+
"types": "./dist/promises.d.ts",
|
|
31
|
+
"import": "./dist/promises.js"
|
|
60
32
|
},
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
33
|
+
"./react": {
|
|
34
|
+
"types": "./dist/react.d.ts",
|
|
35
|
+
"import": "./dist/react.js"
|
|
65
36
|
},
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"jsdom": "25.0.1",
|
|
70
|
-
"react": "19.2.2",
|
|
71
|
-
"react-dom": "19.2.2",
|
|
72
|
-
"tsup": "8.3.5",
|
|
73
|
-
"typescript": "5.9.3",
|
|
74
|
-
"vitest": "2.1.9"
|
|
37
|
+
"./app": {
|
|
38
|
+
"types": "./dist/app-host.d.ts",
|
|
39
|
+
"import": "./dist/app-host.js"
|
|
75
40
|
},
|
|
76
|
-
"
|
|
77
|
-
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"CHANGELOG.md"
|
|
47
|
+
],
|
|
48
|
+
"keywords": [
|
|
49
|
+
"captello",
|
|
50
|
+
"webview",
|
|
51
|
+
"iframe",
|
|
52
|
+
"postmessage",
|
|
53
|
+
"embed",
|
|
54
|
+
"sdk"
|
|
55
|
+
],
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"react": ">=18"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"react": {
|
|
61
|
+
"optional": true
|
|
78
62
|
}
|
|
79
|
-
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@playwright/test": "1.62.1",
|
|
66
|
+
"@testing-library/react": "16.1.0",
|
|
67
|
+
"@types/react": "19.2.2",
|
|
68
|
+
"jsdom": "25.0.1",
|
|
69
|
+
"react": "19.2.2",
|
|
70
|
+
"react-dom": "19.2.2",
|
|
71
|
+
"tsup": "8.3.5",
|
|
72
|
+
"typescript": "5.9.3",
|
|
73
|
+
"vitest": "2.1.9"
|
|
74
|
+
},
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"access": "public"
|
|
77
|
+
},
|
|
78
|
+
"scripts": {
|
|
79
|
+
"build": "tsup",
|
|
80
|
+
"dev": "tsup --watch",
|
|
81
|
+
"typecheck": "tsc --noEmit",
|
|
82
|
+
"typecheck:test": "tsc -p tsconfig.test.json",
|
|
83
|
+
"test": "vitest run",
|
|
84
|
+
"test:watch": "vitest",
|
|
85
|
+
"clean": "rm -rf dist dist-iife",
|
|
86
|
+
"e2e": "pnpm build && playwright test",
|
|
87
|
+
"e2e:ui": "pnpm build && playwright test --ui"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/embed-url.ts"],"names":["EmbedParam","FormMode","LauncherType","ActionButtonPosition","Language"],"mappings":"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","file":"chunk-4E7OW4RJ.js","sourcesContent":["/**\n * Builder for the Captello capture webview embed URL.\n *\n * The webview reads its configuration from query-string params. The short keys\n * below are the contract the webview expects (see the webview's `PARAMS` enum);\n * this builder maps friendly option names onto those keys so hosts never have to\n * hard-code `\"f\"`, `\"m\"`, etc.\n */\n\n/** Query-param keys understood by the webview. */\nexport enum EmbedParam {\n FormId = \"f\",\n SubmissionToken = \"s\",\n StationId = \"st\",\n Mode = \"m\",\n EventWebAccessToken = \"e\",\n ActivationId = \"a\",\n Language = \"l\",\n ActionButtonPosition = \"b\",\n FormType = \"form_type\",\n Launcher = \"launcher\",\n SubmissionType = \"t\",\n SubmitButtonBottomPadding = \"sbbp\",\n Platform = \"platform\",\n HideEmail = \"he\",\n ConnexionsEmbedMode = \"cem\",\n /** Edit mode read-only: locks email-mapped and invitation-code elements. */\n Emro = \"emro\",\n /** Context for filtering form-fill actions (e.g. MMP outbound/inbound/notes). */\n UseIn = \"useIn\",\n}\n\n/** Form render mode (the webview's `FormMode`). */\nexport enum FormMode {\n Preview = \"preview\",\n Submit = \"submit\",\n Edit = \"edit\",\n View = \"view\",\n}\n\n/** Identifies the host embedding the webview (the webview's `LAUNCHER_TYPES`). */\nexport enum LauncherType {\n EventGenMobile = \"event_gen_mobile\",\n EventGenWeb = \"event_gen_web\",\n WebApp = \"webapp\",\n Mmp = \"MMP\",\n}\n\n/** `form_type` discriminator. */\nexport type FormType = \"template\" | \"device\";\n\n/** `t` (submission type) discriminator. */\nexport type SubmissionType = \"normal\" | \"drafted\";\n\n/**\n * Action-button position param (`b`), mirroring the webview's `CTABtnPosition`.\n * The wire values are numeric strings; use {@link ActionButtonPosition} for the\n * readable names.\n */\nexport enum ActionButtonPosition {\n Fixed = \"0\",\n Bottom = \"1\",\n Hidden = \"2\",\n}\n\n/** Supported webview languages. The wire value is the two-letter code. */\nexport enum Language {\n Arabic = \"ar\",\n German = \"de\",\n English = \"en\",\n Spanish = \"es\",\n French = \"fr\",\n Italian = \"it\",\n Japanese = \"ja\",\n Korean = \"ko\",\n Dutch = \"nl\",\n Portuguese = \"pt\",\n Chinese = \"zh\",\n}\n\n/** Context for filtering form-fill actions, sent as the `useIn` param. */\nexport type UseInContext = \"outbound\" | \"inbound\" | \"notes\";\n\n/**\n * Options for {@link buildEmbedUrl}. Every field is optional; only the ones you set\n * are written to the URL. `formId` is effectively required for a real embed but is\n * left optional so callers can build preview/partial URLs.\n */\nexport interface EmbedUrlOptions {\n formId?: string | number;\n submissionToken?: string;\n stationId?: string | number;\n mode?: FormMode;\n eventWebAccessToken?: string;\n activationId?: string | number;\n language?: Language;\n actionButtonPosition?: ActionButtonPosition;\n formType?: FormType;\n launcher?: LauncherType;\n submissionType?: SubmissionType;\n submitButtonBottomPadding?: string | number;\n /** Context for filtering form-fill actions (the `useIn` param). */\n useIn?: UseInContext;\n platform?: \"web\" | \"mobile\";\n hideEmail?: boolean;\n /** Connexions embed mode: suppress in-webview redirect/vCard download. */\n connexionsEmbedMode?: boolean;\n /** Edit mode read-only. */\n emro?: boolean;\n /**\n * Extra query params to append verbatim (e.g. prospect tracking params the\n * webview forwards on submit). Values are stringified; `undefined`/`null` skipped.\n */\n extraParams?: Record<string, string | number | boolean | undefined | null>;\n}\n\n// Maps each option onto its query key. Order here defines the order params are\n// written, which keeps generated URLs stable and diffable.\nconst OPTION_TO_PARAM: ReadonlyArray<[keyof EmbedUrlOptions, EmbedParam]> = [\n [\"formId\", EmbedParam.FormId],\n [\"submissionToken\", EmbedParam.SubmissionToken],\n [\"stationId\", EmbedParam.StationId],\n [\"mode\", EmbedParam.Mode],\n [\"eventWebAccessToken\", EmbedParam.EventWebAccessToken],\n [\"activationId\", EmbedParam.ActivationId],\n [\"language\", EmbedParam.Language],\n [\"actionButtonPosition\", EmbedParam.ActionButtonPosition],\n [\"formType\", EmbedParam.FormType],\n [\"launcher\", EmbedParam.Launcher],\n [\"submissionType\", EmbedParam.SubmissionType],\n [\"submitButtonBottomPadding\", EmbedParam.SubmitButtonBottomPadding],\n [\"useIn\", EmbedParam.UseIn],\n [\"platform\", EmbedParam.Platform],\n];\n\n// Boolean flags are encoded as \"1\" when true and omitted when false/unset, matching\n// how the webview reads them (`Boolean(queryParams[key])` / presence checks).\nconst BOOLEAN_OPTION_TO_PARAM: ReadonlyArray<[keyof EmbedUrlOptions, EmbedParam]> = [\n [\"hideEmail\", EmbedParam.HideEmail],\n [\"connexionsEmbedMode\", EmbedParam.ConnexionsEmbedMode],\n [\"emro\", EmbedParam.Emro],\n];\n\n/** The path the capture webview is served at. The SDK owns this so callers don't. */\nexport const CAPTURE_SUBMISSION_PATH = \"/capture/submission\";\n\n// Recognized capture routes — if the base URL already targets one of these, it is kept\n// as-is; otherwise the canonical submission path is appended. `capture/activation` is\n// preserved so activation embeds aren't rewritten to a submission URL.\nconst CAPTURE_PATH_PATTERN = /\\/capture\\/(submission|activation)\\/?$/;\n\n/**\n * Normalizes a base capture URL so the path is always a valid capture route, no matter\n * what the caller passed. This is the fix for the \"do I include `/capture/submission`?\"\n * footgun: the origin, the origin with a trailing slash, and the full path all converge\n * to the same correct URL.\n *\n * - Origin only (`https://capture.captello.com`) → path set to `/capture/submission`.\n * - Already a capture route (`…/capture/submission`, `…/capture/activation`, with or\n * without a trailing slash) → kept (trailing slash trimmed).\n * - A base path (`https://host/webview`) → `/capture/submission` appended to it, so\n * sub-path deployments still work.\n *\n * Existing query params and the origin are always preserved.\n */\nfunction normalizeCaptureUrl(baseUrl: string): URL {\n const url = new URL(baseUrl);\n\n if (CAPTURE_PATH_PATTERN.test(url.pathname)) {\n url.pathname = url.pathname.replace(/\\/+$/, \"\"); // drop any trailing slash\n return url;\n }\n\n const base = url.pathname.replace(/\\/+$/, \"\"); // \"\" for origin/\"/\", \"/webview\" for a sub-path\n url.pathname = `${base}${CAPTURE_SUBMISSION_PATH}`;\n return url;\n}\n\n/**\n * Builds an absolute embed URL from a capture base URL and typed options.\n *\n * You only need to pass the **capture origin** — the SDK appends the capture path for\n * you. Passing the origin, the origin with a trailing slash, or the full\n * `…/capture/submission` URL all produce the same correct result, so there's nothing to\n * get wrong. Existing query params on `baseUrl` are preserved; options override params\n * with the same key.\n *\n * @example\n * // All three are equivalent:\n * buildEmbedUrl(\"https://capture.captello.com\", { formId: 1234, mode: FormMode.Submit });\n * buildEmbedUrl(\"https://capture.captello.com/\", { formId: 1234, mode: FormMode.Submit });\n * buildEmbedUrl(\"https://capture.captello.com/capture/submission\", { formId: 1234, mode: FormMode.Submit });\n * // → \"https://capture.captello.com/capture/submission?f=1234&m=submit\"\n */\nexport function buildEmbedUrl(baseUrl: string, options: EmbedUrlOptions = {}): string {\n const url = normalizeCaptureUrl(baseUrl);\n\n for (const [optionKey, paramKey] of OPTION_TO_PARAM) {\n const value = options[optionKey];\n if (value !== undefined && value !== null && value !== \"\") {\n url.searchParams.set(paramKey, String(value));\n }\n }\n\n for (const [optionKey, paramKey] of BOOLEAN_OPTION_TO_PARAM) {\n if (options[optionKey]) {\n url.searchParams.set(paramKey, \"1\");\n }\n }\n\n if (options.extraParams) {\n for (const [key, value] of Object.entries(options.extraParams)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n return url.toString();\n}\n"]}
|