@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
|
@@ -6,6 +6,7 @@ var OutboundMessageType = /* @__PURE__ */ ((OutboundMessageType2) => {
|
|
|
6
6
|
OutboundMessageType2["FormSubmitSuccess"] = "form_submit_success";
|
|
7
7
|
OutboundMessageType2["ConnexionsProfileRedirect"] = "connexions_profile_redirect";
|
|
8
8
|
OutboundMessageType2["ConnexionsDownloadVcard"] = "connexions_download_vcard";
|
|
9
|
+
OutboundMessageType2["TranscribeScannerRequest"] = "transcribe_scanner_request";
|
|
9
10
|
return OutboundMessageType2;
|
|
10
11
|
})(OutboundMessageType || {});
|
|
11
12
|
var InboundMessageType = /* @__PURE__ */ ((InboundMessageType2) => {
|
|
@@ -14,6 +15,7 @@ var InboundMessageType = /* @__PURE__ */ ((InboundMessageType2) => {
|
|
|
14
15
|
InboundMessageType2["FormPrefill"] = "form_prefill";
|
|
15
16
|
InboundMessageType2["UpdateDraft"] = "update_draft";
|
|
16
17
|
InboundMessageType2["TriggerValidation"] = "trigger_validation";
|
|
18
|
+
InboundMessageType2["TranscribeScannerResult"] = "transcribe_scanner_result";
|
|
17
19
|
return InboundMessageType2;
|
|
18
20
|
})(InboundMessageType || {});
|
|
19
21
|
var OUTBOUND_TYPES = new Set(Object.values(OutboundMessageType));
|
|
@@ -48,6 +50,14 @@ var SubmissionTimeoutError = class extends Error {
|
|
|
48
50
|
this.name = "SubmissionTimeoutError";
|
|
49
51
|
}
|
|
50
52
|
};
|
|
53
|
+
function makeSubscription(off) {
|
|
54
|
+
const handle = () => {
|
|
55
|
+
off();
|
|
56
|
+
};
|
|
57
|
+
handle.unsubscribe = off;
|
|
58
|
+
return handle;
|
|
59
|
+
}
|
|
60
|
+
var ADOPTED_WINDOW_POLL_MS = 5e3;
|
|
51
61
|
function devWarn(message) {
|
|
52
62
|
try {
|
|
53
63
|
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production") {
|
|
@@ -56,8 +66,21 @@ function devWarn(message) {
|
|
|
56
66
|
} catch {
|
|
57
67
|
}
|
|
58
68
|
}
|
|
69
|
+
function attach(options = {}) {
|
|
70
|
+
return new CaptelloWebview({ contentWindow: null }, { ...options, adoptSource: true });
|
|
71
|
+
}
|
|
72
|
+
function normalizeTargetOrigin(value) {
|
|
73
|
+
if (!value || value === "*") return "*";
|
|
74
|
+
try {
|
|
75
|
+
return new URL(value).origin;
|
|
76
|
+
} catch {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
59
80
|
var CaptelloWebview = class {
|
|
60
81
|
constructor(frame, options = {}) {
|
|
82
|
+
/** The webview window learned from an inbound message — see `adoptSource`. */
|
|
83
|
+
this.adoptedWindow = null;
|
|
61
84
|
this.listeners = /* @__PURE__ */ new Map();
|
|
62
85
|
this.anyListeners = /* @__PURE__ */ new Set();
|
|
63
86
|
this.destroyed = false;
|
|
@@ -66,10 +89,10 @@ var CaptelloWebview = class {
|
|
|
66
89
|
/** Messages sent before ready, flushed in order on load. */
|
|
67
90
|
this.outbox = [];
|
|
68
91
|
if (!frame) {
|
|
69
|
-
throw new Error("CaptelloWebview:
|
|
92
|
+
throw new Error("CaptelloWebview: a mounted iframe element (or { contentWindow }) is required.");
|
|
70
93
|
}
|
|
71
94
|
this.frame = frame;
|
|
72
|
-
this.targetOrigin = options.targetOrigin
|
|
95
|
+
this.targetOrigin = normalizeTargetOrigin(options.targetOrigin);
|
|
73
96
|
this.matchSource = options.matchSource ?? true;
|
|
74
97
|
this.queueUntilReady = options.queueUntilReady ?? true;
|
|
75
98
|
if (this.targetOrigin === "*") {
|
|
@@ -86,6 +109,55 @@ var CaptelloWebview = class {
|
|
|
86
109
|
this.hostWindow = hostWindow;
|
|
87
110
|
this.boundHandler = (event) => this.handleMessage(event);
|
|
88
111
|
this.hostWindow.addEventListener("message", this.boundHandler);
|
|
112
|
+
this.adoptSource = options.adoptSource ?? false;
|
|
113
|
+
this.autoDestroy = options.autoDestroy ?? true;
|
|
114
|
+
if (this.autoDestroy) {
|
|
115
|
+
this.watchForFrameRemoval();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Tears the client down once the bound iframe leaves the document.
|
|
120
|
+
*
|
|
121
|
+
* Observes the whole document subtree because the iframe usually goes away with an
|
|
122
|
+
* ancestor (a panel or modal being emptied), which produces no mutation on the
|
|
123
|
+
* iframe's own parent. `isConnected` is the actual test, so removal at any depth
|
|
124
|
+
* counts; the observer only bothers checking when a mutation actually removed
|
|
125
|
+
* something.
|
|
126
|
+
*/
|
|
127
|
+
/**
|
|
128
|
+
* Tears the client down once an adopted webview window goes away.
|
|
129
|
+
*
|
|
130
|
+
* With no iframe element there is nothing to observe in the DOM, but a removed
|
|
131
|
+
* iframe's `contentWindow` reports `closed === true` — and `closed` is readable
|
|
132
|
+
* cross-origin — so a cheap periodic check is enough. Verified in Chromium against a
|
|
133
|
+
* genuinely cross-origin iframe.
|
|
134
|
+
*/
|
|
135
|
+
watchAdoptedWindow() {
|
|
136
|
+
if (this.adoptedPoll !== void 0) return;
|
|
137
|
+
this.adoptedPoll = setInterval(() => {
|
|
138
|
+
let gone = false;
|
|
139
|
+
try {
|
|
140
|
+
gone = !this.adoptedWindow || this.adoptedWindow.closed;
|
|
141
|
+
} catch {
|
|
142
|
+
gone = true;
|
|
143
|
+
}
|
|
144
|
+
if (gone) this.destroy();
|
|
145
|
+
}, ADOPTED_WINDOW_POLL_MS);
|
|
146
|
+
this.adoptedPoll?.unref?.();
|
|
147
|
+
}
|
|
148
|
+
watchForFrameRemoval() {
|
|
149
|
+
const node = this.frame;
|
|
150
|
+
if (typeof MutationObserver === "undefined" || typeof Node === "undefined" || !(this.frame instanceof Node)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const doc = node.ownerDocument;
|
|
154
|
+
if (!doc?.documentElement) return;
|
|
155
|
+
if (!node.isConnected) return;
|
|
156
|
+
this.frameObserver = new MutationObserver((records) => {
|
|
157
|
+
if (!records.some((record) => record.removedNodes.length > 0)) return;
|
|
158
|
+
if (!node.isConnected) this.destroy();
|
|
159
|
+
});
|
|
160
|
+
this.frameObserver.observe(doc.documentElement, { childList: true, subtree: true });
|
|
89
161
|
}
|
|
90
162
|
/** `true` once the webview has reported `form_load_complete`. */
|
|
91
163
|
get isReady() {
|
|
@@ -106,9 +178,9 @@ var CaptelloWebview = class {
|
|
|
106
178
|
this.listeners.set(type, set);
|
|
107
179
|
}
|
|
108
180
|
set.add(listener);
|
|
109
|
-
return () => {
|
|
181
|
+
return makeSubscription(() => {
|
|
110
182
|
set?.delete(listener);
|
|
111
|
-
};
|
|
183
|
+
});
|
|
112
184
|
}
|
|
113
185
|
/**
|
|
114
186
|
* Subscribe once: the listener is removed automatically after it fires the first
|
|
@@ -124,9 +196,9 @@ var CaptelloWebview = class {
|
|
|
124
196
|
/** Subscribe to every outbound message regardless of type. Returns an unsubscribe function. */
|
|
125
197
|
onAny(listener) {
|
|
126
198
|
this.anyListeners.add(listener);
|
|
127
|
-
return () => {
|
|
199
|
+
return makeSubscription(() => {
|
|
128
200
|
this.anyListeners.delete(listener);
|
|
129
|
-
};
|
|
201
|
+
});
|
|
130
202
|
}
|
|
131
203
|
/* -------------------------------------------------------------- *
|
|
132
204
|
* Sending (host → webview)
|
|
@@ -154,10 +226,10 @@ var CaptelloWebview = class {
|
|
|
154
226
|
}
|
|
155
227
|
/** Posts a message immediately, bypassing the ready-queue. */
|
|
156
228
|
postNow(message) {
|
|
157
|
-
const target = this.frame.contentWindow;
|
|
229
|
+
const target = this.frame.contentWindow ?? this.adoptedWindow;
|
|
158
230
|
if (!target) {
|
|
159
231
|
throw new Error(
|
|
160
|
-
"CaptelloWebview: iframe.contentWindow is null.
|
|
232
|
+
this.adoptSource ? "CaptelloWebview: no webview window yet \u2014 nothing has been adopted, because no message has arrived from the form. Send only in response to a message, or after form_load_complete." : "CaptelloWebview: iframe.contentWindow is null (not yet loaded, or detached). Construct the client with a mounted iframe."
|
|
161
233
|
);
|
|
162
234
|
}
|
|
163
235
|
target.postMessage(JSON.stringify(message), this.targetOrigin);
|
|
@@ -255,6 +327,142 @@ var CaptelloWebview = class {
|
|
|
255
327
|
data
|
|
256
328
|
});
|
|
257
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Answer the webview's transcribe requests (its transcribe button — shown when the
|
|
332
|
+
* embed URL sets `showTranscribeButton` — sends `transcribe_scanner_request` when
|
|
333
|
+
* pressed).
|
|
334
|
+
*
|
|
335
|
+
* The handler receives the request (`element_id`, `image_urls`,
|
|
336
|
+
* `draft_submission_token`) and a {@link TranscribeScannerReply}, and answers in
|
|
337
|
+
* whichever style suits the transcription code. Either way the result is posted for
|
|
338
|
+
* you — the host never builds or stringifies a `transcribe_scanner_result`.
|
|
339
|
+
*
|
|
340
|
+
* **Return the fields** when the work is promise-based:
|
|
341
|
+
*
|
|
342
|
+
* ```js
|
|
343
|
+
* webview.onTranscribeScannerRequest(async ({ image_urls }) => {
|
|
344
|
+
* const fields = await transcribe(image_urls);
|
|
345
|
+
* return { fields, submissionType: "ocr_transcription" };
|
|
346
|
+
* });
|
|
347
|
+
* ```
|
|
348
|
+
*
|
|
349
|
+
* **Or answer through `reply`** when it isn't — a callback-style AJAX wrapper, an
|
|
350
|
+
* event bus, a method that returns `void`. Hand `reply` to whatever does the work:
|
|
351
|
+
*
|
|
352
|
+
* ```js
|
|
353
|
+
* webview.onTranscribeScannerRequest((request, reply) => {
|
|
354
|
+
* transcribeService(request.image_urls,
|
|
355
|
+
* (fields) => reply.resolve({ fields, submissionType: "ocr_transcription" }),
|
|
356
|
+
* () => reply.reject("Transcription failed."),
|
|
357
|
+
* );
|
|
358
|
+
* });
|
|
359
|
+
* ```
|
|
360
|
+
*
|
|
361
|
+
* A handler that returns nothing is taken to own the reply, so the request stays
|
|
362
|
+
* open until `reply` answers it. A thrown error / rejected promise is sent as an
|
|
363
|
+
* error result; an `Error`'s `message` is shown to the user by the webview, so throw
|
|
364
|
+
* display-ready messages. The first answer wins — a second is ignored.
|
|
365
|
+
*
|
|
366
|
+
* Returns an unsubscribe function.
|
|
367
|
+
*/
|
|
368
|
+
onTranscribeScannerRequest(handler) {
|
|
369
|
+
return this.on("transcribe_scanner_request" /* TranscribeScannerRequest */, (request) => {
|
|
370
|
+
let answered = false;
|
|
371
|
+
const answer = (result) => {
|
|
372
|
+
if (answered) {
|
|
373
|
+
devWarn(
|
|
374
|
+
`[captello-sdk] Ignored a second answer to transcribe request "${request.request_id}" \u2014 it was already answered. Answer either by returning from the handler or by calling reply.resolve()/reply.reject(), not both.`
|
|
375
|
+
);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
answered = true;
|
|
379
|
+
this.sendTranscribeScannerResult(request, result);
|
|
380
|
+
};
|
|
381
|
+
const reply = {
|
|
382
|
+
resolve: (data) => answer({ data }),
|
|
383
|
+
reject: (error) => answer({ error: error || "Transcription failed." }),
|
|
384
|
+
get answered() {
|
|
385
|
+
return answered;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
Promise.resolve().then(() => handler(request, reply)).then(
|
|
389
|
+
(data) => {
|
|
390
|
+
if (data === void 0) {
|
|
391
|
+
if (!answered && handler.length < 2) {
|
|
392
|
+
devWarn(
|
|
393
|
+
"[captello-sdk] A transcribe handler returned nothing and takes no `reply` parameter, so this request can never be answered and the form's button will spin until it times out. Either return the fields, or accept `(request, reply)` and call reply.resolve()/reply.reject()."
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
answer({ data });
|
|
399
|
+
},
|
|
400
|
+
(err) => answer({
|
|
401
|
+
error: err instanceof Error && err.message ? err.message : "Transcription failed."
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Answer a transcribe request **later**, from code that can't hand back a promise
|
|
408
|
+
* — a callback-style AJAX wrapper, an event bus, a method that returns `void`.
|
|
409
|
+
*
|
|
410
|
+
* Pair it with a plain `on(OutboundMessageType.TranscribeScannerRequest, ...)`
|
|
411
|
+
* subscription: hold on to the request (or just its `request_id`), and call this
|
|
412
|
+
* once the transcription lands. Prefer {@link onTranscribeScannerRequest} when
|
|
413
|
+
* your transcription code is already promise-based — it does this for you.
|
|
414
|
+
*
|
|
415
|
+
* Accepts the request message itself or its bare `request_id`. The reply is
|
|
416
|
+
* `JSON.stringify`'d and posted immediately, bypassing the ready-queue: a received
|
|
417
|
+
* request already proves the webview is live and listening. Replying after
|
|
418
|
+
* {@link destroy}, or after the iframe is detached, is a silent no-op — by then
|
|
419
|
+
* there is nobody left to answer, and the webview times its own button out.
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* webview.on(OutboundMessageType.TranscribeScannerRequest, (request) => {
|
|
423
|
+
* ajax.send("Scanner", "transcribe", { image_urls: request.image_urls },
|
|
424
|
+
* (res) => webview.resolveTranscribeScannerRequest(request, {
|
|
425
|
+
* fields: res.fields,
|
|
426
|
+
* submissionType: "ocr_transcription",
|
|
427
|
+
* }),
|
|
428
|
+
* () => webview.rejectTranscribeScannerRequest(request, "Transcription failed."),
|
|
429
|
+
* );
|
|
430
|
+
* });
|
|
431
|
+
*/
|
|
432
|
+
resolveTranscribeScannerRequest(request, data) {
|
|
433
|
+
this.sendTranscribeScannerResult(request, { data });
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Fail a transcribe request answered via
|
|
437
|
+
* {@link resolveTranscribeScannerRequest}'s deferred flow. `error` is shown to the
|
|
438
|
+
* user verbatim by the webview, so pass display-ready text.
|
|
439
|
+
*
|
|
440
|
+
* Same delivery semantics as {@link resolveTranscribeScannerRequest}: posted
|
|
441
|
+
* immediately, and a no-op once the client is destroyed or the iframe detached.
|
|
442
|
+
*/
|
|
443
|
+
rejectTranscribeScannerRequest(request, error) {
|
|
444
|
+
this.sendTranscribeScannerResult(request, { error: error || "Transcription failed." });
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Posts a `transcribe_scanner_result`, bypassing the ready-queue. A received
|
|
448
|
+
* request proves the webview is live and listening, even if this client attached
|
|
449
|
+
* after form load and never saw `form_load_complete`; queuing here could stall the
|
|
450
|
+
* reply forever while the webview's button sits in its pending state until timeout.
|
|
451
|
+
*/
|
|
452
|
+
sendTranscribeScannerResult(request, result) {
|
|
453
|
+
if (this.destroyed) return;
|
|
454
|
+
const requestId = typeof request === "string" ? request : request?.request_id;
|
|
455
|
+
try {
|
|
456
|
+
this.postNow({
|
|
457
|
+
type: "transcribe_scanner_result" /* TranscribeScannerResult */,
|
|
458
|
+
// Dropped from the JSON when undefined; the webview accepts a result
|
|
459
|
+
// without one while exactly one request is pending.
|
|
460
|
+
request_id: requestId,
|
|
461
|
+
...result
|
|
462
|
+
});
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
}
|
|
258
466
|
/* -------------------------------------------------------------- *
|
|
259
467
|
* Lifecycle
|
|
260
468
|
* -------------------------------------------------------------- */
|
|
@@ -266,6 +474,12 @@ var CaptelloWebview = class {
|
|
|
266
474
|
this.listeners.clear();
|
|
267
475
|
this.anyListeners.clear();
|
|
268
476
|
this.outbox.length = 0;
|
|
477
|
+
this.frameObserver?.disconnect();
|
|
478
|
+
this.adoptedWindow = null;
|
|
479
|
+
if (this.adoptedPoll !== void 0) {
|
|
480
|
+
clearInterval(this.adoptedPoll);
|
|
481
|
+
this.adoptedPoll = void 0;
|
|
482
|
+
}
|
|
269
483
|
}
|
|
270
484
|
/* -------------------------------------------------------------- *
|
|
271
485
|
* Internals
|
|
@@ -280,19 +494,21 @@ var CaptelloWebview = class {
|
|
|
280
494
|
}
|
|
281
495
|
return;
|
|
282
496
|
}
|
|
497
|
+
const message = parseOutboundMessage(event.data);
|
|
498
|
+
if (!message) return;
|
|
283
499
|
if (this.matchSource) {
|
|
284
|
-
const expected = this.frame.contentWindow;
|
|
500
|
+
const expected = this.frame.contentWindow ?? this.adoptedWindow;
|
|
285
501
|
if (expected && event.source !== expected) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
290
|
-
}
|
|
502
|
+
devWarn(
|
|
503
|
+
"[captello-sdk] Ignored a Captello message from an unexpected source window (not the bound iframe). If the webview relays through another window, set matchSource: false."
|
|
504
|
+
);
|
|
291
505
|
return;
|
|
292
506
|
}
|
|
293
507
|
}
|
|
294
|
-
|
|
295
|
-
|
|
508
|
+
if (this.adoptSource && !this.adoptedWindow && !this.frame.contentWindow) {
|
|
509
|
+
this.adoptedWindow = event.source ?? null;
|
|
510
|
+
if (this.adoptedWindow && this.autoDestroy) this.watchAdoptedWindow();
|
|
511
|
+
}
|
|
296
512
|
if (message.type === "form_load_complete" /* FormLoadComplete */) {
|
|
297
513
|
this.markReadyAndFlush();
|
|
298
514
|
}
|
|
@@ -306,6 +522,6 @@ var CaptelloWebview = class {
|
|
|
306
522
|
}
|
|
307
523
|
};
|
|
308
524
|
|
|
309
|
-
export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, parseOutboundMessage };
|
|
310
|
-
//# sourceMappingURL=chunk-
|
|
311
|
-
//# sourceMappingURL=chunk-
|
|
525
|
+
export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, attach, parseOutboundMessage };
|
|
526
|
+
//# sourceMappingURL=chunk-ZPVXZW2B.js.map
|
|
527
|
+
//# sourceMappingURL=chunk-ZPVXZW2B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/client.ts"],"names":["OutboundMessageType","InboundMessageType"],"mappings":"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","file":"chunk-ZPVXZW2B.js","sourcesContent":["/**\n * The message protocol exchanged between the Captello capture webview (the iframe)\n * and its host page.\n *\n * Wire format (this is the contract — match it exactly):\n * - Every message is a JSON **string**. The webview sends outbound messages with\n * `JSON.stringify(message)` and reads inbound messages with `JSON.parse(event.data)`.\n * A host that posts a raw object instead of a string will be ignored, because the\n * webview's parser produces a non-object and bails.\n * - Every message is an object with a `type` discriminator. Inbound and outbound\n * types are disjoint string enums.\n *\n * Direction is named from the **webview's** point of view:\n * - {@link OutboundMessageType}: webview → host (the host listens for these).\n * - {@link InboundMessageType}: host → webview (the host sends these).\n */\n\nimport type { VisibleSubmissionDataItem } from \"./submission-data\";\n\n/* ------------------------------------------------------------------ *\n * Outbound: webview → host\n * ------------------------------------------------------------------ */\n\n/** Message `type` values the webview emits to its host. */\nexport enum OutboundMessageType {\n /** The form finished loading and rendering. Safe to interact with it after this. */\n FormLoadComplete = \"form_load_complete\",\n /** A user-facing error occurred; `data` is the translated, display-ready message. */\n FormErrorMessage = \"form_error_message\",\n /**\n * Emitted for embedded forms instead of submitting directly: `data` is the full\n * submission body for the host to persist/forward.\n */\n SubmissionBody = \"submission_body\",\n /** The form was submitted successfully. `action` indicates whether it was a new submission or an update. */\n FormSubmitSuccess = \"form_submit_success\",\n /** Connexions: the host should perform the profile redirect (embed mode). */\n ConnexionsProfileRedirect = \"connexions_profile_redirect\",\n /** Connexions: the host should trigger the vCard download (embed mode). */\n ConnexionsDownloadVcard = \"connexions_download_vcard\",\n /**\n * The user pressed the transcribe button (shown only when the embed URL enables\n * it via `show_transcribe_button`): the host should transcribe the request's\n * scan images and answer with an inbound\n * {@link InboundMessageType.TranscribeScannerResult}.\n */\n TranscribeScannerRequest = \"transcribe_scanner_request\",\n}\n\n/**\n * Opaque submission payload carried by {@link OutboundMessageType.SubmissionBody}.\n *\n * This mirrors the webview's internal `FormSubmission` model. It is intentionally\n * typed as an open record here so the SDK stays decoupled from the app's full model\n * graph; the documented fields below are stable, the rest are passed through as-is.\n * Host code that needs the deep element-value types should treat `data` as untyped\n * and key it by element id (e.g. `\"element_12\"`, `\"element_12_3\"`).\n */\nexport interface SubmissionBody {\n id: number;\n form_id: number;\n prospect_id: number;\n email: string;\n first_name: string;\n last_name: string;\n full_name: string;\n company: string;\n phone: string;\n /** Submitted values keyed by element id / sub-element id. */\n data: Record<string, unknown>;\n /**\n * Visible, filled elements ready to render as key/value rows — one item per\n * element, discriminated by `element_type` (narrow on it for a precisely-typed\n * `element_value`). See {@link VisibleSubmissionDataItem}. May be absent on older\n * webview builds.\n */\n visible_submissions_data?: VisibleSubmissionDataItem[];\n submission_date: string;\n /** Query-string params the webview was loaded with, echoed back on submit. */\n query_parameters?: Record<string, string>;\n /** Additional fields from the webview's submission model are passed through verbatim. */\n [key: string]: unknown;\n}\n\n/**\n * Submitted values flat-keyed by element / sub-element id (e.g. `\"element_12\"`, `\"element_12_3\"`).\n *\n * This is the webview's own internal shape rather than the submissions API's: it is what a\n * received {@link SubmissionBody} carries, and what the backend stores verbatim for a draft and\n * hands back unchanged — hence the name. Either can be passed to\n * {@link SubmissionPrefill.data} as-is, so an `onSubmissionBody` payload round-trips.\n *\n * Contrast {@link SubmissionPrefillDataItem}, the array shape the submissions API returns.\n */\nexport type DraftSubmissionData = Record<string, unknown>;\n\n/**\n * One submitted value in {@link SubmissionPrefill.data}. Mirrors the submissions API's\n * `SubmissionDataResponse` shape, so a `submission.data` array fetched from that API can\n * be passed straight through as-is.\n */\nexport interface SubmissionPrefillDataItem {\n element_id: string;\n element_title: string;\n value: string;\n /** Present only when the element has sub-elements (e.g. a simple name or address). */\n value_splitted?: Record<string, string>;\n}\n\n/**\n * Loose submission shape accepted when **pre-filling** the form (host → webview).\n *\n * Distinct from {@link SubmissionBody} in that every field is optional — assemble a partial\n * object from your own data rather than populating a whole body.\n *\n * `data` accepts either shape a host is likely to be holding, and the webview normalizes\n * whichever it receives:\n * - {@link SubmissionPrefillDataItem}`[]` — one entry per element, as the submissions API\n * returns it for a submitted submission. Pass a fetched `submission.data` array straight\n * through.\n * - {@link DraftSubmissionData} — values flat-keyed by element / sub-element id. This is the\n * shape a received {@link SubmissionBody} carries, and the shape a draft is stored and\n * returned in, so `onSubmissionBody` payloads round-trip directly.\n */\nexport interface SubmissionPrefill {\n /**\n * Submitted values, as either the submissions API's array (see\n * {@link SubmissionPrefillDataItem}) or a flat {@link DraftSubmissionData} record.\n */\n data?: SubmissionPrefillDataItem[] | DraftSubmissionData;\n [key: string]: unknown;\n}\n\ninterface FormLoadCompleteMessage {\n type: OutboundMessageType.FormLoadComplete;\n}\ninterface FormSubmitSuccessMessage {\n type: OutboundMessageType.FormSubmitSuccess;\n action: \"create\" | \"update\";\n}\ninterface FormErrorMessageMessage {\n type: OutboundMessageType.FormErrorMessage;\n /** Translated, display-ready error text. */\n data: string;\n}\ninterface SubmissionBodyMessage {\n type: OutboundMessageType.SubmissionBody;\n data: SubmissionBody;\n}\ninterface ConnexionsProfileRedirectMessage {\n type: OutboundMessageType.ConnexionsProfileRedirect;\n}\ninterface ConnexionsDownloadVcardMessage {\n type: OutboundMessageType.ConnexionsDownloadVcard;\n}\n\n/**\n * Webview → host: the user pressed the transcribe button. Answer with a\n * {@link TranscribeScannerResultMessage} (see\n * {@link CaptelloWebview.onTranscribeScannerRequest} for the handler that does this\n * for you). The webview shows a pending state on the button and gives up with a\n * user-facing error if no result arrives in time.\n *\n * Host processing is a convention, not a one-off: future operations get their own\n * `*_request` / `*_result` message pairs shaped like this one.\n */\nexport interface TranscribeScannerRequestMessage {\n type: OutboundMessageType.TranscribeScannerRequest;\n /** Correlation id — echo it back verbatim in the result. */\n request_id: string;\n /** The scanner (badge/barcode) element whose scan should be transcribed (e.g. `\"element_6\"`). */\n element_id: string;\n /** Public URLs of the uploaded scan images (the scanner element's image value). */\n image_urls: string[];\n /** The draft submission token the webview was loaded with, when it has one. */\n draft_submission_token?: string;\n}\n\n/** Discriminated union of every message the webview can emit to its host. */\nexport type OutboundMessage =\n | FormLoadCompleteMessage\n | FormSubmitSuccessMessage\n | FormErrorMessageMessage\n | SubmissionBodyMessage\n | ConnexionsProfileRedirectMessage\n | ConnexionsDownloadVcardMessage\n | TranscribeScannerRequestMessage;\n\n/** Maps each outbound `type` to its full message shape (used by the client's `.on`). */\nexport type OutboundMessageMap = {\n [M in OutboundMessage as M[\"type\"]]: M;\n};\n\n/* ------------------------------------------------------------------ *\n * Inbound: host → webview\n * ------------------------------------------------------------------ */\n\n/** Message `type` values the host sends into the webview. */\nexport enum InboundMessageType {\n /** Programmatically trigger form submission (as if the user pressed submit). */\n Submit = \"submit_form\",\n /** Reset the form, clearing all entered values. */\n Reset = \"reset_form\",\n /** Pre-fill the form with existing data. */\n FormPrefill = \"form_prefill\",\n /** Switch the current submission into draft-update mode. */\n UpdateDraft = \"update_draft\",\n /** Run validation against a target field (or the whole form). */\n TriggerValidation = \"trigger_validation\",\n /** Answer to an outbound {@link OutboundMessageType.TranscribeScannerRequest}. */\n TranscribeScannerResult = \"transcribe_scanner_result\",\n}\n\n/**\n * Shape selector for {@link InboundMessageType.FormPrefill} payloads.\n *\n * Vestigial: the webview keys only off the message `type` and reads `data` directly —\n * it never inspects `data_type`. Confirmed by mutation-testing the integration spec (a\n * deliberately wrong value changed nothing). Kept because it is part of the shipped wire\n * shape and older webview builds may still read it; `prefill()` sets it for you.\n */\nexport enum PrefillDataType {\n UlcSubmissionAndInfo = \"ulc_submission_and_info\",\n}\n\n/** Targets for {@link InboundMessageType.TriggerValidation}. */\nexport type ValidationTarget = \"invitation_code\" | \"email\" | \"all\";\n\n/**\n * A single transcription field/value item for the `info` prefill array.\n *\n * The webview matches each item to a form element by `ll_field_unique_identifier`\n * alone (e.g. `\"FirstName\"`, `\"Email\"`); `ll_field_id` is catalog metadata and is not\n * used for matching, so it is accepted as either a number or a string. `value` is\n * typically a string but may be a boolean (e.g. the PII opt-out field).\n */\nexport interface PrefillInfoItem {\n ll_field_unique_identifier: string;\n ll_field_id?: string | number;\n value: string | boolean;\n}\n\ninterface SubmitMessage {\n type: InboundMessageType.Submit;\n}\ninterface ResetMessage {\n type: InboundMessageType.Reset;\n}\ninterface UpdateDraftMessage {\n type: InboundMessageType.UpdateDraft;\n}\ninterface TriggerValidationMessage {\n type: InboundMessageType.TriggerValidation;\n target: ValidationTarget;\n}\ninterface PrefillMessage {\n type: InboundMessageType.FormPrefill;\n data_type: PrefillDataType.UlcSubmissionAndInfo;\n data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] };\n}\n\n/**\n * One transcribed field in {@link TranscribeScannerResultData.fields}.\n *\n * Semantically the same thing as a {@link PrefillInfoItem} but in the transcription\n * service's camelCase wire casing — `llFieldIdentifier` carries the same values as\n * `ll_field_unique_identifier` (e.g. `\"FirstName\"`, `\"Email\"`), and it is what the\n * webview matches form elements by. The webview ignores fields with an empty `value`\n * rather than blanking already-filled inputs.\n */\nexport interface TranscribedScannerField {\n /** Catalog field identifier the webview matches on, e.g. `\"FirstName\"`. */\n llFieldIdentifier: string;\n /** Catalog field id — metadata, not used for matching. */\n llFieldId?: number;\n /** Display name as the transcription service reports it. */\n llFieldNameName?: string;\n value: string;\n}\n\n/** Success payload of a {@link TranscribeScannerResultMessage}. */\nexport interface TranscribeScannerResultData {\n fields: TranscribedScannerField[];\n /** How the transcription was produced, e.g. `\"ocr_transcription\"`. */\n submissionType?: string;\n}\n\n/**\n * Host → webview: the answer to a {@link TranscribeScannerRequestMessage}. Send\n * `data` with the transcribed fields on success, or `error` (display-ready text) on\n * failure. `request_id` should echo the request's; the webview also accepts a result\n * without one while exactly one request is pending, for hosts that answer strictly\n * one at a time.\n */\nexport interface TranscribeScannerResultMessage {\n type: InboundMessageType.TranscribeScannerResult;\n /** The `request_id` of the request being answered, verbatim. */\n request_id?: string;\n /** Transcribed fields (success). */\n data?: TranscribeScannerResultData;\n /** Display-ready error text (failure) — leave `data` unset. */\n error?: string;\n}\n\n/** Discriminated union of every message the host can send into the webview. */\nexport type InboundMessage =\n | SubmitMessage\n | ResetMessage\n | UpdateDraftMessage\n | TriggerValidationMessage\n | PrefillMessage\n | TranscribeScannerResultMessage;\n\n/* ------------------------------------------------------------------ *\n * Runtime guards / parsing\n * ------------------------------------------------------------------ */\n\nconst OUTBOUND_TYPES: ReadonlySet<string> = new Set(Object.values(OutboundMessageType));\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parses a raw `MessageEvent.data` value into a typed {@link OutboundMessage}, or\n * returns `null` if it is not a recognized Captello webview message.\n *\n * Accepts either a JSON string (the webview always sends strings) or an\n * already-parsed object, so it is robust to hosts/proxies that pre-parse.\n */\nexport function parseOutboundMessage(data: unknown): OutboundMessage | null {\n let value: unknown = data;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n return null;\n }\n }\n if (!isPlainObject(value)) return null;\n if (typeof value[\"type\"] !== \"string\" || !OUTBOUND_TYPES.has(value[\"type\"])) return null;\n return value as unknown as OutboundMessage;\n}\n","import { InboundMessageType, OutboundMessageType, parseOutboundMessage, PrefillDataType } from \"./messages\";\nimport type {\n InboundMessage,\n OutboundMessage,\n OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n TranscribeScannerRequestMessage,\n TranscribeScannerResultData,\n ValidationTarget,\n} from \"./messages\";\n\n/** Listener for a specific outbound message type. */\nexport type OutboundListener<T extends OutboundMessageType> = (message: OutboundMessageMap[T]) => void;\n\n/** Listener for every outbound message (used by {@link CaptelloWebview.onAny}). */\nexport type AnyOutboundListener = (message: OutboundMessage) => void;\n\n/**\n * Handler for {@link CaptelloWebview.onTranscribeScannerRequest}. Receives the full\n * request (`element_id`, `image_urls`, `draft_submission_token`) plus a\n * {@link TranscribeScannerReply}, and answers in either style:\n *\n * - **return** the transcribed fields (a promise is fine) — the promise-based style, or\n * - **call `reply.resolve(...)` / `reply.reject(...)`** whenever the work finishes —\n * for callback-style code that can't hand back a promise.\n *\n * Returning nothing hands ownership to `reply`, leaving the request open until it\n * answers. A thrown error / rejection becomes an error result; if it's an `Error`, its\n * `message` is shown to the user by the webview, so throw display-ready messages.\n */\nexport type TranscribeScannerRequestHandler = (\n request: TranscribeScannerRequestMessage,\n reply: TranscribeScannerReply,\n) => TranscribeScannerResultData | void | Promise<TranscribeScannerResultData | void>;\n\n/**\n * The second argument handed to a {@link TranscribeScannerRequestHandler} — everything\n * needed to answer one request, so the handler never has to reach back out to the\n * client or touch `postMessage` itself.\n *\n * Use it when the transcription can't hand back a promise: a callback-style AJAX\n * wrapper, an event bus, a method that returns `void` and finishes whenever it\n * finishes. Pass it along to whatever does the work and let that code answer:\n *\n * ```js\n * webview.onTranscribeScannerRequest((request, reply) => {\n * transcribeService(request.image_urls,\n * (fields) => reply.resolve({ fields, submissionType: \"ocr_transcription\" }),\n * () => reply.reject(\"Transcription failed.\"),\n * );\n * });\n * ```\n *\n * A handler that returns a value (or a promise of one) is answered from that return\n * value and can ignore this entirely — the promise style is unchanged.\n *\n * The first answer wins: once `resolve`, `reject`, or the handler's own return value\n * has answered, later calls are ignored rather than posting a second result.\n */\nexport interface TranscribeScannerReply {\n /** Answer with the transcribed fields. */\n resolve(data: TranscribeScannerResultData): void;\n /**\n * Fail the request. `error` is shown to the user by the webview verbatim, so pass\n * display-ready text.\n */\n reject(error: string): void;\n /** `true` once this request has been answered, by either path. */\n readonly answered: boolean;\n}\n\n/**\n * Identifies the transcribe request being answered by\n * {@link CaptelloWebview.resolveTranscribeScannerRequest} /\n * {@link CaptelloWebview.rejectTranscribeScannerRequest} — the request message itself,\n * or its bare `request_id`.\n *\n * `null`/`undefined` sends an un-correlated result, which the webview accepts only\n * while exactly one request is pending. Pass the request whenever you have it.\n */\nexport type TranscribeScannerRequestRef = TranscribeScannerRequestMessage | string | null | undefined;\n\n/** The `unsubscribe()` side of a subscription handle. */\nexport interface Subscription {\n /** Remove the listener. Safe to call more than once. */\n unsubscribe(): void;\n}\n\n/**\n * Handle returned by every `on*` method. It is an object carrying\n * {@link Subscription.unsubscribe}, and is also directly callable:\n *\n * ```ts\n * const sub = webview.on(OutboundMessageType.SubmissionBody, save);\n * sub.unsubscribe(); // preferred — reads clearly at the call site\n * sub(); // equivalent\n * ```\n *\n * Both do the same thing; the callable form keeps older `const off = ...; off()`\n * code working.\n */\nexport type Unsubscribe = (() => void) & Subscription;\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when the webview reports\n * a `form_error_message`. `message` is the translated, display-ready text.\n */\nexport class SubmissionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SubmissionError\";\n }\n}\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when no `submission_body`\n * or `form_error_message` arrives within the timeout.\n */\nexport class SubmissionTimeoutError extends Error {\n constructor(public readonly timeoutMs: number) {\n super(`Captello webview did not respond to submit within ${timeoutMs}ms.`);\n this.name = \"SubmissionTimeoutError\";\n }\n}\n\nexport interface CaptelloWebviewOptions {\n /**\n * Origin to validate incoming messages against and to target outgoing messages.\n * Strongly recommended — set it to the webview's origin (e.g.\n * `\"https://capture.captello.com\"`).\n *\n * A full URL is accepted and reduced to its origin, so you can pass the embed URL\n * or the webview base URL you already have on hand rather than deriving the origin\n * yourself.\n *\n * Defaults to `\"*\"`, which accepts messages from any origin and posts without an\n * origin check. Only acceptable for trusted/local development.\n */\n targetOrigin?: string;\n /**\n * The window to attach the `message` listener to. Defaults to the global `window`.\n * Override for testing or non-standard host environments.\n */\n hostWindow?: Window;\n /**\n * If `true` (default), incoming messages are accepted only when they originate\n * from the bound iframe's `contentWindow`. Set `false` only if the webview relays\n * messages through an intermediate window and source matching is impossible.\n */\n matchSource?: boolean;\n /**\n * If `true` (default), messages sent before the webview reports\n * `form_load_complete` are buffered and flushed, in order, once it's ready. This\n * removes a common footgun: calling `prefill(...)` right after mount would\n * otherwise post to a form that isn't listening yet and be silently dropped.\n *\n * Set `false` to send immediately (the legacy behavior). Note: a client that\n * attaches *after* the form already loaded will not have seen `form_load_complete`,\n * so its queued messages won't flush — create the client with the iframe.\n */\n queueUntilReady?: boolean;\n /**\n * If `true` (default), the client calls {@link CaptelloWebview.destroy} on itself\n * once the bound iframe is removed from the document — so a host that tears down a\n * panel, modal, or route doesn't leak the `message` listener or have to remember a\n * matching `destroy()`.\n *\n * Removal is detected with a `MutationObserver`, and an ancestor being removed\n * counts: what matters is that the iframe is no longer connected to the document.\n * Only ever fires after the iframe has been seen connected, so constructing against\n * a not-yet-inserted element is not mistaken for a teardown.\n *\n * Requires a real DOM element and `MutationObserver`; with a `{ contentWindow }`\n * stand-in, or in a non-DOM environment, there is nothing to observe and this is\n * inert. Set `false` to manage the lifetime entirely yourself — e.g. when you\n * deliberately detach and re-insert the same iframe element.\n */\n autoDestroy?: boolean;\n /**\n * If `true`, the client may learn its webview window from the **first** inbound\n * message instead of being handed an iframe — see {@link attach}, which is the\n * supported way to switch this on.\n *\n * Only ever adopts a sender that has already cleared the origin check and parsed as\n * a well-formed Captello message, so with `targetOrigin` set to the webview's origin\n * only the webview can be adopted. Every later message is source-matched against\n * whatever was adopted, so this is as strict as an explicit iframe after the first\n * message. Under the default `targetOrigin: \"*\"` that gate is absent — one more\n * reason to set it in production.\n *\n * An explicitly passed iframe always wins; nothing is adopted while one resolves.\n *\n * @default false\n */\n adoptSource?: boolean;\n}\n\n/**\n * Wraps a removal function into the {@link Unsubscribe} handle: callable, and carrying\n * an `unsubscribe()` method that does the same thing.\n */\nfunction makeSubscription(off: () => void): Unsubscribe {\n const handle = (() => {\n off();\n }) as Unsubscribe;\n handle.unsubscribe = off;\n return handle;\n}\n\n/**\n * How often an adopted webview window is checked for `closed`. Long enough to be free\n * in practice, short enough that a closed panel doesn't leave a listener attached for\n * any meaningful time.\n */\nconst ADOPTED_WINDOW_POLL_MS = 5_000;\n\n/** Emits a console warning in development builds only. No-op in production / no bundler. */\nfunction devWarn(message: string): void {\n try {\n if (typeof process !== \"undefined\" && process.env && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(message);\n }\n } catch {\n /* `process` not defined (pure browser, no bundler define) → stay silent */\n }\n}\n\n/** Options for {@link attach}. Same as the client's, minus the ones it manages itself. */\nexport type AttachOptions = Omit<CaptelloWebviewOptions, \"adoptSource\">;\n\n/**\n * Attaches to the embedded Captello webview **without an iframe reference**.\n *\n * This is the drop-in replacement for a hand-rolled global\n * `window.addEventListener(\"message\", …)` dispatcher. Call it once, register your\n * handlers, and you are done — there is nothing to query from the DOM, no ordering to\n * get right relative to when the iframe is created, and no teardown to remember.\n *\n * ```js\n * const webview = CaptelloSdk.attach({ targetOrigin: CAPTURE_PORTAL_WEB_VIEW_BASE });\n *\n * webview.on(CaptelloSdk.OutboundMessageType.FormErrorMessage, (m) => showError(m.data));\n * webview.on(CaptelloSdk.OutboundMessageType.SubmissionBody, (m) => save(m.data));\n * webview.onTranscribeScannerRequest((request, reply) => transcribe(request, reply));\n * ```\n *\n * How it finds the form: the first inbound message that clears the origin check **and**\n * parses as a well-formed Captello message identifies the webview. That sender becomes\n * the target for sends and the window every later message is source-matched against —\n * so after the first message this is as strict as passing the iframe. Set\n * `targetOrigin` to the webview's origin and only the webview can ever be adopted.\n *\n * Teardown is still automatic: a removed iframe's window reports `closed`, which is\n * readable cross-origin, so the client notices its form going away and destroys itself.\n * Pass `autoDestroy: false` to own the lifetime yourself.\n *\n * The trade-off versus {@link CaptelloWebview}: until the form speaks once there is no\n * window to post to, so an unprompted `submit()` / `prefill()` before then has nowhere\n * to go. With the default `queueUntilReady` those sends are buffered and flushed on\n * `form_load_complete`, which covers the normal case. If you drive the form\n * unprompted from page load and can hold the element, prefer\n * `new CaptelloWebview(iframe, …)`.\n */\nexport function attach(options: AttachOptions = {}): CaptelloWebview {\n // No element to bind: the window is filled in by adoption on the first message.\n return new CaptelloWebview({ contentWindow: null }, { ...options, adoptSource: true });\n}\n\n/**\n * Reduces a `targetOrigin` option to a bare origin.\n *\n * `postMessage` and the inbound `event.origin` check both work on origins only, but the\n * value a host has on hand is usually a full webview URL or a base path (e.g. the\n * `.../webview` base your embed URLs are built from). Accept either and derive the\n * origin, so passing a URL scopes messaging correctly instead of silently matching\n * nothing.\n */\nfunction normalizeTargetOrigin(value: string | undefined): string {\n if (!value || value === \"*\") return \"*\";\n try {\n return new URL(value).origin;\n } catch {\n // Not parseable as a URL — pass through unchanged so an exact-origin string\n // still works in environments without a URL implementation.\n return value;\n }\n}\n\n/**\n * The webview's iframe — the element itself, or any object exposing its\n * `contentWindow` (useful for test doubles).\n *\n * The caller is responsible for the iframe being mounted before constructing the\n * client: messages are source-matched against its `contentWindow` from the very first\n * one, and sends post to it directly.\n *\n * When a real DOM element is passed, the client also tears itself down automatically\n * once that element leaves the document — see\n * {@link CaptelloWebviewOptions.autoDestroy}.\n */\nexport type FrameLike = HTMLIFrameElement | { contentWindow: Window | null };\n\n/**\n * Host-side controller for an embedded Captello capture webview.\n *\n * Wraps a single `<iframe>` and encodes the full message protocol:\n * - **Receiving** (webview → host): subscribe with {@link on} / {@link onAny}.\n * - **Sending** (host → webview): use {@link submit}, {@link reset}, {@link prefill},\n * {@link triggerValidation}, {@link updateDraft}, or the lower-level {@link send}.\n *\n * Wire details handled for you: outgoing messages are `JSON.stringify`'d (the webview\n * parses inbound data with `JSON.parse`, so a raw object would be ignored), and\n * incoming messages are validated by origin + source before being parsed.\n *\n * @example\n * ```ts\n * const iframe = document.querySelector(\"iframe\")!;\n * const webview = new CaptelloWebview(iframe, {\n * targetOrigin: \"https://capture.captello.com\",\n * });\n *\n * webview.on(OutboundMessageType.FormLoadComplete, () => console.log(\"ready\"));\n * webview.on(OutboundMessageType.SubmissionBody, (msg) => save(msg.data));\n *\n * // later, drive the form:\n * webview.submit();\n *\n * // on teardown:\n * webview.destroy();\n * ```\n */\nexport class CaptelloWebview {\n private readonly frame: FrameLike;\n /** Watches for the iframe leaving the document — see `autoDestroy`. */\n private frameObserver?: MutationObserver;\n /** The webview window learned from an inbound message — see `adoptSource`. */\n private adoptedWindow: Window | null = null;\n private readonly adoptSource: boolean;\n private readonly autoDestroy: boolean;\n /** Watches an adopted window for `closed` — see `watchAdoptedWindow`. */\n private adoptedPoll?: ReturnType<typeof setInterval>;\n private readonly targetOrigin: string;\n private readonly hostWindow: Window;\n private readonly matchSource: boolean;\n\n private readonly listeners = new Map<OutboundMessageType, Set<OutboundListener<OutboundMessageType>>>();\n private readonly anyListeners = new Set<AnyOutboundListener>();\n private readonly boundHandler: (event: MessageEvent) => void;\n private destroyed = false;\n\n private readonly queueUntilReady: boolean;\n /** True once `form_load_complete` has been observed. */\n private ready = false;\n /** Messages sent before ready, flushed in order on load. */\n private readonly outbox: InboundMessage[] = [];\n\n constructor(frame: FrameLike, options: CaptelloWebviewOptions = {}) {\n if (!frame) {\n throw new Error(\"CaptelloWebview: a mounted iframe element (or { contentWindow }) is required.\");\n }\n this.frame = frame;\n this.targetOrigin = normalizeTargetOrigin(options.targetOrigin);\n this.matchSource = options.matchSource ?? true;\n this.queueUntilReady = options.queueUntilReady ?? true;\n\n // Nudge (dev only) when running without origin scoping. \"*\" accepts inbound\n // messages from any origin and posts outbound without an origin check — fine\n // for local/trusted dev, unsafe in production. Set targetOrigin to the\n // webview's origin, e.g. `new URL(embedUrl).origin`.\n if (this.targetOrigin === \"*\") {\n devWarn(\n '[captello-sdk] No targetOrigin set — defaulting to \"*\", which accepts messages ' +\n \"from any origin and posts without an origin check. Set targetOrigin to the webview's \" +\n \"origin (e.g. new URL(embedUrl).origin) in production.\",\n );\n }\n\n const hostWindow = options.hostWindow ?? (typeof window !== \"undefined\" ? window : undefined);\n if (!hostWindow) {\n throw new Error(\n \"CaptelloWebview: no host window available. Pass `hostWindow` when constructing outside a browser.\",\n );\n }\n this.hostWindow = hostWindow;\n\n this.boundHandler = (event: MessageEvent) => this.handleMessage(event);\n this.hostWindow.addEventListener(\"message\", this.boundHandler);\n\n this.adoptSource = options.adoptSource ?? false;\n this.autoDestroy = options.autoDestroy ?? true;\n if (this.autoDestroy) {\n this.watchForFrameRemoval();\n }\n }\n\n /**\n * Tears the client down once the bound iframe leaves the document.\n *\n * Observes the whole document subtree because the iframe usually goes away with an\n * ancestor (a panel or modal being emptied), which produces no mutation on the\n * iframe's own parent. `isConnected` is the actual test, so removal at any depth\n * counts; the observer only bothers checking when a mutation actually removed\n * something.\n */\n /**\n * Tears the client down once an adopted webview window goes away.\n *\n * With no iframe element there is nothing to observe in the DOM, but a removed\n * iframe's `contentWindow` reports `closed === true` — and `closed` is readable\n * cross-origin — so a cheap periodic check is enough. Verified in Chromium against a\n * genuinely cross-origin iframe.\n */\n private watchAdoptedWindow(): void {\n if (this.adoptedPoll !== undefined) return;\n this.adoptedPoll = setInterval(() => {\n let gone = false;\n try {\n gone = !this.adoptedWindow || this.adoptedWindow.closed;\n } catch {\n // Even `closed` can throw in exotic cases; treat that as gone rather\n // than looping on it forever.\n gone = true;\n }\n if (gone) this.destroy();\n }, ADOPTED_WINDOW_POLL_MS);\n // Don't hold a Node process open just for this (no-op in browsers).\n (this.adoptedPoll as unknown as { unref?: () => void })?.unref?.();\n }\n\n private watchForFrameRemoval(): void {\n const node = this.frame as Partial<Node>;\n // A `{ contentWindow }` stand-in, or an environment without the DOM APIs, has\n // nothing observable — leave the lifetime to the caller.\n if (typeof MutationObserver === \"undefined\" || typeof Node === \"undefined\" || !(this.frame instanceof Node)) {\n return;\n }\n const doc = node.ownerDocument;\n if (!doc?.documentElement) return;\n\n // Only arm once the iframe is actually in the document, so constructing against\n // an element that hasn't been inserted yet isn't read as a teardown.\n if (!node.isConnected) return;\n\n this.frameObserver = new MutationObserver((records) => {\n if (!records.some((record) => record.removedNodes.length > 0)) return;\n if (!node.isConnected) this.destroy();\n });\n this.frameObserver.observe(doc.documentElement, { childList: true, subtree: true });\n }\n\n /** `true` once the webview has reported `form_load_complete`. */\n get isReady(): boolean {\n return this.ready;\n }\n\n /* -------------------------------------------------------------- *\n * Receiving (webview → host)\n * -------------------------------------------------------------- */\n\n /**\n * Subscribe to a single outbound message type. Returns an unsubscribe function.\n *\n * @example webview.on(OutboundMessageType.FormErrorMessage, (m) => toast(m.data));\n */\n on<T extends OutboundMessageType>(type: T, listener: OutboundListener<T>): Unsubscribe {\n let set = this.listeners.get(type);\n if (!set) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener as OutboundListener<OutboundMessageType>);\n return makeSubscription(() => {\n set?.delete(listener as OutboundListener<OutboundMessageType>);\n });\n }\n\n /**\n * Subscribe once: the listener is removed automatically after it fires the first\n * time for `type`. Returns an unsubscribe function for cancelling early.\n */\n once<T extends OutboundMessageType>(type: T, listener: OutboundListener<T>): Unsubscribe {\n const off = this.on(type, (message) => {\n off();\n listener(message);\n });\n return off;\n }\n\n /** Subscribe to every outbound message regardless of type. Returns an unsubscribe function. */\n onAny(listener: AnyOutboundListener): Unsubscribe {\n this.anyListeners.add(listener);\n return makeSubscription(() => {\n this.anyListeners.delete(listener);\n });\n }\n\n /* -------------------------------------------------------------- *\n * Sending (host → webview)\n * -------------------------------------------------------------- */\n\n /**\n * Low-level send: posts any inbound message to the webview as a JSON string.\n * Prefer the typed helpers below; use this only for forward-compatibility.\n *\n * When `queueUntilReady` is enabled (the default) and the form hasn't reported\n * `form_load_complete` yet, the message is buffered and flushed on load instead of\n * posted immediately.\n *\n * @throws if the iframe's `contentWindow` is not available (not yet loaded /\n * detached) and the message can't be queued.\n */\n send(message: InboundMessage): void {\n if (this.destroyed) {\n throw new Error(\"CaptelloWebview: cannot send after destroy().\");\n }\n if (this.queueUntilReady && !this.ready) {\n this.outbox.push(message);\n return;\n }\n this.postNow(message);\n }\n\n /** Posts a message immediately, bypassing the ready-queue. */\n private postNow(message: InboundMessage): void {\n const target = this.frame.contentWindow ?? this.adoptedWindow;\n if (!target) {\n throw new Error(\n this.adoptSource\n ? \"CaptelloWebview: no webview window yet — nothing has been adopted, because no \" +\n \"message has arrived from the form. Send only in response to a message, or after \" +\n \"form_load_complete.\"\n : \"CaptelloWebview: iframe.contentWindow is null (not yet loaded, or detached). \" +\n \"Construct the client with a mounted iframe.\",\n );\n }\n // The webview reads inbound data with JSON.parse(event.data), so it must be a string.\n target.postMessage(JSON.stringify(message), this.targetOrigin);\n }\n\n /** Marks the client ready and flushes any queued messages, in order. */\n private markReadyAndFlush(): void {\n if (this.ready) return;\n this.ready = true;\n const queued = this.outbox.splice(0);\n for (const message of queued) {\n try {\n this.postNow(message);\n } catch {\n /* iframe detached between load and flush — drop silently */\n }\n }\n }\n\n /** Programmatically submit the form (fire-and-forget). */\n submit(): void {\n this.send({ type: InboundMessageType.Submit });\n }\n\n /**\n * Submit the form and await the outcome.\n *\n * Sends `submit_form`, then resolves with the {@link SubmissionBody} when the\n * webview emits `submission_body`, or rejects with a {@link SubmissionError}\n * (carrying the translated message) when it emits `form_error_message`. Rejects\n * with a {@link SubmissionTimeoutError} if neither arrives within `timeoutMs`.\n *\n * This is the typed, leak-free version of the common \"click submit, wait for the\n * result\" flow — listeners are always cleaned up, including on timeout.\n *\n * @param timeoutMs how long to wait before giving up. Defaults to 60_000.\n * @example\n * try {\n * const body = await webview.submitAndWait();\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\n submitAndWait(timeoutMs = 60_000): Promise<SubmissionBody> {\n return new Promise<SubmissionBody>((resolve, reject) => {\n let settled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const cleanup = () => {\n settled = true;\n offSuccess();\n offError();\n if (timer !== undefined) clearTimeout(timer);\n };\n\n const offSuccess = this.on(OutboundMessageType.SubmissionBody, (message) => {\n if (settled) return;\n cleanup();\n resolve(message.data);\n });\n const offError = this.on(OutboundMessageType.FormErrorMessage, (message) => {\n if (settled) return;\n cleanup();\n reject(new SubmissionError(message.data));\n });\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => {\n if (settled) return;\n cleanup();\n reject(new SubmissionTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n\n try {\n this.send({ type: InboundMessageType.Submit });\n } catch (err) {\n if (!settled) {\n cleanup();\n reject(err);\n }\n }\n });\n }\n\n /** Reset the form, clearing all entered values. */\n reset(): void {\n this.send({ type: InboundMessageType.Reset });\n }\n\n /** Switch the current submission into draft-update mode. */\n updateDraft(): void {\n this.send({ type: InboundMessageType.UpdateDraft });\n }\n\n /** Run validation against a target field, or `\"all\"` for the whole form. */\n triggerValidation(target: ValidationTarget): void {\n this.send({ type: InboundMessageType.TriggerValidation, target });\n }\n\n /** Pre-fill form fields from a submission body, transcription items, or both. */\n prefill(data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }): void {\n this.send({\n type: InboundMessageType.FormPrefill,\n data_type: PrefillDataType.UlcSubmissionAndInfo,\n data,\n });\n }\n\n /**\n * Answer the webview's transcribe requests (its transcribe button — shown when the\n * embed URL sets `showTranscribeButton` — sends `transcribe_scanner_request` when\n * pressed).\n *\n * The handler receives the request (`element_id`, `image_urls`,\n * `draft_submission_token`) and a {@link TranscribeScannerReply}, and answers in\n * whichever style suits the transcription code. Either way the result is posted for\n * you — the host never builds or stringifies a `transcribe_scanner_result`.\n *\n * **Return the fields** when the work is promise-based:\n *\n * ```js\n * webview.onTranscribeScannerRequest(async ({ image_urls }) => {\n * const fields = await transcribe(image_urls);\n * return { fields, submissionType: \"ocr_transcription\" };\n * });\n * ```\n *\n * **Or answer through `reply`** when it isn't — a callback-style AJAX wrapper, an\n * event bus, a method that returns `void`. Hand `reply` to whatever does the work:\n *\n * ```js\n * webview.onTranscribeScannerRequest((request, reply) => {\n * transcribeService(request.image_urls,\n * (fields) => reply.resolve({ fields, submissionType: \"ocr_transcription\" }),\n * () => reply.reject(\"Transcription failed.\"),\n * );\n * });\n * ```\n *\n * A handler that returns nothing is taken to own the reply, so the request stays\n * open until `reply` answers it. A thrown error / rejected promise is sent as an\n * error result; an `Error`'s `message` is shown to the user by the webview, so throw\n * display-ready messages. The first answer wins — a second is ignored.\n *\n * Returns an unsubscribe function.\n */\n onTranscribeScannerRequest(handler: TranscribeScannerRequestHandler): Unsubscribe {\n return this.on(OutboundMessageType.TranscribeScannerRequest, (request) => {\n // One answer per request, whichever path gets there first: the reply handle,\n // or the handler's own return value.\n let answered = false;\n const answer = (result: { data?: TranscribeScannerResultData; error?: string }): void => {\n if (answered) {\n devWarn(\n \"[captello-sdk] Ignored a second answer to transcribe request \" +\n `\"${request.request_id}\" — it was already answered. Answer either by returning ` +\n \"from the handler or by calling reply.resolve()/reply.reject(), not both.\",\n );\n return;\n }\n answered = true;\n this.sendTranscribeScannerResult(request, result);\n };\n\n const reply: TranscribeScannerReply = {\n resolve: (data) => answer({ data }),\n reject: (error) => answer({ error: error || \"Transcription failed.\" }),\n get answered() {\n return answered;\n },\n };\n\n Promise.resolve()\n .then(() => handler(request, reply))\n .then(\n (data) => {\n // `undefined` means the handler didn't answer by returning, so it\n // owns the reply — leave the request open. Answering later through\n // `reply` is the normal deferred path, so this is not a problem...\n if (data === undefined) {\n // ...unless the handler never took a `reply` parameter, in\n // which case nothing can ever answer and the form's button\n // would spin until it times out. Almost always an async\n // handler missing its `return`.\n if (!answered && handler.length < 2) {\n devWarn(\n \"[captello-sdk] A transcribe handler returned nothing and takes no `reply` \" +\n \"parameter, so this request can never be answered and the form's button \" +\n \"will spin until it times out. Either return the fields, or accept \" +\n \"`(request, reply)` and call reply.resolve()/reply.reject().\",\n );\n }\n return;\n }\n answer({ data });\n },\n (err: unknown) =>\n answer({\n error: err instanceof Error && err.message ? err.message : \"Transcription failed.\",\n }),\n );\n });\n }\n\n /**\n * Answer a transcribe request **later**, from code that can't hand back a promise\n * — a callback-style AJAX wrapper, an event bus, a method that returns `void`.\n *\n * Pair it with a plain `on(OutboundMessageType.TranscribeScannerRequest, ...)`\n * subscription: hold on to the request (or just its `request_id`), and call this\n * once the transcription lands. Prefer {@link onTranscribeScannerRequest} when\n * your transcription code is already promise-based — it does this for you.\n *\n * Accepts the request message itself or its bare `request_id`. The reply is\n * `JSON.stringify`'d and posted immediately, bypassing the ready-queue: a received\n * request already proves the webview is live and listening. Replying after\n * {@link destroy}, or after the iframe is detached, is a silent no-op — by then\n * there is nobody left to answer, and the webview times its own button out.\n *\n * @example\n * webview.on(OutboundMessageType.TranscribeScannerRequest, (request) => {\n * ajax.send(\"Scanner\", \"transcribe\", { image_urls: request.image_urls },\n * (res) => webview.resolveTranscribeScannerRequest(request, {\n * fields: res.fields,\n * submissionType: \"ocr_transcription\",\n * }),\n * () => webview.rejectTranscribeScannerRequest(request, \"Transcription failed.\"),\n * );\n * });\n */\n resolveTranscribeScannerRequest(request: TranscribeScannerRequestRef, data: TranscribeScannerResultData): void {\n this.sendTranscribeScannerResult(request, { data });\n }\n\n /**\n * Fail a transcribe request answered via\n * {@link resolveTranscribeScannerRequest}'s deferred flow. `error` is shown to the\n * user verbatim by the webview, so pass display-ready text.\n *\n * Same delivery semantics as {@link resolveTranscribeScannerRequest}: posted\n * immediately, and a no-op once the client is destroyed or the iframe detached.\n */\n rejectTranscribeScannerRequest(request: TranscribeScannerRequestRef, error: string): void {\n this.sendTranscribeScannerResult(request, { error: error || \"Transcription failed.\" });\n }\n\n /**\n * Posts a `transcribe_scanner_result`, bypassing the ready-queue. A received\n * request proves the webview is live and listening, even if this client attached\n * after form load and never saw `form_load_complete`; queuing here could stall the\n * reply forever while the webview's button sits in its pending state until timeout.\n */\n private sendTranscribeScannerResult(\n request: TranscribeScannerRequestRef,\n result: { data?: TranscribeScannerResultData; error?: string },\n ): void {\n if (this.destroyed) return;\n const requestId = typeof request === \"string\" ? request : request?.request_id;\n try {\n this.postNow({\n type: InboundMessageType.TranscribeScannerResult,\n // Dropped from the JSON when undefined; the webview accepts a result\n // without one while exactly one request is pending.\n request_id: requestId,\n ...result,\n });\n } catch {\n /* iframe detached while transcribing — nobody left to answer */\n }\n }\n\n /* -------------------------------------------------------------- *\n * Lifecycle\n * -------------------------------------------------------------- */\n\n /** Remove the `message` listener and drop all subscriptions. Idempotent. */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.hostWindow.removeEventListener(\"message\", this.boundHandler);\n this.listeners.clear();\n this.anyListeners.clear();\n this.outbox.length = 0;\n this.frameObserver?.disconnect();\n this.adoptedWindow = null;\n if (this.adoptedPoll !== undefined) {\n clearInterval(this.adoptedPoll);\n this.adoptedPoll = undefined;\n }\n }\n\n /* -------------------------------------------------------------- *\n * Internals\n * -------------------------------------------------------------- */\n\n private handleMessage(event: MessageEvent): void {\n if (this.destroyed) return;\n\n // Origin check: skip when targetOrigin is the wildcard. Warn (dev only) if a\n // message that *looks* like ours is dropped on origin — a common \"why isn't my\n // listener firing?\" cause.\n if (this.targetOrigin !== \"*\" && event.origin !== this.targetOrigin) {\n if (parseOutboundMessage(event.data)) {\n devWarn(\n `[captello-sdk] Ignored a Captello message from origin \"${event.origin}\" ` +\n `(expected \"${this.targetOrigin}\"). Check the targetOrigin you passed.`,\n );\n }\n return;\n }\n\n // Parse before the source check: only a genuine Captello message is worth\n // warning about, and only one is allowed to become the adopted source below.\n const message = parseOutboundMessage(event.data);\n if (!message) return;\n\n // Source check: only accept messages from the bound iframe's window (or, in\n // adopt mode, from whichever window was adopted first).\n if (this.matchSource) {\n const expected = this.frame.contentWindow ?? this.adoptedWindow;\n if (expected && event.source !== expected) {\n devWarn(\n \"[captello-sdk] Ignored a Captello message from an unexpected source window \" +\n \"(not the bound iframe). If the webview relays through another window, set matchSource: false.\",\n );\n return;\n }\n }\n\n // Adopt the sender as the webview, if we're in adopt mode and don't have one yet.\n // It has cleared the origin check and parsed as a real Captello message, so it is\n // the form — and it is where replies belong. Done before the ready-flush so\n // queued sends have somewhere to go.\n if (this.adoptSource && !this.adoptedWindow && !this.frame.contentWindow) {\n this.adoptedWindow = (event.source as Window | null) ?? null;\n if (this.adoptedWindow && this.autoDestroy) this.watchAdoptedWindow();\n }\n\n // Flip to ready (and flush queued sends) the moment the form loads, before\n // dispatching to listeners — so a listener can send and have it post immediately.\n if (message.type === OutboundMessageType.FormLoadComplete) {\n this.markReadyAndFlush();\n }\n\n const set = this.listeners.get(message.type);\n if (set) {\n // Copy to a snapshot so a listener that unsubscribes mid-dispatch is safe.\n for (const listener of [...set]) listener(message);\n }\n if (this.anyListeners.size) {\n for (const listener of [...this.anyListeners]) listener(message);\n }\n }\n}\n"]}
|
|
@@ -17,6 +17,7 @@ var EmbedParam = /* @__PURE__ */ ((EmbedParam2) => {
|
|
|
17
17
|
EmbedParam2["ConnexionsEmbedMode"] = "cem";
|
|
18
18
|
EmbedParam2["Emro"] = "emro";
|
|
19
19
|
EmbedParam2["UseIn"] = "useIn";
|
|
20
|
+
EmbedParam2["ShowTranscribeButton"] = "show_transcribe_button";
|
|
20
21
|
return EmbedParam2;
|
|
21
22
|
})(EmbedParam || {});
|
|
22
23
|
var FormMode = /* @__PURE__ */ ((FormMode2) => {
|
|
@@ -72,7 +73,8 @@ var OPTION_TO_PARAM = [
|
|
|
72
73
|
var BOOLEAN_OPTION_TO_PARAM = [
|
|
73
74
|
["hideEmail", "he" /* HideEmail */],
|
|
74
75
|
["connexionsEmbedMode", "cem" /* ConnexionsEmbedMode */],
|
|
75
|
-
["emro", "emro" /* Emro */]
|
|
76
|
+
["emro", "emro" /* Emro */],
|
|
77
|
+
["showTranscribeButton", "show_transcribe_button" /* ShowTranscribeButton */]
|
|
76
78
|
];
|
|
77
79
|
var CAPTURE_SUBMISSION_PATH = "/capture/submission";
|
|
78
80
|
var CAPTURE_PATH_PATTERN = /\/capture\/(submission|activation)\/?$/;
|
|
@@ -110,5 +112,5 @@ function buildEmbedUrl(baseUrl, options = {}) {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl };
|
|
113
|
-
//# sourceMappingURL=chunk-
|
|
114
|
-
//# sourceMappingURL=chunk-
|
|
115
|
+
//# sourceMappingURL=chunk-ZX4AKPWF.js.map
|
|
116
|
+
//# sourceMappingURL=chunk-ZX4AKPWF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/embed-url.ts"],"names":["EmbedParam","FormMode","LauncherType","ActionButtonPosition","Language"],"mappings":"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","file":"chunk-ZX4AKPWF.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 * Show the transcribe button: the form renders a button that emits a\n * `host_processing_request` with `processing_type: \"transcribe_scanner_request\"`.\n * Only enable it when the host answers those requests (e.g. via\n * `CaptelloWebview.onProcessingRequest`).\n */\n ShowTranscribeButton = \"show_transcribe_button\",\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 * Show the transcribe button in the form (emits `host_processing_request` with\n * `processing_type: \"transcribe_scanner_request\"` when pressed). Enable only when\n * the host answers those requests (e.g. via `CaptelloWebview.onProcessingRequest`) —\n * otherwise the button times out with an error for the user.\n */\n showTranscribeButton?: 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 [\"showTranscribeButton\", EmbedParam.ShowTranscribeButton],\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"]}
|