@captello/ulc-webview-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +424 -0
- package/dist/chunk-ETF52K7K.js +91 -0
- package/dist/chunk-ETF52K7K.js.map +1 -0
- package/dist/chunk-ZMYMZK2A.js +328 -0
- package/dist/chunk-ZMYMZK2A.js.map +1 -0
- package/dist/client-3IBxKbIE.d.cts +561 -0
- package/dist/client-3IBxKbIE.d.ts +561 -0
- package/dist/index.cjs +594 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +121 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/promises.cjs +366 -0
- package/dist/promises.cjs.map +1 -0
- package/dist/promises.d.cts +72 -0
- package/dist/promises.d.ts +72 -0
- package/dist/promises.js +51 -0
- package/dist/promises.js.map +1 -0
- package/dist/react.cjs +476 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +108 -0
- package/dist/react.d.ts +108 -0
- package/dist/react.js +112 -0
- package/dist/react.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/messages.ts
|
|
4
|
+
var OutboundMessageType = /* @__PURE__ */ ((OutboundMessageType2) => {
|
|
5
|
+
OutboundMessageType2["FormLoadComplete"] = "form_load_complete";
|
|
6
|
+
OutboundMessageType2["FormErrorMessage"] = "form_error_message";
|
|
7
|
+
OutboundMessageType2["SubmissionBody"] = "submission_body";
|
|
8
|
+
OutboundMessageType2["FormSubmitSuccess"] = "form_submit_success";
|
|
9
|
+
OutboundMessageType2["ConnexionsProfileRedirect"] = "connexions_profile_redirect";
|
|
10
|
+
OutboundMessageType2["ConnexionsDownloadVcard"] = "connexions_download_vcard";
|
|
11
|
+
return OutboundMessageType2;
|
|
12
|
+
})(OutboundMessageType || {});
|
|
13
|
+
var OUTBOUND_TYPES = new Set(Object.values(OutboundMessageType));
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function parseOutboundMessage(data) {
|
|
18
|
+
let value = data;
|
|
19
|
+
if (typeof value === "string") {
|
|
20
|
+
try {
|
|
21
|
+
value = JSON.parse(value);
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (!isPlainObject(value)) return null;
|
|
27
|
+
if (typeof value["type"] !== "string" || !OUTBOUND_TYPES.has(value["type"])) return null;
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/client.ts
|
|
32
|
+
var SubmissionError = class extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "SubmissionError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var SubmissionTimeoutError = class extends Error {
|
|
39
|
+
constructor(timeoutMs) {
|
|
40
|
+
super(`Captello webview did not respond to submit within ${timeoutMs}ms.`);
|
|
41
|
+
this.timeoutMs = timeoutMs;
|
|
42
|
+
this.name = "SubmissionTimeoutError";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function devWarn(message) {
|
|
46
|
+
try {
|
|
47
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production") {
|
|
48
|
+
console.warn(message);
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
var CaptelloWebview = class {
|
|
54
|
+
constructor(frame, options = {}) {
|
|
55
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
56
|
+
this.anyListeners = /* @__PURE__ */ new Set();
|
|
57
|
+
this.destroyed = false;
|
|
58
|
+
/** True once `form_load_complete` has been observed. */
|
|
59
|
+
this.ready = false;
|
|
60
|
+
/** Messages sent before ready, flushed in order on load. */
|
|
61
|
+
this.outbox = [];
|
|
62
|
+
if (!frame) {
|
|
63
|
+
throw new Error("CaptelloWebview: an iframe element (or { contentWindow }) is required.");
|
|
64
|
+
}
|
|
65
|
+
this.frame = frame;
|
|
66
|
+
this.targetOrigin = options.targetOrigin ?? "*";
|
|
67
|
+
this.matchSource = options.matchSource ?? true;
|
|
68
|
+
this.queueUntilReady = options.queueUntilReady ?? true;
|
|
69
|
+
const hostWindow = options.hostWindow ?? (typeof window !== "undefined" ? window : void 0);
|
|
70
|
+
if (!hostWindow) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"CaptelloWebview: no host window available. Pass `hostWindow` when constructing outside a browser."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
this.hostWindow = hostWindow;
|
|
76
|
+
this.boundHandler = (event) => this.handleMessage(event);
|
|
77
|
+
this.hostWindow.addEventListener("message", this.boundHandler);
|
|
78
|
+
}
|
|
79
|
+
/** `true` once the webview has reported `form_load_complete`. */
|
|
80
|
+
get isReady() {
|
|
81
|
+
return this.ready;
|
|
82
|
+
}
|
|
83
|
+
/* -------------------------------------------------------------- *
|
|
84
|
+
* Receiving (webview → host)
|
|
85
|
+
* -------------------------------------------------------------- */
|
|
86
|
+
/**
|
|
87
|
+
* Subscribe to a single outbound message type. Returns an unsubscribe function.
|
|
88
|
+
*
|
|
89
|
+
* @example webview.on(OutboundMessageType.FormErrorMessage, (m) => toast(m.data));
|
|
90
|
+
*/
|
|
91
|
+
on(type, listener) {
|
|
92
|
+
let set = this.listeners.get(type);
|
|
93
|
+
if (!set) {
|
|
94
|
+
set = /* @__PURE__ */ new Set();
|
|
95
|
+
this.listeners.set(type, set);
|
|
96
|
+
}
|
|
97
|
+
set.add(listener);
|
|
98
|
+
return () => {
|
|
99
|
+
set?.delete(listener);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Subscribe once: the listener is removed automatically after it fires the first
|
|
104
|
+
* time for `type`. Returns an unsubscribe function for cancelling early.
|
|
105
|
+
*/
|
|
106
|
+
once(type, listener) {
|
|
107
|
+
const off = this.on(type, (message) => {
|
|
108
|
+
off();
|
|
109
|
+
listener(message);
|
|
110
|
+
});
|
|
111
|
+
return off;
|
|
112
|
+
}
|
|
113
|
+
/** Subscribe to every outbound message regardless of type. Returns an unsubscribe function. */
|
|
114
|
+
onAny(listener) {
|
|
115
|
+
this.anyListeners.add(listener);
|
|
116
|
+
return () => {
|
|
117
|
+
this.anyListeners.delete(listener);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/* -------------------------------------------------------------- *
|
|
121
|
+
* Sending (host → webview)
|
|
122
|
+
* -------------------------------------------------------------- */
|
|
123
|
+
/**
|
|
124
|
+
* Low-level send: posts any inbound message to the webview as a JSON string.
|
|
125
|
+
* Prefer the typed helpers below; use this only for forward-compatibility.
|
|
126
|
+
*
|
|
127
|
+
* When `queueUntilReady` is enabled (the default) and the form hasn't reported
|
|
128
|
+
* `form_load_complete` yet, the message is buffered and flushed on load instead of
|
|
129
|
+
* posted immediately.
|
|
130
|
+
*
|
|
131
|
+
* @throws if the iframe's `contentWindow` is not available (not yet loaded /
|
|
132
|
+
* detached) and the message can't be queued.
|
|
133
|
+
*/
|
|
134
|
+
send(message) {
|
|
135
|
+
if (this.destroyed) {
|
|
136
|
+
throw new Error("CaptelloWebview: cannot send after destroy().");
|
|
137
|
+
}
|
|
138
|
+
if (this.queueUntilReady && !this.ready) {
|
|
139
|
+
this.outbox.push(message);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.postNow(message);
|
|
143
|
+
}
|
|
144
|
+
/** Posts a message immediately, bypassing the ready-queue. */
|
|
145
|
+
postNow(message) {
|
|
146
|
+
const target = this.frame.contentWindow;
|
|
147
|
+
if (!target) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"CaptelloWebview: iframe.contentWindow is null. Wait for the iframe to load before sending."
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
target.postMessage(JSON.stringify(message), this.targetOrigin);
|
|
153
|
+
}
|
|
154
|
+
/** Marks the client ready and flushes any queued messages, in order. */
|
|
155
|
+
markReadyAndFlush() {
|
|
156
|
+
if (this.ready) return;
|
|
157
|
+
this.ready = true;
|
|
158
|
+
const queued = this.outbox.splice(0);
|
|
159
|
+
for (const message of queued) {
|
|
160
|
+
try {
|
|
161
|
+
this.postNow(message);
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Programmatically submit the form (fire-and-forget). */
|
|
167
|
+
submit() {
|
|
168
|
+
this.send({ type: "submit_form" /* Submit */ });
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Submit the form and await the outcome.
|
|
172
|
+
*
|
|
173
|
+
* Sends `submit_form`, then resolves with the {@link SubmissionBody} when the
|
|
174
|
+
* webview emits `submission_body`, or rejects with a {@link SubmissionError}
|
|
175
|
+
* (carrying the translated message) when it emits `form_error_message`. Rejects
|
|
176
|
+
* with a {@link SubmissionTimeoutError} if neither arrives within `timeoutMs`.
|
|
177
|
+
*
|
|
178
|
+
* This is the typed, leak-free version of the common "click submit, wait for the
|
|
179
|
+
* result" flow — listeners are always cleaned up, including on timeout.
|
|
180
|
+
*
|
|
181
|
+
* @param timeoutMs how long to wait before giving up. Defaults to 60_000.
|
|
182
|
+
* @example
|
|
183
|
+
* try {
|
|
184
|
+
* const body = await webview.submitAndWait();
|
|
185
|
+
* await persist(body);
|
|
186
|
+
* } catch (err) {
|
|
187
|
+
* if (err instanceof SubmissionError) showToast(err.message);
|
|
188
|
+
* }
|
|
189
|
+
*/
|
|
190
|
+
submitAndWait(timeoutMs = 6e4) {
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
let settled = false;
|
|
193
|
+
let timer;
|
|
194
|
+
const cleanup = () => {
|
|
195
|
+
settled = true;
|
|
196
|
+
offSuccess();
|
|
197
|
+
offError();
|
|
198
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
199
|
+
};
|
|
200
|
+
const offSuccess = this.on("submission_body" /* SubmissionBody */, (message) => {
|
|
201
|
+
if (settled) return;
|
|
202
|
+
cleanup();
|
|
203
|
+
resolve(message.data);
|
|
204
|
+
});
|
|
205
|
+
const offError = this.on("form_error_message" /* FormErrorMessage */, (message) => {
|
|
206
|
+
if (settled) return;
|
|
207
|
+
cleanup();
|
|
208
|
+
reject(new SubmissionError(message.data));
|
|
209
|
+
});
|
|
210
|
+
if (timeoutMs > 0 && timeoutMs !== Infinity) {
|
|
211
|
+
timer = setTimeout(() => {
|
|
212
|
+
if (settled) return;
|
|
213
|
+
cleanup();
|
|
214
|
+
reject(new SubmissionTimeoutError(timeoutMs));
|
|
215
|
+
}, timeoutMs);
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
this.send({ type: "submit_form" /* Submit */ });
|
|
219
|
+
} catch (err) {
|
|
220
|
+
if (!settled) {
|
|
221
|
+
cleanup();
|
|
222
|
+
reject(err);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/** Reset the form, clearing all entered values. */
|
|
228
|
+
reset() {
|
|
229
|
+
this.send({ type: "reset_form" /* Reset */ });
|
|
230
|
+
}
|
|
231
|
+
/** Switch the current submission into draft-update mode. */
|
|
232
|
+
updateDraft() {
|
|
233
|
+
this.send({ type: "update_draft" /* UpdateDraft */ });
|
|
234
|
+
}
|
|
235
|
+
/** Run validation against a target field, or `"all"` for the whole form. */
|
|
236
|
+
triggerValidation(target) {
|
|
237
|
+
this.send({ type: "trigger_validation" /* TriggerValidation */, target });
|
|
238
|
+
}
|
|
239
|
+
/** Pre-fill the form from a submission body (a received body or a partial). */
|
|
240
|
+
prefillSubmission(submission) {
|
|
241
|
+
this.send({
|
|
242
|
+
type: "form_prefill" /* FormPrefill */,
|
|
243
|
+
data_type: "ulc_submission" /* UlcSubmission */,
|
|
244
|
+
data: submission
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/** Pre-fill the form from a list of transcription field/value items. */
|
|
248
|
+
prefillInfo(info) {
|
|
249
|
+
this.send({
|
|
250
|
+
type: "form_prefill" /* FormPrefill */,
|
|
251
|
+
data_type: "info" /* Info */,
|
|
252
|
+
data: info
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/** Pre-fill the form from a submission plus transcription items. */
|
|
256
|
+
prefillSubmissionAndInfo(data) {
|
|
257
|
+
this.send({
|
|
258
|
+
type: "form_prefill" /* FormPrefill */,
|
|
259
|
+
data_type: "ulc_submission_and_info" /* UlcSubmissionAndInfo */,
|
|
260
|
+
data
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/* -------------------------------------------------------------- *
|
|
264
|
+
* Lifecycle
|
|
265
|
+
* -------------------------------------------------------------- */
|
|
266
|
+
/** Remove the `message` listener and drop all subscriptions. Idempotent. */
|
|
267
|
+
destroy() {
|
|
268
|
+
if (this.destroyed) return;
|
|
269
|
+
this.destroyed = true;
|
|
270
|
+
this.hostWindow.removeEventListener("message", this.boundHandler);
|
|
271
|
+
this.listeners.clear();
|
|
272
|
+
this.anyListeners.clear();
|
|
273
|
+
this.outbox.length = 0;
|
|
274
|
+
}
|
|
275
|
+
/* -------------------------------------------------------------- *
|
|
276
|
+
* Internals
|
|
277
|
+
* -------------------------------------------------------------- */
|
|
278
|
+
handleMessage(event) {
|
|
279
|
+
if (this.destroyed) return;
|
|
280
|
+
if (this.targetOrigin !== "*" && event.origin !== this.targetOrigin) {
|
|
281
|
+
if (parseOutboundMessage(event.data)) {
|
|
282
|
+
devWarn(
|
|
283
|
+
`[captello-sdk] Ignored a Captello message from origin "${event.origin}" (expected "${this.targetOrigin}"). Check the targetOrigin you passed.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (this.matchSource) {
|
|
289
|
+
const expected = this.frame.contentWindow;
|
|
290
|
+
if (expected && event.source !== expected) {
|
|
291
|
+
if (parseOutboundMessage(event.data)) {
|
|
292
|
+
devWarn(
|
|
293
|
+
"[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."
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const message = parseOutboundMessage(event.data);
|
|
300
|
+
if (!message) return;
|
|
301
|
+
if (message.type === "form_load_complete" /* FormLoadComplete */) {
|
|
302
|
+
this.markReadyAndFlush();
|
|
303
|
+
}
|
|
304
|
+
const set = this.listeners.get(message.type);
|
|
305
|
+
if (set) {
|
|
306
|
+
for (const listener of [...set]) listener(message);
|
|
307
|
+
}
|
|
308
|
+
if (this.anyListeners.size) {
|
|
309
|
+
for (const listener of [...this.anyListeners]) listener(message);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/promises.ts
|
|
315
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
316
|
+
var MessageTimeoutError = class extends Error {
|
|
317
|
+
constructor(messageType, timeoutMs) {
|
|
318
|
+
super(`Timed out after ${timeoutMs}ms waiting for "${messageType}" from the Captello webview.`);
|
|
319
|
+
this.messageType = messageType;
|
|
320
|
+
this.timeoutMs = timeoutMs;
|
|
321
|
+
this.name = "MessageTimeoutError";
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
function clientOptions(options) {
|
|
325
|
+
return {
|
|
326
|
+
targetOrigin: options.targetOrigin,
|
|
327
|
+
hostWindow: options.hostWindow,
|
|
328
|
+
matchSource: options.matchSource,
|
|
329
|
+
// One-shot helpers act on a form assumed already loaded; never buffer their
|
|
330
|
+
// sends waiting for a form_load_complete that may have already fired.
|
|
331
|
+
queueUntilReady: false
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function waitForMessage(frame, type, options = {}) {
|
|
335
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
336
|
+
return new Promise((resolve, reject) => {
|
|
337
|
+
const client = new CaptelloWebview(frame, clientOptions(options));
|
|
338
|
+
let timer;
|
|
339
|
+
const settle = (fn) => {
|
|
340
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
341
|
+
client.destroy();
|
|
342
|
+
fn();
|
|
343
|
+
};
|
|
344
|
+
client.once(type, (message) => settle(() => resolve(message)));
|
|
345
|
+
if (timeoutMs > 0 && timeoutMs !== Infinity) {
|
|
346
|
+
timer = setTimeout(() => settle(() => reject(new MessageTimeoutError(type, timeoutMs))), timeoutMs);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function waitForFormLoad(frame, options = {}) {
|
|
351
|
+
return waitForMessage(frame, "form_load_complete" /* FormLoadComplete */, options).then(() => void 0);
|
|
352
|
+
}
|
|
353
|
+
function submitForm(frame, options = {}) {
|
|
354
|
+
const client = new CaptelloWebview(frame, clientOptions(options));
|
|
355
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
356
|
+
return client.submitAndWait(timeoutMs).finally(() => client.destroy());
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
exports.MessageTimeoutError = MessageTimeoutError;
|
|
360
|
+
exports.SubmissionError = SubmissionError;
|
|
361
|
+
exports.SubmissionTimeoutError = SubmissionTimeoutError;
|
|
362
|
+
exports.submitForm = submitForm;
|
|
363
|
+
exports.waitForFormLoad = waitForFormLoad;
|
|
364
|
+
exports.waitForMessage = waitForMessage;
|
|
365
|
+
//# sourceMappingURL=promises.cjs.map
|
|
366
|
+
//# sourceMappingURL=promises.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/client.ts","../src/promises.ts"],"names":["OutboundMessageType"],"mappings":"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","file":"promises.cjs","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 (used in kiosk / quick-capture flows). */\n FormSubmitSuccess = \"form_submit_success\",\n /** Connexions: the host should perform the profile redirect (embed mode). */\n ConnexionsProfileRedirect = \"connexions_profile_redirect\",\n /** Connexions: the host should trigger the vCard download (embed mode). */\n ConnexionsDownloadVcard = \"connexions_download_vcard\",\n}\n\n/**\n * Opaque submission payload carried by {@link OutboundMessageType.SubmissionBody}.\n *\n * This mirrors the webview's internal `FormSubmission` model. It is intentionally\n * typed as an open record here so the SDK stays decoupled from the app's full model\n * graph; the documented fields below are stable, the rest are passed through as-is.\n * Host code that needs the deep element-value types should treat `data` as untyped\n * and key it by element id (e.g. `\"element_12\"`, `\"element_12_3\"`).\n */\nexport interface SubmissionBody {\n id: number;\n form_id: number;\n prospect_id: number;\n email: string;\n first_name: string;\n last_name: string;\n full_name: string;\n company: string;\n phone: string;\n /** Submitted values keyed by element id / sub-element id. */\n data: Record<string, unknown>;\n /**\n * Visible, filled elements ready to render as key/value rows — one item per\n * element, discriminated by `element_type`. Use {@link parseVisibleSubmissionsData}\n * or {@link toDisplayPairs} to consume it. May be absent on older webview builds.\n */\n visible_submissions_data?: VisibleSubmissionDataItem[];\n submission_date: string;\n /** Query-string params the webview was loaded with, echoed back on submit. */\n query_parameters?: Record<string, string>;\n /** Additional fields from the webview's submission model are passed through verbatim. */\n [key: string]: unknown;\n}\n\n/**\n * Loose submission shape accepted when **pre-filling** the form (host → webview).\n *\n * Distinct from {@link SubmissionBody}: a received `submission_body` is always fully\n * populated, but when pre-filling you typically either round-trip a previously-received\n * body or pass a partial object assembled from your own data. A {@link SubmissionBody}\n * is assignable to this, so round-tripping just works.\n */\nexport interface SubmissionPrefill {\n /** Submitted values keyed by element id / sub-element id. */\n data?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface FormLoadCompleteMessage {\n type: OutboundMessageType.FormLoadComplete;\n}\ninterface FormSubmitSuccessMessage {\n type: OutboundMessageType.FormSubmitSuccess;\n}\ninterface FormErrorMessageMessage {\n type: OutboundMessageType.FormErrorMessage;\n /** Translated, display-ready error text. */\n data: string;\n}\ninterface SubmissionBodyMessage {\n type: OutboundMessageType.SubmissionBody;\n data: SubmissionBody;\n}\ninterface ConnexionsProfileRedirectMessage {\n type: OutboundMessageType.ConnexionsProfileRedirect;\n}\ninterface ConnexionsDownloadVcardMessage {\n type: OutboundMessageType.ConnexionsDownloadVcard;\n}\n\n/** Discriminated union of every message the webview can emit to its host. */\nexport type OutboundMessage =\n | FormLoadCompleteMessage\n | FormSubmitSuccessMessage\n | FormErrorMessageMessage\n | SubmissionBodyMessage\n | ConnexionsProfileRedirectMessage\n | ConnexionsDownloadVcardMessage;\n\n/** Maps each outbound `type` to its full message shape (used by the client's `.on`). */\nexport type OutboundMessageMap = {\n [M in OutboundMessage as M[\"type\"]]: M;\n};\n\n/* ------------------------------------------------------------------ *\n * Inbound: host → webview\n * ------------------------------------------------------------------ */\n\n/** Message `type` values the host sends into the webview. */\nexport enum InboundMessageType {\n /** Programmatically trigger form submission (as if the user pressed submit). */\n Submit = \"submit_form\",\n /** Reset the form, clearing all entered values. */\n Reset = \"reset_form\",\n /** Pre-fill the form with existing data. See {@link PrefillDataType}. */\n FormPrefill = \"form_prefill\",\n /** Switch the current submission into draft-update mode. */\n UpdateDraft = \"update_draft\",\n /** Run validation against a target field (or the whole form). */\n TriggerValidation = \"trigger_validation\",\n}\n\n/** Shape selector for {@link InboundMessageType.FormPrefill} payloads. */\nexport enum PrefillDataType {\n /** `data` is a full ULC submission (the webview's submission model). */\n UlcSubmission = \"ulc_submission\",\n /** `data` is a list of transcription field/value items. */\n Info = \"info\",\n /** `data` is `{ submission, info }` — a submission plus transcription items. */\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 used by {@link PrefillDataType.Info}.\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 PrefillSubmissionMessage {\n type: InboundMessageType.FormPrefill;\n data_type: PrefillDataType.UlcSubmission;\n /** A submission body to pre-fill from (a received {@link SubmissionBody} or a partial). */\n data: SubmissionPrefill;\n}\ninterface PrefillInfoMessage {\n type: InboundMessageType.FormPrefill;\n data_type: PrefillDataType.Info;\n data: PrefillInfoItem[];\n}\ninterface PrefillSubmissionAndInfoMessage {\n type: InboundMessageType.FormPrefill;\n data_type: PrefillDataType.UlcSubmissionAndInfo;\n data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] };\n}\n\n/** Discriminated union of every message the host can send into the webview. */\nexport type InboundMessage =\n | SubmitMessage\n | ResetMessage\n | UpdateDraftMessage\n | TriggerValidationMessage\n | PrefillSubmissionMessage\n | PrefillInfoMessage\n | PrefillSubmissionAndInfoMessage;\n\n/* ------------------------------------------------------------------ *\n * Runtime guards / parsing\n * ------------------------------------------------------------------ */\n\nconst OUTBOUND_TYPES: ReadonlySet<string> = new Set(Object.values(OutboundMessageType));\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parses a raw `MessageEvent.data` value into a typed {@link OutboundMessage}, or\n * returns `null` if it is not a recognized Captello webview message.\n *\n * Accepts either a JSON string (the webview always sends strings) or an\n * already-parsed object, so it is robust to hosts/proxies that pre-parse.\n */\nexport function parseOutboundMessage(data: unknown): OutboundMessage | null {\n let value: unknown = data;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n return null;\n }\n }\n if (!isPlainObject(value)) return null;\n if (typeof value[\"type\"] !== \"string\" || !OUTBOUND_TYPES.has(value[\"type\"])) return null;\n return value as unknown as OutboundMessage;\n}\n","import { InboundMessageType, OutboundMessageType, parseOutboundMessage, PrefillDataType } from \"./messages\";\nimport type {\n InboundMessage,\n OutboundMessage,\n OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/** Listener for a specific outbound message type. */\nexport type OutboundListener<T extends OutboundMessageType> = (message: OutboundMessageMap[T]) => void;\n\n/** Listener for every outbound message (used by {@link CaptelloWebview.onAny}). */\nexport type AnyOutboundListener = (message: OutboundMessage) => void;\n\n/** Unsubscribe handle returned by every `on*` method. Calling it removes the listener. */\nexport type Unsubscribe = () => void;\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when the webview reports\n * a `form_error_message`. `message` is the translated, display-ready text.\n */\nexport class SubmissionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SubmissionError\";\n }\n}\n\n/**\n * Rejection reason from {@link CaptelloWebview.submitAndWait} when no `submission_body`\n * or `form_error_message` arrives within the timeout.\n */\nexport class SubmissionTimeoutError extends Error {\n constructor(public readonly timeoutMs: number) {\n super(`Captello webview did not respond to submit within ${timeoutMs}ms.`);\n this.name = \"SubmissionTimeoutError\";\n }\n}\n\nexport interface CaptelloWebviewOptions {\n /**\n * Origin to validate incoming messages against and to target outgoing messages.\n * Strongly recommended — set it to the webview's origin (e.g.\n * `\"https://capture.captello.com\"`). Use {@link targetOriginFromUrl} to derive it\n * from the embed URL.\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 `prefillInfo(...)` right after mount would\n * otherwise post to a form that isn't listening yet and be silently dropped.\n *\n * Set `false` to send immediately (the legacy behavior). Note: a client that\n * attaches *after* the form already loaded will not have seen `form_load_complete`,\n * so its queued messages won't flush — create the client with the iframe.\n */\n queueUntilReady?: boolean;\n}\n\n/** Emits a console warning in development builds only. No-op in production / no bundler. */\nfunction devWarn(message: string): void {\n try {\n if (typeof process !== \"undefined\" && process.env && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(message);\n }\n } catch {\n /* `process` not defined (pure browser, no bundler define) → stay silent */\n }\n}\n\ntype ElementOrFrame = HTMLIFrameElement | { contentWindow: Window | null };\n\n/**\n * Host-side controller for an embedded Captello capture webview.\n *\n * Wraps a single `<iframe>` and encodes the full message protocol:\n * - **Receiving** (webview → host): subscribe with {@link on} / {@link onAny}.\n * - **Sending** (host → webview): use {@link submit}, {@link reset}, {@link prefill},\n * {@link triggerValidation}, {@link updateDraft}, or the lower-level {@link send}.\n *\n * Wire details handled for you: outgoing messages are `JSON.stringify`'d (the webview\n * parses inbound data with `JSON.parse`, so a raw object would be ignored), and\n * incoming messages are validated by origin + source before being parsed.\n *\n * @example\n * ```ts\n * const iframe = document.querySelector(\"iframe\")!;\n * const webview = new CaptelloWebview(iframe, {\n * targetOrigin: \"https://capture.captello.com\",\n * });\n *\n * webview.on(OutboundMessageType.FormLoadComplete, () => console.log(\"ready\"));\n * webview.on(OutboundMessageType.SubmissionBody, (msg) => save(msg.data));\n *\n * // later, drive the form:\n * webview.submit();\n *\n * // on teardown:\n * webview.destroy();\n * ```\n */\nexport class CaptelloWebview {\n private readonly frame: ElementOrFrame;\n private readonly targetOrigin: string;\n private readonly hostWindow: Window;\n private readonly matchSource: boolean;\n\n private readonly listeners = new Map<OutboundMessageType, Set<OutboundListener<OutboundMessageType>>>();\n private readonly anyListeners = new Set<AnyOutboundListener>();\n private readonly boundHandler: (event: MessageEvent) => void;\n private destroyed = false;\n\n private readonly queueUntilReady: boolean;\n /** True once `form_load_complete` has been observed. */\n private ready = false;\n /** Messages sent before ready, flushed in order on load. */\n private readonly outbox: InboundMessage[] = [];\n\n constructor(frame: ElementOrFrame, options: CaptelloWebviewOptions = {}) {\n if (!frame) {\n throw new Error(\"CaptelloWebview: an iframe element (or { contentWindow }) is required.\");\n }\n this.frame = frame;\n this.targetOrigin = options.targetOrigin ?? \"*\";\n this.matchSource = options.matchSource ?? true;\n this.queueUntilReady = options.queueUntilReady ?? true;\n\n const hostWindow = options.hostWindow ?? (typeof window !== \"undefined\" ? window : undefined);\n if (!hostWindow) {\n throw new Error(\n \"CaptelloWebview: no host window available. Pass `hostWindow` when constructing outside a browser.\",\n );\n }\n this.hostWindow = hostWindow;\n\n this.boundHandler = (event: MessageEvent) => this.handleMessage(event);\n this.hostWindow.addEventListener(\"message\", this.boundHandler);\n }\n\n /** `true` once the webview has reported `form_load_complete`. */\n get isReady(): boolean {\n return this.ready;\n }\n\n /* -------------------------------------------------------------- *\n * Receiving (webview → host)\n * -------------------------------------------------------------- */\n\n /**\n * Subscribe to a single outbound message type. Returns an unsubscribe function.\n *\n * @example webview.on(OutboundMessageType.FormErrorMessage, (m) => toast(m.data));\n */\n on<T extends OutboundMessageType>(type: T, listener: OutboundListener<T>): Unsubscribe {\n let set = this.listeners.get(type);\n if (!set) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener as OutboundListener<OutboundMessageType>);\n return () => {\n set?.delete(listener as OutboundListener<OutboundMessageType>);\n };\n }\n\n /**\n * Subscribe once: the listener is removed automatically after it fires the first\n * time for `type`. Returns an unsubscribe function for cancelling early.\n */\n once<T extends OutboundMessageType>(type: T, listener: OutboundListener<T>): Unsubscribe {\n const off = this.on(type, (message) => {\n off();\n listener(message);\n });\n return off;\n }\n\n /** Subscribe to every outbound message regardless of type. Returns an unsubscribe function. */\n onAny(listener: AnyOutboundListener): Unsubscribe {\n this.anyListeners.add(listener);\n return () => {\n this.anyListeners.delete(listener);\n };\n }\n\n /* -------------------------------------------------------------- *\n * Sending (host → webview)\n * -------------------------------------------------------------- */\n\n /**\n * Low-level send: posts any inbound message to the webview as a JSON string.\n * Prefer the typed helpers below; use this only for forward-compatibility.\n *\n * When `queueUntilReady` is enabled (the default) and the form hasn't reported\n * `form_load_complete` yet, the message is buffered and flushed on load instead of\n * posted immediately.\n *\n * @throws if the iframe's `contentWindow` is not available (not yet loaded /\n * detached) and the message can't be queued.\n */\n send(message: InboundMessage): void {\n if (this.destroyed) {\n throw new Error(\"CaptelloWebview: cannot send after destroy().\");\n }\n if (this.queueUntilReady && !this.ready) {\n this.outbox.push(message);\n return;\n }\n this.postNow(message);\n }\n\n /** Posts a message immediately, bypassing the ready-queue. */\n private postNow(message: InboundMessage): void {\n const target = this.frame.contentWindow;\n if (!target) {\n throw new Error(\n \"CaptelloWebview: iframe.contentWindow is null. Wait for the iframe to load before sending.\",\n );\n }\n // The webview reads inbound data with JSON.parse(event.data), so it must be a string.\n target.postMessage(JSON.stringify(message), this.targetOrigin);\n }\n\n /** Marks the client ready and flushes any queued messages, in order. */\n private markReadyAndFlush(): void {\n if (this.ready) return;\n this.ready = true;\n const queued = this.outbox.splice(0);\n for (const message of queued) {\n try {\n this.postNow(message);\n } catch {\n /* iframe detached between load and flush — drop silently */\n }\n }\n }\n\n /** Programmatically submit the form (fire-and-forget). */\n submit(): void {\n this.send({ type: InboundMessageType.Submit });\n }\n\n /**\n * Submit the form and await the outcome.\n *\n * Sends `submit_form`, then resolves with the {@link SubmissionBody} when the\n * webview emits `submission_body`, or rejects with a {@link SubmissionError}\n * (carrying the translated message) when it emits `form_error_message`. Rejects\n * with a {@link SubmissionTimeoutError} if neither arrives within `timeoutMs`.\n *\n * This is the typed, leak-free version of the common \"click submit, wait for the\n * result\" flow — listeners are always cleaned up, including on timeout.\n *\n * @param timeoutMs how long to wait before giving up. Defaults to 60_000.\n * @example\n * try {\n * const body = await webview.submitAndWait();\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\n submitAndWait(timeoutMs = 60_000): Promise<SubmissionBody> {\n return new Promise<SubmissionBody>((resolve, reject) => {\n let settled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const cleanup = () => {\n settled = true;\n offSuccess();\n offError();\n if (timer !== undefined) clearTimeout(timer);\n };\n\n const offSuccess = this.on(OutboundMessageType.SubmissionBody, (message) => {\n if (settled) return;\n cleanup();\n resolve(message.data);\n });\n const offError = this.on(OutboundMessageType.FormErrorMessage, (message) => {\n if (settled) return;\n cleanup();\n reject(new SubmissionError(message.data));\n });\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => {\n if (settled) return;\n cleanup();\n reject(new SubmissionTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n\n try {\n this.send({ type: InboundMessageType.Submit });\n } catch (err) {\n if (!settled) {\n cleanup();\n reject(err);\n }\n }\n });\n }\n\n /** Reset the form, clearing all entered values. */\n reset(): void {\n this.send({ type: InboundMessageType.Reset });\n }\n\n /** Switch the current submission into draft-update mode. */\n updateDraft(): void {\n this.send({ type: InboundMessageType.UpdateDraft });\n }\n\n /** Run validation against a target field, or `\"all\"` for the whole form. */\n triggerValidation(target: ValidationTarget): void {\n this.send({ type: InboundMessageType.TriggerValidation, target });\n }\n\n /** Pre-fill the form from a submission body (a received body or a partial). */\n prefillSubmission(submission: SubmissionPrefill): void {\n this.send({\n type: InboundMessageType.FormPrefill,\n data_type: PrefillDataType.UlcSubmission,\n data: submission,\n });\n }\n\n /** Pre-fill the form from a list of transcription field/value items. */\n prefillInfo(info: PrefillInfoItem[]): void {\n this.send({\n type: InboundMessageType.FormPrefill,\n data_type: PrefillDataType.Info,\n data: info,\n });\n }\n\n /** Pre-fill the form from a submission plus transcription items. */\n prefillSubmissionAndInfo(data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }): void {\n this.send({\n type: InboundMessageType.FormPrefill,\n data_type: PrefillDataType.UlcSubmissionAndInfo,\n data,\n });\n }\n\n /* -------------------------------------------------------------- *\n * Lifecycle\n * -------------------------------------------------------------- */\n\n /** Remove the `message` listener and drop all subscriptions. Idempotent. */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.hostWindow.removeEventListener(\"message\", this.boundHandler);\n this.listeners.clear();\n this.anyListeners.clear();\n this.outbox.length = 0;\n }\n\n /* -------------------------------------------------------------- *\n * Internals\n * -------------------------------------------------------------- */\n\n private handleMessage(event: MessageEvent): void {\n if (this.destroyed) return;\n\n // Origin check: skip when targetOrigin is the wildcard. Warn (dev only) if a\n // message that *looks* like ours is dropped on origin — a common \"why isn't my\n // listener firing?\" cause.\n if (this.targetOrigin !== \"*\" && event.origin !== this.targetOrigin) {\n if (parseOutboundMessage(event.data)) {\n devWarn(\n `[captello-sdk] Ignored a Captello message from origin \"${event.origin}\" ` +\n `(expected \"${this.targetOrigin}\"). Check the targetOrigin you passed.`,\n );\n }\n return;\n }\n\n // Source check: only accept messages from the bound iframe's window.\n if (this.matchSource) {\n const expected = this.frame.contentWindow;\n if (expected && event.source !== expected) {\n if (parseOutboundMessage(event.data)) {\n devWarn(\n \"[captello-sdk] Ignored a Captello message from an unexpected source window \" +\n \"(not the bound iframe). If the webview relays through another window, set matchSource: false.\",\n );\n }\n return;\n }\n }\n\n const message = parseOutboundMessage(event.data);\n if (!message) return;\n\n // Flip to ready (and flush queued sends) the moment the form loads, before\n // dispatching to listeners — so a listener can send and have it post immediately.\n if (message.type === OutboundMessageType.FormLoadComplete) {\n this.markReadyAndFlush();\n }\n\n const set = this.listeners.get(message.type);\n if (set) {\n // Copy to a snapshot so a listener that unsubscribes mid-dispatch is safe.\n for (const listener of [...set]) listener(message);\n }\n if (this.anyListeners.size) {\n for (const listener of [...this.anyListeners]) listener(message);\n }\n }\n}\n","/**\n * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.\n *\n * Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are\n * fire-once-and-await utilities that take an iframe directly: create a short-lived\n * client internally, wait for the relevant message, then tear it down. Ideal for\n * `await`-style code (e.g. \"submit and get the body\", \"wait until the form loads\").\n *\n * @example\n * import { submitForm, waitForFormLoad } from \"@captello/ulc-webview-sdk/promises\";\n *\n * await waitForFormLoad(iframe, { targetOrigin });\n * const body = await submitForm(iframe, { targetOrigin });\n */\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions } from \"./client\";\nimport { OutboundMessageType } from \"./messages\";\nimport type { OutboundMessageMap, SubmissionBody } from \"./messages\";\n\nexport { SubmissionError, SubmissionTimeoutError } from \"./client\";\n\n/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */\ntype ElementOrFrame = HTMLIFrameElement | { contentWindow: Window | null };\n\n/** Options shared by every promise helper. */\nexport interface WaitOptions extends Pick<CaptelloWebviewOptions, \"targetOrigin\" | \"hostWindow\" | \"matchSource\"> {\n /**\n * How long to wait before rejecting. Defaults to 60_000ms. Pass `0` or `Infinity`\n * to wait indefinitely (the caller is then responsible for not leaking the wait).\n */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/** Rejection reason from {@link waitForMessage} / {@link waitForFormLoad} on timeout. */\nexport class MessageTimeoutError extends Error {\n constructor(\n public readonly messageType: string,\n public readonly timeoutMs: number,\n ) {\n super(`Timed out after ${timeoutMs}ms waiting for \"${messageType}\" from the Captello webview.`);\n this.name = \"MessageTimeoutError\";\n }\n}\n\nfunction clientOptions(options: WaitOptions): CaptelloWebviewOptions {\n return {\n targetOrigin: options.targetOrigin,\n hostWindow: options.hostWindow,\n matchSource: options.matchSource,\n // One-shot helpers act on a form assumed already loaded; never buffer their\n // sends waiting for a form_load_complete that may have already fired.\n queueUntilReady: false,\n };\n}\n\n/**\n * Resolves with the next outbound message of `type` from the webview, or rejects with\n * a {@link MessageTimeoutError} if none arrives within the timeout. The internal\n * listener is always removed before settling.\n *\n * @example const msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });\n */\nexport function waitForMessage<T extends OutboundMessageType>(\n frame: ElementOrFrame,\n type: T,\n options: WaitOptions = {},\n): Promise<OutboundMessageMap[T]> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise<OutboundMessageMap[T]>((resolve, reject) => {\n const client = new CaptelloWebview(frame, clientOptions(options));\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = (fn: () => void) => {\n if (timer !== undefined) clearTimeout(timer);\n client.destroy();\n fn();\n };\n\n client.once(type, (message) => settle(() => resolve(message)));\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => settle(() => reject(new MessageTimeoutError(type, timeoutMs))), timeoutMs);\n }\n });\n}\n\n/**\n * Resolves once the webview reports `form_load_complete`, or rejects with a\n * {@link MessageTimeoutError} on timeout.\n *\n * Note: this only catches a *future* load event. If the form may have already loaded\n * before you call this (e.g. you attach late), prefer subscribing with a long-lived\n * {@link CaptelloWebview} created before the iframe navigates.\n */\nexport function waitForFormLoad(frame: ElementOrFrame, options: WaitOptions = {}): Promise<void> {\n return waitForMessage(frame, OutboundMessageType.FormLoadComplete, options).then(() => undefined);\n}\n\n/**\n * Submits the form and awaits the outcome: resolves with the {@link SubmissionBody} on\n * `submission_body`, rejects with a `SubmissionError` (translated message) on\n * `form_error_message`, or a `SubmissionTimeoutError` if neither arrives in time.\n *\n * Standalone equivalent of {@link CaptelloWebview.submitAndWait} for code that doesn't\n * hold a long-lived client — it creates one, submits, and tears it down.\n *\n * @example\n * try {\n * const body = await submitForm(iframeRef.current!, { targetOrigin });\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\nexport function submitForm(frame: ElementOrFrame, options: WaitOptions = {}): Promise<SubmissionBody> {\n const client = new CaptelloWebview(frame, clientOptions(options));\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return client.submitAndWait(timeoutMs).finally(() => client.destroy());\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-3IBxKbIE.cjs';
|
|
2
|
+
export { b as SubmissionError, c as SubmissionTimeoutError } from './client-3IBxKbIE.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.
|
|
6
|
+
*
|
|
7
|
+
* Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are
|
|
8
|
+
* fire-once-and-await utilities that take an iframe directly: create a short-lived
|
|
9
|
+
* client internally, wait for the relevant message, then tear it down. Ideal for
|
|
10
|
+
* `await`-style code (e.g. "submit and get the body", "wait until the form loads").
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import { submitForm, waitForFormLoad } from "@captello/ulc-webview-sdk/promises";
|
|
14
|
+
*
|
|
15
|
+
* await waitForFormLoad(iframe, { targetOrigin });
|
|
16
|
+
* const body = await submitForm(iframe, { targetOrigin });
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */
|
|
20
|
+
type ElementOrFrame = HTMLIFrameElement | {
|
|
21
|
+
contentWindow: Window | null;
|
|
22
|
+
};
|
|
23
|
+
/** Options shared by every promise helper. */
|
|
24
|
+
interface WaitOptions extends Pick<CaptelloWebviewOptions, "targetOrigin" | "hostWindow" | "matchSource"> {
|
|
25
|
+
/**
|
|
26
|
+
* How long to wait before rejecting. Defaults to 60_000ms. Pass `0` or `Infinity`
|
|
27
|
+
* to wait indefinitely (the caller is then responsible for not leaking the wait).
|
|
28
|
+
*/
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Rejection reason from {@link waitForMessage} / {@link waitForFormLoad} on timeout. */
|
|
32
|
+
declare class MessageTimeoutError extends Error {
|
|
33
|
+
readonly messageType: string;
|
|
34
|
+
readonly timeoutMs: number;
|
|
35
|
+
constructor(messageType: string, timeoutMs: number);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolves with the next outbound message of `type` from the webview, or rejects with
|
|
39
|
+
* a {@link MessageTimeoutError} if none arrives within the timeout. The internal
|
|
40
|
+
* listener is always removed before settling.
|
|
41
|
+
*
|
|
42
|
+
* @example const msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });
|
|
43
|
+
*/
|
|
44
|
+
declare function waitForMessage<T extends OutboundMessageType>(frame: ElementOrFrame, type: T, options?: WaitOptions): Promise<OutboundMessageMap[T]>;
|
|
45
|
+
/**
|
|
46
|
+
* Resolves once the webview reports `form_load_complete`, or rejects with a
|
|
47
|
+
* {@link MessageTimeoutError} on timeout.
|
|
48
|
+
*
|
|
49
|
+
* Note: this only catches a *future* load event. If the form may have already loaded
|
|
50
|
+
* before you call this (e.g. you attach late), prefer subscribing with a long-lived
|
|
51
|
+
* {@link CaptelloWebview} created before the iframe navigates.
|
|
52
|
+
*/
|
|
53
|
+
declare function waitForFormLoad(frame: ElementOrFrame, options?: WaitOptions): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Submits the form and awaits the outcome: resolves with the {@link SubmissionBody} on
|
|
56
|
+
* `submission_body`, rejects with a `SubmissionError` (translated message) on
|
|
57
|
+
* `form_error_message`, or a `SubmissionTimeoutError` if neither arrives in time.
|
|
58
|
+
*
|
|
59
|
+
* Standalone equivalent of {@link CaptelloWebview.submitAndWait} for code that doesn't
|
|
60
|
+
* hold a long-lived client — it creates one, submits, and tears it down.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* try {
|
|
64
|
+
* const body = await submitForm(iframeRef.current!, { targetOrigin });
|
|
65
|
+
* await persist(body);
|
|
66
|
+
* } catch (err) {
|
|
67
|
+
* if (err instanceof SubmissionError) showToast(err.message);
|
|
68
|
+
* }
|
|
69
|
+
*/
|
|
70
|
+
declare function submitForm(frame: ElementOrFrame, options?: WaitOptions): Promise<SubmissionBody>;
|
|
71
|
+
|
|
72
|
+
export { MessageTimeoutError, type WaitOptions, submitForm, waitForFormLoad, waitForMessage };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-3IBxKbIE.js';
|
|
2
|
+
export { b as SubmissionError, c as SubmissionTimeoutError } from './client-3IBxKbIE.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.
|
|
6
|
+
*
|
|
7
|
+
* Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are
|
|
8
|
+
* fire-once-and-await utilities that take an iframe directly: create a short-lived
|
|
9
|
+
* client internally, wait for the relevant message, then tear it down. Ideal for
|
|
10
|
+
* `await`-style code (e.g. "submit and get the body", "wait until the form loads").
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import { submitForm, waitForFormLoad } from "@captello/ulc-webview-sdk/promises";
|
|
14
|
+
*
|
|
15
|
+
* await waitForFormLoad(iframe, { targetOrigin });
|
|
16
|
+
* const body = await submitForm(iframe, { targetOrigin });
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */
|
|
20
|
+
type ElementOrFrame = HTMLIFrameElement | {
|
|
21
|
+
contentWindow: Window | null;
|
|
22
|
+
};
|
|
23
|
+
/** Options shared by every promise helper. */
|
|
24
|
+
interface WaitOptions extends Pick<CaptelloWebviewOptions, "targetOrigin" | "hostWindow" | "matchSource"> {
|
|
25
|
+
/**
|
|
26
|
+
* How long to wait before rejecting. Defaults to 60_000ms. Pass `0` or `Infinity`
|
|
27
|
+
* to wait indefinitely (the caller is then responsible for not leaking the wait).
|
|
28
|
+
*/
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Rejection reason from {@link waitForMessage} / {@link waitForFormLoad} on timeout. */
|
|
32
|
+
declare class MessageTimeoutError extends Error {
|
|
33
|
+
readonly messageType: string;
|
|
34
|
+
readonly timeoutMs: number;
|
|
35
|
+
constructor(messageType: string, timeoutMs: number);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolves with the next outbound message of `type` from the webview, or rejects with
|
|
39
|
+
* a {@link MessageTimeoutError} if none arrives within the timeout. The internal
|
|
40
|
+
* listener is always removed before settling.
|
|
41
|
+
*
|
|
42
|
+
* @example const msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });
|
|
43
|
+
*/
|
|
44
|
+
declare function waitForMessage<T extends OutboundMessageType>(frame: ElementOrFrame, type: T, options?: WaitOptions): Promise<OutboundMessageMap[T]>;
|
|
45
|
+
/**
|
|
46
|
+
* Resolves once the webview reports `form_load_complete`, or rejects with a
|
|
47
|
+
* {@link MessageTimeoutError} on timeout.
|
|
48
|
+
*
|
|
49
|
+
* Note: this only catches a *future* load event. If the form may have already loaded
|
|
50
|
+
* before you call this (e.g. you attach late), prefer subscribing with a long-lived
|
|
51
|
+
* {@link CaptelloWebview} created before the iframe navigates.
|
|
52
|
+
*/
|
|
53
|
+
declare function waitForFormLoad(frame: ElementOrFrame, options?: WaitOptions): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Submits the form and awaits the outcome: resolves with the {@link SubmissionBody} on
|
|
56
|
+
* `submission_body`, rejects with a `SubmissionError` (translated message) on
|
|
57
|
+
* `form_error_message`, or a `SubmissionTimeoutError` if neither arrives in time.
|
|
58
|
+
*
|
|
59
|
+
* Standalone equivalent of {@link CaptelloWebview.submitAndWait} for code that doesn't
|
|
60
|
+
* hold a long-lived client — it creates one, submits, and tears it down.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* try {
|
|
64
|
+
* const body = await submitForm(iframeRef.current!, { targetOrigin });
|
|
65
|
+
* await persist(body);
|
|
66
|
+
* } catch (err) {
|
|
67
|
+
* if (err instanceof SubmissionError) showToast(err.message);
|
|
68
|
+
* }
|
|
69
|
+
*/
|
|
70
|
+
declare function submitForm(frame: ElementOrFrame, options?: WaitOptions): Promise<SubmissionBody>;
|
|
71
|
+
|
|
72
|
+
export { MessageTimeoutError, type WaitOptions, submitForm, waitForFormLoad, waitForMessage };
|
package/dist/promises.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CaptelloWebview } from './chunk-ZMYMZK2A.js';
|
|
2
|
+
export { SubmissionError, SubmissionTimeoutError } from './chunk-ZMYMZK2A.js';
|
|
3
|
+
|
|
4
|
+
// src/promises.ts
|
|
5
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
6
|
+
var MessageTimeoutError = class extends Error {
|
|
7
|
+
constructor(messageType, timeoutMs) {
|
|
8
|
+
super(`Timed out after ${timeoutMs}ms waiting for "${messageType}" from the Captello webview.`);
|
|
9
|
+
this.messageType = messageType;
|
|
10
|
+
this.timeoutMs = timeoutMs;
|
|
11
|
+
this.name = "MessageTimeoutError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function clientOptions(options) {
|
|
15
|
+
return {
|
|
16
|
+
targetOrigin: options.targetOrigin,
|
|
17
|
+
hostWindow: options.hostWindow,
|
|
18
|
+
matchSource: options.matchSource,
|
|
19
|
+
// One-shot helpers act on a form assumed already loaded; never buffer their
|
|
20
|
+
// sends waiting for a form_load_complete that may have already fired.
|
|
21
|
+
queueUntilReady: false
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function waitForMessage(frame, type, options = {}) {
|
|
25
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const client = new CaptelloWebview(frame, clientOptions(options));
|
|
28
|
+
let timer;
|
|
29
|
+
const settle = (fn) => {
|
|
30
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
31
|
+
client.destroy();
|
|
32
|
+
fn();
|
|
33
|
+
};
|
|
34
|
+
client.once(type, (message) => settle(() => resolve(message)));
|
|
35
|
+
if (timeoutMs > 0 && timeoutMs !== Infinity) {
|
|
36
|
+
timer = setTimeout(() => settle(() => reject(new MessageTimeoutError(type, timeoutMs))), timeoutMs);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function waitForFormLoad(frame, options = {}) {
|
|
41
|
+
return waitForMessage(frame, "form_load_complete" /* FormLoadComplete */, options).then(() => void 0);
|
|
42
|
+
}
|
|
43
|
+
function submitForm(frame, options = {}) {
|
|
44
|
+
const client = new CaptelloWebview(frame, clientOptions(options));
|
|
45
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
46
|
+
return client.submitAndWait(timeoutMs).finally(() => client.destroy());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { MessageTimeoutError, submitForm, waitForFormLoad, waitForMessage };
|
|
50
|
+
//# sourceMappingURL=promises.js.map
|
|
51
|
+
//# sourceMappingURL=promises.js.map
|