@captello/ulc-webview-sdk 1.1.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.
@@ -202,7 +202,14 @@ declare enum OutboundMessageType {
202
202
  /** Connexions: the host should perform the profile redirect (embed mode). */
203
203
  ConnexionsProfileRedirect = "connexions_profile_redirect",
204
204
  /** Connexions: the host should trigger the vCard download (embed mode). */
205
- ConnexionsDownloadVcard = "connexions_download_vcard"
205
+ ConnexionsDownloadVcard = "connexions_download_vcard",
206
+ /**
207
+ * The user pressed the transcribe button (shown only when the embed URL enables
208
+ * it via `show_transcribe_button`): the host should transcribe the request's
209
+ * scan images and answer with an inbound
210
+ * {@link InboundMessageType.TranscribeScannerResult}.
211
+ */
212
+ TranscribeScannerRequest = "transcribe_scanner_request"
206
213
  }
207
214
  /**
208
215
  * Opaque submission payload carried by {@link OutboundMessageType.SubmissionBody}.
@@ -306,8 +313,29 @@ interface ConnexionsProfileRedirectMessage {
306
313
  interface ConnexionsDownloadVcardMessage {
307
314
  type: OutboundMessageType.ConnexionsDownloadVcard;
308
315
  }
316
+ /**
317
+ * Webview → host: the user pressed the transcribe button. Answer with a
318
+ * {@link TranscribeScannerResultMessage} (see
319
+ * {@link CaptelloWebview.onTranscribeScannerRequest} for the handler that does this
320
+ * for you). The webview shows a pending state on the button and gives up with a
321
+ * user-facing error if no result arrives in time.
322
+ *
323
+ * Host processing is a convention, not a one-off: future operations get their own
324
+ * `*_request` / `*_result` message pairs shaped like this one.
325
+ */
326
+ interface TranscribeScannerRequestMessage {
327
+ type: OutboundMessageType.TranscribeScannerRequest;
328
+ /** Correlation id — echo it back verbatim in the result. */
329
+ request_id: string;
330
+ /** The scanner (badge/barcode) element whose scan should be transcribed (e.g. `"element_6"`). */
331
+ element_id: string;
332
+ /** Public URLs of the uploaded scan images (the scanner element's image value). */
333
+ image_urls: string[];
334
+ /** The draft submission token the webview was loaded with, when it has one. */
335
+ draft_submission_token?: string;
336
+ }
309
337
  /** Discriminated union of every message the webview can emit to its host. */
310
- type OutboundMessage = FormLoadCompleteMessage | FormSubmitSuccessMessage | FormErrorMessageMessage | SubmissionBodyMessage | ConnexionsProfileRedirectMessage | ConnexionsDownloadVcardMessage;
338
+ type OutboundMessage = FormLoadCompleteMessage | FormSubmitSuccessMessage | FormErrorMessageMessage | SubmissionBodyMessage | ConnexionsProfileRedirectMessage | ConnexionsDownloadVcardMessage | TranscribeScannerRequestMessage;
311
339
  /** Maps each outbound `type` to its full message shape (used by the client's `.on`). */
312
340
  type OutboundMessageMap = {
313
341
  [M in OutboundMessage as M["type"]]: M;
@@ -323,9 +351,18 @@ declare enum InboundMessageType {
323
351
  /** Switch the current submission into draft-update mode. */
324
352
  UpdateDraft = "update_draft",
325
353
  /** Run validation against a target field (or the whole form). */
326
- TriggerValidation = "trigger_validation"
354
+ TriggerValidation = "trigger_validation",
355
+ /** Answer to an outbound {@link OutboundMessageType.TranscribeScannerRequest}. */
356
+ TranscribeScannerResult = "transcribe_scanner_result"
327
357
  }
328
- /** Shape selector for {@link InboundMessageType.FormPrefill} payloads. */
358
+ /**
359
+ * Shape selector for {@link InboundMessageType.FormPrefill} payloads.
360
+ *
361
+ * Vestigial: the webview keys only off the message `type` and reads `data` directly —
362
+ * it never inspects `data_type`. Confirmed by mutation-testing the integration spec (a
363
+ * deliberately wrong value changed nothing). Kept because it is part of the shipped wire
364
+ * shape and older webview builds may still read it; `prefill()` sets it for you.
365
+ */
329
366
  declare enum PrefillDataType {
330
367
  UlcSubmissionAndInfo = "ulc_submission_and_info"
331
368
  }
@@ -365,8 +402,48 @@ interface PrefillMessage {
365
402
  info?: PrefillInfoItem[];
366
403
  };
367
404
  }
405
+ /**
406
+ * One transcribed field in {@link TranscribeScannerResultData.fields}.
407
+ *
408
+ * Semantically the same thing as a {@link PrefillInfoItem} but in the transcription
409
+ * service's camelCase wire casing — `llFieldIdentifier` carries the same values as
410
+ * `ll_field_unique_identifier` (e.g. `"FirstName"`, `"Email"`), and it is what the
411
+ * webview matches form elements by. The webview ignores fields with an empty `value`
412
+ * rather than blanking already-filled inputs.
413
+ */
414
+ interface TranscribedScannerField {
415
+ /** Catalog field identifier the webview matches on, e.g. `"FirstName"`. */
416
+ llFieldIdentifier: string;
417
+ /** Catalog field id — metadata, not used for matching. */
418
+ llFieldId?: number;
419
+ /** Display name as the transcription service reports it. */
420
+ llFieldNameName?: string;
421
+ value: string;
422
+ }
423
+ /** Success payload of a {@link TranscribeScannerResultMessage}. */
424
+ interface TranscribeScannerResultData {
425
+ fields: TranscribedScannerField[];
426
+ /** How the transcription was produced, e.g. `"ocr_transcription"`. */
427
+ submissionType?: string;
428
+ }
429
+ /**
430
+ * Host → webview: the answer to a {@link TranscribeScannerRequestMessage}. Send
431
+ * `data` with the transcribed fields on success, or `error` (display-ready text) on
432
+ * failure. `request_id` should echo the request's; the webview also accepts a result
433
+ * without one while exactly one request is pending, for hosts that answer strictly
434
+ * one at a time.
435
+ */
436
+ interface TranscribeScannerResultMessage {
437
+ type: InboundMessageType.TranscribeScannerResult;
438
+ /** The `request_id` of the request being answered, verbatim. */
439
+ request_id?: string;
440
+ /** Transcribed fields (success). */
441
+ data?: TranscribeScannerResultData;
442
+ /** Display-ready error text (failure) — leave `data` unset. */
443
+ error?: string;
444
+ }
368
445
  /** Discriminated union of every message the host can send into the webview. */
369
- type InboundMessage = SubmitMessage | ResetMessage | UpdateDraftMessage | TriggerValidationMessage | PrefillMessage;
446
+ type InboundMessage = SubmitMessage | ResetMessage | UpdateDraftMessage | TriggerValidationMessage | PrefillMessage | TranscribeScannerResultMessage;
370
447
  /**
371
448
  * Parses a raw `MessageEvent.data` value into a typed {@link OutboundMessage}, or
372
449
  * returns `null` if it is not a recognized Captello webview message.
@@ -380,8 +457,84 @@ declare function parseOutboundMessage(data: unknown): OutboundMessage | null;
380
457
  type OutboundListener<T extends OutboundMessageType> = (message: OutboundMessageMap[T]) => void;
381
458
  /** Listener for every outbound message (used by {@link CaptelloWebview.onAny}). */
382
459
  type AnyOutboundListener = (message: OutboundMessage) => void;
383
- /** Unsubscribe handle returned by every `on*` method. Calling it removes the listener. */
384
- type Unsubscribe = () => void;
460
+ /**
461
+ * Handler for {@link CaptelloWebview.onTranscribeScannerRequest}. Receives the full
462
+ * request (`element_id`, `image_urls`, `draft_submission_token`) plus a
463
+ * {@link TranscribeScannerReply}, and answers in either style:
464
+ *
465
+ * - **return** the transcribed fields (a promise is fine) — the promise-based style, or
466
+ * - **call `reply.resolve(...)` / `reply.reject(...)`** whenever the work finishes —
467
+ * for callback-style code that can't hand back a promise.
468
+ *
469
+ * Returning nothing hands ownership to `reply`, leaving the request open until it
470
+ * answers. A thrown error / rejection becomes an error result; if it's an `Error`, its
471
+ * `message` is shown to the user by the webview, so throw display-ready messages.
472
+ */
473
+ type TranscribeScannerRequestHandler = (request: TranscribeScannerRequestMessage, reply: TranscribeScannerReply) => TranscribeScannerResultData | void | Promise<TranscribeScannerResultData | void>;
474
+ /**
475
+ * The second argument handed to a {@link TranscribeScannerRequestHandler} — everything
476
+ * needed to answer one request, so the handler never has to reach back out to the
477
+ * client or touch `postMessage` itself.
478
+ *
479
+ * Use it when the transcription can't hand back a promise: a callback-style AJAX
480
+ * wrapper, an event bus, a method that returns `void` and finishes whenever it
481
+ * finishes. Pass it along to whatever does the work and let that code answer:
482
+ *
483
+ * ```js
484
+ * webview.onTranscribeScannerRequest((request, reply) => {
485
+ * transcribeService(request.image_urls,
486
+ * (fields) => reply.resolve({ fields, submissionType: "ocr_transcription" }),
487
+ * () => reply.reject("Transcription failed."),
488
+ * );
489
+ * });
490
+ * ```
491
+ *
492
+ * A handler that returns a value (or a promise of one) is answered from that return
493
+ * value and can ignore this entirely — the promise style is unchanged.
494
+ *
495
+ * The first answer wins: once `resolve`, `reject`, or the handler's own return value
496
+ * has answered, later calls are ignored rather than posting a second result.
497
+ */
498
+ interface TranscribeScannerReply {
499
+ /** Answer with the transcribed fields. */
500
+ resolve(data: TranscribeScannerResultData): void;
501
+ /**
502
+ * Fail the request. `error` is shown to the user by the webview verbatim, so pass
503
+ * display-ready text.
504
+ */
505
+ reject(error: string): void;
506
+ /** `true` once this request has been answered, by either path. */
507
+ readonly answered: boolean;
508
+ }
509
+ /**
510
+ * Identifies the transcribe request being answered by
511
+ * {@link CaptelloWebview.resolveTranscribeScannerRequest} /
512
+ * {@link CaptelloWebview.rejectTranscribeScannerRequest} — the request message itself,
513
+ * or its bare `request_id`.
514
+ *
515
+ * `null`/`undefined` sends an un-correlated result, which the webview accepts only
516
+ * while exactly one request is pending. Pass the request whenever you have it.
517
+ */
518
+ type TranscribeScannerRequestRef = TranscribeScannerRequestMessage | string | null | undefined;
519
+ /** The `unsubscribe()` side of a subscription handle. */
520
+ interface Subscription {
521
+ /** Remove the listener. Safe to call more than once. */
522
+ unsubscribe(): void;
523
+ }
524
+ /**
525
+ * Handle returned by every `on*` method. It is an object carrying
526
+ * {@link Subscription.unsubscribe}, and is also directly callable:
527
+ *
528
+ * ```ts
529
+ * const sub = webview.on(OutboundMessageType.SubmissionBody, save);
530
+ * sub.unsubscribe(); // preferred — reads clearly at the call site
531
+ * sub(); // equivalent
532
+ * ```
533
+ *
534
+ * Both do the same thing; the callable form keeps older `const off = ...; off()`
535
+ * code working.
536
+ */
537
+ type Unsubscribe = (() => void) & Subscription;
385
538
  /**
386
539
  * Rejection reason from {@link CaptelloWebview.submitAndWait} when the webview reports
387
540
  * a `form_error_message`. `message` is the translated, display-ready text.
@@ -401,7 +554,11 @@ interface CaptelloWebviewOptions {
401
554
  /**
402
555
  * Origin to validate incoming messages against and to target outgoing messages.
403
556
  * Strongly recommended — set it to the webview's origin (e.g.
404
- * `"https://capture.captello.com"`), e.g. `new URL(embedUrl).origin`.
557
+ * `"https://capture.captello.com"`).
558
+ *
559
+ * A full URL is accepted and reduced to its origin, so you can pass the embed URL
560
+ * or the webview base URL you already have on hand rather than deriving the origin
561
+ * yourself.
405
562
  *
406
563
  * Defaults to `"*"`, which accepts messages from any origin and posts without an
407
564
  * origin check. Only acceptable for trusted/local development.
@@ -429,8 +586,90 @@ interface CaptelloWebviewOptions {
429
586
  * so its queued messages won't flush — create the client with the iframe.
430
587
  */
431
588
  queueUntilReady?: boolean;
589
+ /**
590
+ * If `true` (default), the client calls {@link CaptelloWebview.destroy} on itself
591
+ * once the bound iframe is removed from the document — so a host that tears down a
592
+ * panel, modal, or route doesn't leak the `message` listener or have to remember a
593
+ * matching `destroy()`.
594
+ *
595
+ * Removal is detected with a `MutationObserver`, and an ancestor being removed
596
+ * counts: what matters is that the iframe is no longer connected to the document.
597
+ * Only ever fires after the iframe has been seen connected, so constructing against
598
+ * a not-yet-inserted element is not mistaken for a teardown.
599
+ *
600
+ * Requires a real DOM element and `MutationObserver`; with a `{ contentWindow }`
601
+ * stand-in, or in a non-DOM environment, there is nothing to observe and this is
602
+ * inert. Set `false` to manage the lifetime entirely yourself — e.g. when you
603
+ * deliberately detach and re-insert the same iframe element.
604
+ */
605
+ autoDestroy?: boolean;
606
+ /**
607
+ * If `true`, the client may learn its webview window from the **first** inbound
608
+ * message instead of being handed an iframe — see {@link attach}, which is the
609
+ * supported way to switch this on.
610
+ *
611
+ * Only ever adopts a sender that has already cleared the origin check and parsed as
612
+ * a well-formed Captello message, so with `targetOrigin` set to the webview's origin
613
+ * only the webview can be adopted. Every later message is source-matched against
614
+ * whatever was adopted, so this is as strict as an explicit iframe after the first
615
+ * message. Under the default `targetOrigin: "*"` that gate is absent — one more
616
+ * reason to set it in production.
617
+ *
618
+ * An explicitly passed iframe always wins; nothing is adopted while one resolves.
619
+ *
620
+ * @default false
621
+ */
622
+ adoptSource?: boolean;
432
623
  }
433
- type ElementOrFrame = HTMLIFrameElement | {
624
+ /** Options for {@link attach}. Same as the client's, minus the ones it manages itself. */
625
+ type AttachOptions = Omit<CaptelloWebviewOptions, "adoptSource">;
626
+ /**
627
+ * Attaches to the embedded Captello webview **without an iframe reference**.
628
+ *
629
+ * This is the drop-in replacement for a hand-rolled global
630
+ * `window.addEventListener("message", …)` dispatcher. Call it once, register your
631
+ * handlers, and you are done — there is nothing to query from the DOM, no ordering to
632
+ * get right relative to when the iframe is created, and no teardown to remember.
633
+ *
634
+ * ```js
635
+ * const webview = CaptelloSdk.attach({ targetOrigin: CAPTURE_PORTAL_WEB_VIEW_BASE });
636
+ *
637
+ * webview.on(CaptelloSdk.OutboundMessageType.FormErrorMessage, (m) => showError(m.data));
638
+ * webview.on(CaptelloSdk.OutboundMessageType.SubmissionBody, (m) => save(m.data));
639
+ * webview.onTranscribeScannerRequest((request, reply) => transcribe(request, reply));
640
+ * ```
641
+ *
642
+ * How it finds the form: the first inbound message that clears the origin check **and**
643
+ * parses as a well-formed Captello message identifies the webview. That sender becomes
644
+ * the target for sends and the window every later message is source-matched against —
645
+ * so after the first message this is as strict as passing the iframe. Set
646
+ * `targetOrigin` to the webview's origin and only the webview can ever be adopted.
647
+ *
648
+ * Teardown is still automatic: a removed iframe's window reports `closed`, which is
649
+ * readable cross-origin, so the client notices its form going away and destroys itself.
650
+ * Pass `autoDestroy: false` to own the lifetime yourself.
651
+ *
652
+ * The trade-off versus {@link CaptelloWebview}: until the form speaks once there is no
653
+ * window to post to, so an unprompted `submit()` / `prefill()` before then has nowhere
654
+ * to go. With the default `queueUntilReady` those sends are buffered and flushed on
655
+ * `form_load_complete`, which covers the normal case. If you drive the form
656
+ * unprompted from page load and can hold the element, prefer
657
+ * `new CaptelloWebview(iframe, …)`.
658
+ */
659
+ declare function attach(options?: AttachOptions): CaptelloWebview;
660
+ /**
661
+ * The webview's iframe — the element itself, or any object exposing its
662
+ * `contentWindow` (useful for test doubles).
663
+ *
664
+ * The caller is responsible for the iframe being mounted before constructing the
665
+ * client: messages are source-matched against its `contentWindow` from the very first
666
+ * one, and sends post to it directly.
667
+ *
668
+ * When a real DOM element is passed, the client also tears itself down automatically
669
+ * once that element leaves the document — see
670
+ * {@link CaptelloWebviewOptions.autoDestroy}.
671
+ */
672
+ type FrameLike = HTMLIFrameElement | {
434
673
  contentWindow: Window | null;
435
674
  };
436
675
  /**
@@ -464,6 +703,14 @@ type ElementOrFrame = HTMLIFrameElement | {
464
703
  */
465
704
  declare class CaptelloWebview {
466
705
  private readonly frame;
706
+ /** Watches for the iframe leaving the document — see `autoDestroy`. */
707
+ private frameObserver?;
708
+ /** The webview window learned from an inbound message — see `adoptSource`. */
709
+ private adoptedWindow;
710
+ private readonly adoptSource;
711
+ private readonly autoDestroy;
712
+ /** Watches an adopted window for `closed` — see `watchAdoptedWindow`. */
713
+ private adoptedPoll?;
467
714
  private readonly targetOrigin;
468
715
  private readonly hostWindow;
469
716
  private readonly matchSource;
@@ -476,7 +723,26 @@ declare class CaptelloWebview {
476
723
  private ready;
477
724
  /** Messages sent before ready, flushed in order on load. */
478
725
  private readonly outbox;
479
- constructor(frame: ElementOrFrame, options?: CaptelloWebviewOptions);
726
+ constructor(frame: FrameLike, options?: CaptelloWebviewOptions);
727
+ /**
728
+ * Tears the client down once the bound iframe leaves the document.
729
+ *
730
+ * Observes the whole document subtree because the iframe usually goes away with an
731
+ * ancestor (a panel or modal being emptied), which produces no mutation on the
732
+ * iframe's own parent. `isConnected` is the actual test, so removal at any depth
733
+ * counts; the observer only bothers checking when a mutation actually removed
734
+ * something.
735
+ */
736
+ /**
737
+ * Tears the client down once an adopted webview window goes away.
738
+ *
739
+ * With no iframe element there is nothing to observe in the DOM, but a removed
740
+ * iframe's `contentWindow` reports `closed === true` — and `closed` is readable
741
+ * cross-origin — so a cheap periodic check is enough. Verified in Chromium against a
742
+ * genuinely cross-origin iframe.
743
+ */
744
+ private watchAdoptedWindow;
745
+ private watchForFrameRemoval;
480
746
  /** `true` once the webview has reported `form_load_complete`. */
481
747
  get isReady(): boolean;
482
748
  /**
@@ -542,9 +808,91 @@ declare class CaptelloWebview {
542
808
  submission?: SubmissionPrefill;
543
809
  info?: PrefillInfoItem[];
544
810
  }): void;
811
+ /**
812
+ * Answer the webview's transcribe requests (its transcribe button — shown when the
813
+ * embed URL sets `showTranscribeButton` — sends `transcribe_scanner_request` when
814
+ * pressed).
815
+ *
816
+ * The handler receives the request (`element_id`, `image_urls`,
817
+ * `draft_submission_token`) and a {@link TranscribeScannerReply}, and answers in
818
+ * whichever style suits the transcription code. Either way the result is posted for
819
+ * you — the host never builds or stringifies a `transcribe_scanner_result`.
820
+ *
821
+ * **Return the fields** when the work is promise-based:
822
+ *
823
+ * ```js
824
+ * webview.onTranscribeScannerRequest(async ({ image_urls }) => {
825
+ * const fields = await transcribe(image_urls);
826
+ * return { fields, submissionType: "ocr_transcription" };
827
+ * });
828
+ * ```
829
+ *
830
+ * **Or answer through `reply`** when it isn't — a callback-style AJAX wrapper, an
831
+ * event bus, a method that returns `void`. Hand `reply` to whatever does the work:
832
+ *
833
+ * ```js
834
+ * webview.onTranscribeScannerRequest((request, reply) => {
835
+ * transcribeService(request.image_urls,
836
+ * (fields) => reply.resolve({ fields, submissionType: "ocr_transcription" }),
837
+ * () => reply.reject("Transcription failed."),
838
+ * );
839
+ * });
840
+ * ```
841
+ *
842
+ * A handler that returns nothing is taken to own the reply, so the request stays
843
+ * open until `reply` answers it. A thrown error / rejected promise is sent as an
844
+ * error result; an `Error`'s `message` is shown to the user by the webview, so throw
845
+ * display-ready messages. The first answer wins — a second is ignored.
846
+ *
847
+ * Returns an unsubscribe function.
848
+ */
849
+ onTranscribeScannerRequest(handler: TranscribeScannerRequestHandler): Unsubscribe;
850
+ /**
851
+ * Answer a transcribe request **later**, from code that can't hand back a promise
852
+ * — a callback-style AJAX wrapper, an event bus, a method that returns `void`.
853
+ *
854
+ * Pair it with a plain `on(OutboundMessageType.TranscribeScannerRequest, ...)`
855
+ * subscription: hold on to the request (or just its `request_id`), and call this
856
+ * once the transcription lands. Prefer {@link onTranscribeScannerRequest} when
857
+ * your transcription code is already promise-based — it does this for you.
858
+ *
859
+ * Accepts the request message itself or its bare `request_id`. The reply is
860
+ * `JSON.stringify`'d and posted immediately, bypassing the ready-queue: a received
861
+ * request already proves the webview is live and listening. Replying after
862
+ * {@link destroy}, or after the iframe is detached, is a silent no-op — by then
863
+ * there is nobody left to answer, and the webview times its own button out.
864
+ *
865
+ * @example
866
+ * webview.on(OutboundMessageType.TranscribeScannerRequest, (request) => {
867
+ * ajax.send("Scanner", "transcribe", { image_urls: request.image_urls },
868
+ * (res) => webview.resolveTranscribeScannerRequest(request, {
869
+ * fields: res.fields,
870
+ * submissionType: "ocr_transcription",
871
+ * }),
872
+ * () => webview.rejectTranscribeScannerRequest(request, "Transcription failed."),
873
+ * );
874
+ * });
875
+ */
876
+ resolveTranscribeScannerRequest(request: TranscribeScannerRequestRef, data: TranscribeScannerResultData): void;
877
+ /**
878
+ * Fail a transcribe request answered via
879
+ * {@link resolveTranscribeScannerRequest}'s deferred flow. `error` is shown to the
880
+ * user verbatim by the webview, so pass display-ready text.
881
+ *
882
+ * Same delivery semantics as {@link resolveTranscribeScannerRequest}: posted
883
+ * immediately, and a no-op once the client is destroyed or the iframe detached.
884
+ */
885
+ rejectTranscribeScannerRequest(request: TranscribeScannerRequestRef, error: string): void;
886
+ /**
887
+ * Posts a `transcribe_scanner_result`, bypassing the ready-queue. A received
888
+ * request proves the webview is live and listening, even if this client attached
889
+ * after form load and never saw `form_load_complete`; queuing here could stall the
890
+ * reply forever while the webview's button sits in its pending state until timeout.
891
+ */
892
+ private sendTranscribeScannerResult;
545
893
  /** Remove the `message` listener and drop all subscriptions. Idempotent. */
546
894
  destroy(): void;
547
895
  private handleMessage;
548
896
  }
549
897
 
550
- export { type AddressSubmissionValue as A, type BusinessCardValue as B, type CaptelloWebviewOptions as C, type DraftSubmissionData as D, FormElementType as F, type InboundMessage as I, type NameSubmissionValue as N, OutboundMessageType as O, type PrefillInfoItem as P, type SubmissionBody as S, type Unsubscribe as U, type ValidationTarget as V, type OutboundMessageMap as a, SubmissionError as b, SubmissionTimeoutError as c, type OutboundMessage as d, type SubmissionPrefill as e, CaptelloWebview as f, type AnyOutboundListener as g, type AttachmentValue as h, InboundMessageType as i, type OrderCheckboxSubmissionData as j, type OrderRadioSubmissionData as k, type OutboundListener as l, type SubmissionPrefillDataItem as m, type SubmissionQuestionData as n, type VisibleSubmissionDataItem as o, type VisibleSubmissionElementType as p, type VisibleSubmissionElementValueMap as q, parseOutboundMessage as r };
898
+ export { type AddressSubmissionValue as A, type BusinessCardValue as B, type CaptelloWebviewOptions as C, type DraftSubmissionData as D, parseOutboundMessage as E, type FrameLike as F, type InboundMessage as I, type NameSubmissionValue as N, OutboundMessageType as O, type PrefillInfoItem as P, type SubmissionBody as S, type TranscribeScannerRequestHandler as T, type Unsubscribe as U, type ValidationTarget as V, type OutboundMessageMap as a, SubmissionError as b, SubmissionTimeoutError as c, type OutboundMessage as d, type SubmissionPrefill as e, CaptelloWebview as f, type AnyOutboundListener as g, type AttachOptions as h, type AttachmentValue as i, FormElementType as j, InboundMessageType as k, type OrderCheckboxSubmissionData as l, type OrderRadioSubmissionData as m, type OutboundListener as n, type SubmissionPrefillDataItem as o, type SubmissionQuestionData as p, type TranscribeScannerReply as q, type TranscribeScannerRequestMessage as r, type TranscribeScannerRequestRef as s, type TranscribeScannerResultData as t, type TranscribeScannerResultMessage as u, type TranscribedScannerField as v, type VisibleSubmissionDataItem as w, type VisibleSubmissionElementType as x, type VisibleSubmissionElementValueMap as y, attach as z };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AddressSubmissionValue, g as AnyOutboundListener, h as AttachmentValue, B as BusinessCardValue, f as CaptelloWebview, C as CaptelloWebviewOptions, D as DraftSubmissionData, F as FormElementType, I as InboundMessage, i as InboundMessageType, N as NameSubmissionValue, j as OrderCheckboxSubmissionData, k as OrderRadioSubmissionData, l as OutboundListener, d as OutboundMessage, a as OutboundMessageMap, O as OutboundMessageType, P as PrefillInfoItem, S as SubmissionBody, b as SubmissionError, e as SubmissionPrefill, m as SubmissionPrefillDataItem, n as SubmissionQuestionData, c as SubmissionTimeoutError, U as Unsubscribe, V as ValidationTarget, o as VisibleSubmissionDataItem, p as VisibleSubmissionElementType, q as VisibleSubmissionElementValueMap, r as parseOutboundMessage } from './client-cZpygJTD.js';
1
+ export { A as AddressSubmissionValue, g as AnyOutboundListener, h as AttachOptions, i as AttachmentValue, B as BusinessCardValue, f as CaptelloWebview, C as CaptelloWebviewOptions, D as DraftSubmissionData, j as FormElementType, F as FrameLike, I as InboundMessage, k as InboundMessageType, N as NameSubmissionValue, l as OrderCheckboxSubmissionData, m as OrderRadioSubmissionData, n as OutboundListener, d as OutboundMessage, a as OutboundMessageMap, O as OutboundMessageType, P as PrefillInfoItem, S as SubmissionBody, b as SubmissionError, e as SubmissionPrefill, o as SubmissionPrefillDataItem, p as SubmissionQuestionData, c as SubmissionTimeoutError, q as TranscribeScannerReply, T as TranscribeScannerRequestHandler, r as TranscribeScannerRequestMessage, s as TranscribeScannerRequestRef, t as TranscribeScannerResultData, u as TranscribeScannerResultMessage, v as TranscribedScannerField, U as Unsubscribe, V as ValidationTarget, w as VisibleSubmissionDataItem, x as VisibleSubmissionElementType, y as VisibleSubmissionElementValueMap, z as attach, E as parseOutboundMessage } from './client-CAMlFA8s.js';
2
2
 
3
3
  /**
4
4
  * Builder for the Captello capture webview embed URL.
@@ -28,7 +28,14 @@ declare enum EmbedParam {
28
28
  /** Edit mode read-only: locks email-mapped and invitation-code elements. */
29
29
  Emro = "emro",
30
30
  /** Context for filtering form-fill actions (e.g. MMP outbound/inbound/notes). */
31
- UseIn = "useIn"
31
+ UseIn = "useIn",
32
+ /**
33
+ * Show the transcribe button: the form renders a button that emits a
34
+ * `host_processing_request` with `processing_type: "transcribe_scanner_request"`.
35
+ * Only enable it when the host answers those requests (e.g. via
36
+ * `CaptelloWebview.onProcessingRequest`).
37
+ */
38
+ ShowTranscribeButton = "show_transcribe_button"
32
39
  }
33
40
  /** Form render mode (the webview's `FormMode`). */
34
41
  declare enum FormMode {
@@ -100,6 +107,13 @@ interface EmbedUrlOptions {
100
107
  connexionsEmbedMode?: boolean;
101
108
  /** Edit mode read-only. */
102
109
  emro?: boolean;
110
+ /**
111
+ * Show the transcribe button in the form (emits `host_processing_request` with
112
+ * `processing_type: "transcribe_scanner_request"` when pressed). Enable only when
113
+ * the host answers those requests (e.g. via `CaptelloWebview.onProcessingRequest`) —
114
+ * otherwise the button times out with an error for the user.
115
+ */
116
+ showTranscribeButton?: boolean;
103
117
  /**
104
118
  * Extra query params to append verbatim (e.g. prospect tracking params the
105
119
  * webview forwards on submit). Values are stringified; `undefined`/`null` skipped.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl } from './chunk-4E7OW4RJ.js';
2
- export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, parseOutboundMessage } from './chunk-UUEUW2WL.js';
1
+ export { ActionButtonPosition, EmbedParam, FormMode, Language, LauncherType, buildEmbedUrl } from './chunk-ZX4AKPWF.js';
2
+ export { CaptelloWebview, InboundMessageType, OutboundMessageType, SubmissionError, SubmissionTimeoutError, attach, parseOutboundMessage } from './chunk-ZPVXZW2B.js';
3
3
 
4
4
  // src/submission-data.ts
5
5
  var FormElementType = /* @__PURE__ */ ((FormElementType2) => {
@@ -1,5 +1,5 @@
1
- import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-cZpygJTD.js';
2
- export { b as SubmissionError, c as SubmissionTimeoutError } from './client-cZpygJTD.js';
1
+ import { C as CaptelloWebviewOptions, F as FrameLike, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-CAMlFA8s.js';
2
+ export { b as SubmissionError, c as SubmissionTimeoutError } from './client-CAMlFA8s.js';
3
3
 
4
4
  /**
5
5
  * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.
@@ -17,9 +17,7 @@ export { b as SubmissionError, c as SubmissionTimeoutError } from './client-cZpy
17
17
  */
18
18
 
19
19
  /** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */
20
- type ElementOrFrame = HTMLIFrameElement | {
21
- contentWindow: Window | null;
22
- };
20
+ type ElementOrFrame = FrameLike;
23
21
  /** Options shared by every promise helper. */
24
22
  interface WaitOptions extends Pick<CaptelloWebviewOptions, "targetOrigin" | "hostWindow" | "matchSource"> {
25
23
  /**
package/dist/promises.js CHANGED
@@ -1,5 +1,5 @@
1
- import { CaptelloWebview } from './chunk-UUEUW2WL.js';
2
- export { SubmissionError, SubmissionTimeoutError } from './chunk-UUEUW2WL.js';
1
+ import { CaptelloWebview } from './chunk-ZPVXZW2B.js';
2
+ export { SubmissionError, SubmissionTimeoutError } from './chunk-ZPVXZW2B.js';
3
3
 
4
4
  // src/promises.ts
5
5
  var DEFAULT_TIMEOUT_MS = 6e4;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/promises.ts"],"names":[],"mappings":";;;;AAkCA,IAAM,kBAAA,GAAqB,GAAA;AAGpB,IAAM,mBAAA,GAAN,cAAkC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACoB,aACA,SAAA,EAClB;AACE,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,gBAAA,EAAmB,WAAW,CAAA,4BAAA,CAA8B,CAAA;AAH9E,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EAChB;AACJ;AAEA,SAAS,cAAc,OAAA,EAA8C;AACjE,EAAA,OAAO;AAAA,IACH,cAAc,OAAA,CAAQ,YAAA;AAAA,IACtB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,aAAa,OAAA,CAAQ,WAAA;AAAA;AAAA;AAAA,IAGrB,eAAA,EAAiB;AAAA,GACrB;AACJ;AASO,SAAS,cAAA,CACZ,KAAA,EACA,IAAA,EACA,OAAA,GAAuB,EAAC,EACM;AAC9B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,IAAI,OAAA,CAA+B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3D,IAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,IAAA,IAAI,KAAA;AAEJ,IAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAmB;AAC/B,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAC3C,MAAA,MAAA,CAAO,OAAA,EAAQ;AACf,MAAA,EAAA,EAAG;AAAA,IACP,CAAA;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,OAAA,KAAY,OAAO,MAAM,OAAA,CAAQ,OAAO,CAAC,CAAC,CAAA;AAE7D,IAAA,IAAI,SAAA,GAAY,CAAA,IAAK,SAAA,KAAc,QAAA,EAAU;AACzC,MAAA,KAAA,GAAQ,UAAA,CAAW,MAAM,MAAA,CAAO,MAAM,MAAA,CAAO,IAAI,mBAAA,CAAoB,IAAA,EAAM,SAAS,CAAC,CAAC,CAAA,EAAG,SAAS,CAAA;AAAA,IACtG;AAAA,EACJ,CAAC,CAAA;AACL;AAUO,SAAS,eAAA,CAAgB,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAAkB;AAC7F,EAAA,OAAO,eAAe,KAAA,EAAA,oBAAA,yBAA6C,OAAO,CAAA,CAAE,IAAA,CAAK,MAAM,MAAS,CAAA;AACpG;AAkBO,SAAS,UAAA,CAAW,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAA4B;AAClG,EAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,MAAA,CAAO,cAAc,SAAS,CAAA,CAAE,QAAQ,MAAM,MAAA,CAAO,SAAS,CAAA;AACzE","file":"promises.js","sourcesContent":["/**\n * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.\n *\n * Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are\n * fire-once-and-await utilities that take an iframe directly: create a short-lived\n * client internally, wait for the relevant message, then tear it down. Ideal for\n * `await`-style code (e.g. \"submit and get the body\", \"wait until the form loads\").\n *\n * @example\n * import { submitForm, waitForFormLoad } from \"@captello/ulc-webview-sdk/promises\";\n *\n * await waitForFormLoad(iframe, { targetOrigin });\n * const body = await submitForm(iframe, { targetOrigin });\n */\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions } from \"./client\";\nimport { OutboundMessageType } from \"./messages\";\nimport type { OutboundMessageMap, SubmissionBody } from \"./messages\";\n\nexport { SubmissionError, SubmissionTimeoutError } from \"./client\";\n\n/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */\ntype ElementOrFrame = 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"]}
1
+ {"version":3,"sources":["../src/promises.ts"],"names":[],"mappings":";;;;AAkCA,IAAM,kBAAA,GAAqB,GAAA;AAGpB,IAAM,mBAAA,GAAN,cAAkC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACoB,aACA,SAAA,EAClB;AACE,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,gBAAA,EAAmB,WAAW,CAAA,4BAAA,CAA8B,CAAA;AAH9E,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EAChB;AACJ;AAEA,SAAS,cAAc,OAAA,EAA8C;AACjE,EAAA,OAAO;AAAA,IACH,cAAc,OAAA,CAAQ,YAAA;AAAA,IACtB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,aAAa,OAAA,CAAQ,WAAA;AAAA;AAAA;AAAA,IAGrB,eAAA,EAAiB;AAAA,GACrB;AACJ;AASO,SAAS,cAAA,CACZ,KAAA,EACA,IAAA,EACA,OAAA,GAAuB,EAAC,EACM;AAC9B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,IAAI,OAAA,CAA+B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3D,IAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,IAAA,IAAI,KAAA;AAEJ,IAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAmB;AAC/B,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAC3C,MAAA,MAAA,CAAO,OAAA,EAAQ;AACf,MAAA,EAAA,EAAG;AAAA,IACP,CAAA;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,OAAA,KAAY,OAAO,MAAM,OAAA,CAAQ,OAAO,CAAC,CAAC,CAAA;AAE7D,IAAA,IAAI,SAAA,GAAY,CAAA,IAAK,SAAA,KAAc,QAAA,EAAU;AACzC,MAAA,KAAA,GAAQ,UAAA,CAAW,MAAM,MAAA,CAAO,MAAM,MAAA,CAAO,IAAI,mBAAA,CAAoB,IAAA,EAAM,SAAS,CAAC,CAAC,CAAA,EAAG,SAAS,CAAA;AAAA,IACtG;AAAA,EACJ,CAAC,CAAA;AACL;AAUO,SAAS,eAAA,CAAgB,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAAkB;AAC7F,EAAA,OAAO,eAAe,KAAA,EAAA,oBAAA,yBAA6C,OAAO,CAAA,CAAE,IAAA,CAAK,MAAM,MAAS,CAAA;AACpG;AAkBO,SAAS,UAAA,CAAW,KAAA,EAAuB,OAAA,GAAuB,EAAC,EAA4B;AAClG,EAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,aAAA,CAAc,OAAO,CAAC,CAAA;AAChE,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,OAAO,MAAA,CAAO,cAAc,SAAS,CAAA,CAAE,QAAQ,MAAM,MAAA,CAAO,SAAS,CAAA;AACzE","file":"promises.js","sourcesContent":["/**\n * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.\n *\n * Where {@link CaptelloWebview} is a long-lived client you subscribe to, these are\n * fire-once-and-await utilities that take an iframe directly: create a short-lived\n * client internally, wait for the relevant message, then tear it down. Ideal for\n * `await`-style code (e.g. \"submit and get the body\", \"wait until the form loads\").\n *\n * @example\n * import { submitForm, waitForFormLoad } from \"@captello/ulc-webview-sdk/promises\";\n *\n * await waitForFormLoad(iframe, { targetOrigin });\n * const body = await submitForm(iframe, { targetOrigin });\n */\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions, FrameLike } from \"./client\";\nimport { OutboundMessageType } from \"./messages\";\nimport type { OutboundMessageMap, SubmissionBody } from \"./messages\";\n\nexport { SubmissionError, SubmissionTimeoutError } from \"./client\";\n\n/** Frame accepted by the helpers — an `<iframe>` or anything exposing `contentWindow`. */\ntype ElementOrFrame = FrameLike;\n\n/** Options shared by every promise helper. */\nexport interface WaitOptions extends Pick<CaptelloWebviewOptions, \"targetOrigin\" | \"hostWindow\" | \"matchSource\"> {\n /**\n * How long to wait before rejecting. Defaults to 60_000ms. Pass `0` or `Infinity`\n * to wait indefinitely (the caller is then responsible for not leaking the wait).\n */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/** Rejection reason from {@link waitForMessage} / {@link waitForFormLoad} on timeout. */\nexport class MessageTimeoutError extends Error {\n constructor(\n public readonly messageType: string,\n public readonly timeoutMs: number,\n ) {\n super(`Timed out after ${timeoutMs}ms waiting for \"${messageType}\" from the Captello webview.`);\n this.name = \"MessageTimeoutError\";\n }\n}\n\nfunction clientOptions(options: WaitOptions): CaptelloWebviewOptions {\n return {\n targetOrigin: options.targetOrigin,\n hostWindow: options.hostWindow,\n matchSource: options.matchSource,\n // One-shot helpers act on a form assumed already loaded; never buffer their\n // sends waiting for a form_load_complete that may have already fired.\n queueUntilReady: false,\n };\n}\n\n/**\n * Resolves with the next outbound message of `type` from the webview, or rejects with\n * a {@link MessageTimeoutError} if none arrives within the timeout. The internal\n * listener is always removed before settling.\n *\n * @example const msg = await waitForMessage(iframe, OutboundMessageType.SubmissionBody, { targetOrigin });\n */\nexport function waitForMessage<T extends OutboundMessageType>(\n frame: ElementOrFrame,\n type: T,\n options: WaitOptions = {},\n): Promise<OutboundMessageMap[T]> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise<OutboundMessageMap[T]>((resolve, reject) => {\n const client = new CaptelloWebview(frame, clientOptions(options));\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = (fn: () => void) => {\n if (timer !== undefined) clearTimeout(timer);\n client.destroy();\n fn();\n };\n\n client.once(type, (message) => settle(() => resolve(message)));\n\n if (timeoutMs > 0 && timeoutMs !== Infinity) {\n timer = setTimeout(() => settle(() => reject(new MessageTimeoutError(type, timeoutMs))), timeoutMs);\n }\n });\n}\n\n/**\n * Resolves once the webview reports `form_load_complete`, or rejects with a\n * {@link MessageTimeoutError} on timeout.\n *\n * Note: this only catches a *future* load event. If the form may have already loaded\n * before you call this (e.g. you attach late), prefer subscribing with a long-lived\n * {@link CaptelloWebview} created before the iframe navigates.\n */\nexport function waitForFormLoad(frame: ElementOrFrame, options: WaitOptions = {}): Promise<void> {\n return waitForMessage(frame, OutboundMessageType.FormLoadComplete, options).then(() => undefined);\n}\n\n/**\n * Submits the form and awaits the outcome: resolves with the {@link SubmissionBody} on\n * `submission_body`, rejects with a `SubmissionError` (translated message) on\n * `form_error_message`, or a `SubmissionTimeoutError` if neither arrives in time.\n *\n * Standalone equivalent of {@link CaptelloWebview.submitAndWait} for code that doesn't\n * hold a long-lived client — it creates one, submits, and tears it down.\n *\n * @example\n * try {\n * const body = await submitForm(iframeRef.current!, { targetOrigin });\n * await persist(body);\n * } catch (err) {\n * if (err instanceof SubmissionError) showToast(err.message);\n * }\n */\nexport function submitForm(frame: ElementOrFrame, options: WaitOptions = {}): Promise<SubmissionBody> {\n const client = new CaptelloWebview(frame, clientOptions(options));\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return client.submitAndWait(timeoutMs).finally(() => client.destroy());\n}\n"]}
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, IframeHTMLAttributes, ReactNode, RefCallback } from 'react';
3
- import { C as CaptelloWebviewOptions, S as SubmissionBody, d as OutboundMessage, e as SubmissionPrefill, P as PrefillInfoItem, f as CaptelloWebview, V as ValidationTarget } from './client-cZpygJTD.js';
4
- export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-cZpygJTD.js';
3
+ import { C as CaptelloWebviewOptions, S as SubmissionBody, d as OutboundMessage, e as SubmissionPrefill, P as PrefillInfoItem, T as TranscribeScannerRequestHandler, f as CaptelloWebview, V as ValidationTarget } from './client-CAMlFA8s.js';
4
+ export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-CAMlFA8s.js';
5
5
  import { EmbedUrlOptions } from './index.js';
6
6
 
7
7
  /**
@@ -80,6 +80,16 @@ interface UseCaptelloWebviewOptions extends Omit<CaptelloWebviewOptions, "target
80
80
  * defaultFormValues={{ info: [{ ll_field_unique_identifier: "Email", value: user.email }] }}
81
81
  */
82
82
  defaultFormValues?: DefaultFormValues;
83
+ /**
84
+ * Answer the form's transcribe requests (its transcribe button, shown when the
85
+ * embed URL sets `showTranscribeButton`). Same semantics as
86
+ * {@link CaptelloWebview.onTranscribeScannerRequest}: return the fields (a promise
87
+ * is fine) and they are sent back as the result, or answer through the `reply`
88
+ * second argument when the work is callback-style; a thrown `Error`'s message is
89
+ * shown to the user. Must be set from the first render (it is wired when the iframe
90
+ * attaches); the latest function is always the one invoked, so inline closures are fine.
91
+ */
92
+ onTranscribeScannerRequest?: TranscribeScannerRequestHandler;
83
93
  }
84
94
  /** Readiness of the embedded form. */
85
95
  type CaptelloWebviewStatus = "loading" | "ready" | "error";
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
- import { buildEmbedUrl } from './chunk-4E7OW4RJ.js';
2
- import { CaptelloWebview, OutboundMessageType } from './chunk-UUEUW2WL.js';
3
- export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-UUEUW2WL.js';
1
+ import { buildEmbedUrl } from './chunk-ZX4AKPWF.js';
2
+ import { CaptelloWebview, OutboundMessageType } from './chunk-ZPVXZW2B.js';
3
+ export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-ZPVXZW2B.js';
4
4
  import { forwardRef, useState, useRef, useCallback, useImperativeHandle, useEffect } from 'react';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
6
 
@@ -77,6 +77,17 @@ function useCaptelloWebview(options) {
77
77
  })
78
78
  );
79
79
  }
80
+ if (optionsRef.current.onTranscribeScannerRequest) {
81
+ offs.push(
82
+ client.onTranscribeScannerRequest((request, reply) => {
83
+ const handler = optionsRef.current.onTranscribeScannerRequest;
84
+ if (!handler) {
85
+ throw new Error("Transcription failed.");
86
+ }
87
+ return handler(request, reply);
88
+ })
89
+ );
90
+ }
80
91
  teardown.current = () => {
81
92
  for (const off of offs) off();
82
93
  client.destroy();