@wise/dynamic-flow-client 5.6.0 → 5.6.1

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/build/main.js CHANGED
@@ -2317,6 +2317,35 @@ var modalToComponent = (uid, { content, title }, mapperProps, schemaComponents)
2317
2317
  mapperProps.onComponentUpdate
2318
2318
  );
2319
2319
 
2320
+ // src/domain/components/step/ExternalConfirmationComponent.ts
2321
+ var createExternalConfirmation = (uid, url, onComponentUpdate) => {
2322
+ const update = getInputUpdateFunction(onComponentUpdate);
2323
+ return {
2324
+ type: "external-confirmation",
2325
+ kind: "layout",
2326
+ uid,
2327
+ url,
2328
+ status: "initial",
2329
+ onSuccess() {
2330
+ update(this, (draft) => {
2331
+ draft.status = "success";
2332
+ });
2333
+ },
2334
+ onFailure() {
2335
+ if (this.status === "initial") {
2336
+ update(this, (draft) => {
2337
+ draft.status = "failure";
2338
+ });
2339
+ }
2340
+ },
2341
+ onCancel() {
2342
+ update(this, (draft) => {
2343
+ draft.status = "dismissed";
2344
+ });
2345
+ }
2346
+ };
2347
+ };
2348
+
2320
2349
  // src/utils/recursiveMerge.ts
2321
2350
  function recursiveMerge(valueA, valueB) {
2322
2351
  if (valueA === null) {
@@ -2356,526 +2385,162 @@ function mergeArrays(valueA, valueB) {
2356
2385
  );
2357
2386
  }
2358
2387
 
2359
- // src/controller/getErrorMessage.ts
2360
- var getErrorMessage = (error) => {
2361
- return error instanceof Error ? error.message : typeof error === "string" ? error : `Unknown Error: type is ${typeof error}`;
2362
- };
2388
+ // src/utils/component-utils.ts
2389
+ var getSubmittableData = async (components) => Promise.all(components.map(async (component) => component.getSubmittableValue())).then(
2390
+ (values) => values.reduce((acc, value) => recursiveMerge(acc, value), null)
2391
+ );
2392
+ var getSubmittableDataSync = (components) => components.map((component) => component.getSubmittableValueSync()).reduce((acc, value) => recursiveMerge(acc, value), null);
2393
+ var getLocalValues = (components) => components.map((component) => component.getLocalValue()).reduce((acc, value) => recursiveMerge(acc, value), null);
2363
2394
 
2364
- // src/controller/response-utils.ts
2365
- var import_spec = require("@wise/dynamic-flow-types/spec");
2366
- var parseResponseBodyAsJsonElement = async (response) => {
2367
- assertResponseIsValid(response);
2368
- try {
2369
- return await response.json();
2370
- } catch (e) {
2371
- return null;
2372
- }
2373
- };
2374
- var parseResponseBodyAsText = async (response) => {
2375
- try {
2376
- return await response.text();
2377
- } catch (e) {
2378
- return null;
2379
- }
2395
+ // src/domain/components/step/StepDomainComponent.ts
2396
+ var createStepComponent = (stepProps) => {
2397
+ const _a = stepProps, { uid, stepPolling, stepRefreshAfter, stepPrefetch, onComponentUpdate } = _a, rest = __objRest(_a, ["uid", "stepPolling", "stepRefreshAfter", "stepPrefetch", "onComponentUpdate"]);
2398
+ const update = getInputUpdateFunction(onComponentUpdate);
2399
+ const component = __spreadProps(__spreadValues({
2400
+ uid
2401
+ }, rest), {
2402
+ type: "step",
2403
+ kind: "step",
2404
+ modals: [],
2405
+ requestCache: stepPrefetch.requestCache,
2406
+ dismissModal() {
2407
+ var _a2;
2408
+ (_a2 = this.modals.at(-1)) == null ? void 0 : _a2.close();
2409
+ },
2410
+ dismissAllModals() {
2411
+ this._update((draft) => {
2412
+ draft.modals = draft.modals.map((m) => __spreadProps(__spreadValues({}, m), { open: false }));
2413
+ });
2414
+ },
2415
+ showModal(modal) {
2416
+ this._update((draft) => {
2417
+ draft.modals = [...draft.modals, modal];
2418
+ });
2419
+ },
2420
+ _update(updateFn) {
2421
+ update(this, updateFn);
2422
+ },
2423
+ getChildren() {
2424
+ return this.externalConfirmation ? [...this.layoutComponents, this.externalConfirmation] : this.layoutComponents;
2425
+ },
2426
+ getModals() {
2427
+ return this.modals;
2428
+ },
2429
+ async getSubmittableValue() {
2430
+ return getSubmittableData(this.schemaComponents);
2431
+ },
2432
+ getSubmittableValueSync() {
2433
+ return getSubmittableDataSync(this.schemaComponents);
2434
+ },
2435
+ getLocalValue() {
2436
+ return getLocalValues(this.schemaComponents);
2437
+ },
2438
+ validate() {
2439
+ return this.schemaComponents.every(
2440
+ (inputComponent) => inputComponent.isSchemaReferencedInStep ? inputComponent.validate() : true
2441
+ );
2442
+ },
2443
+ setLoadingState(loadingState) {
2444
+ this._update((draft) => {
2445
+ draft.loadingState = loadingState;
2446
+ });
2447
+ },
2448
+ start() {
2449
+ stepPolling == null ? void 0 : stepPolling.start();
2450
+ stepRefreshAfter == null ? void 0 : stepRefreshAfter.start();
2451
+ stepPrefetch.start(this.getSubmittableValueSync());
2452
+ },
2453
+ stop() {
2454
+ stepPolling == null ? void 0 : stepPolling.stop();
2455
+ stepRefreshAfter == null ? void 0 : stepRefreshAfter.stop();
2456
+ stepPrefetch.stop();
2457
+ this._update((draft) => {
2458
+ draft.modals = [];
2459
+ });
2460
+ }
2461
+ });
2462
+ return component;
2380
2463
  };
2381
- function isActionResponseBody(body) {
2382
- return (0, import_spec.validateActionResponse)(body).valid;
2383
- }
2384
- function assertActionResponseBody(body) {
2385
- if (!isObject(body) || !isObject(body.action)) {
2386
- throw new Error(
2387
- "Incorrect response body in action response. Expected an object satisfying the type { action: Action }."
2388
- );
2464
+
2465
+ // src/domain/features/polling/getStepPolling.ts
2466
+ var getStepPolling = ({
2467
+ pollingConfig,
2468
+ logEvent,
2469
+ onBehavior,
2470
+ onPoll,
2471
+ registerSubmissionBehavior
2472
+ }) => {
2473
+ const { interval, delay = interval, maxAttempts, url, onError } = pollingConfig;
2474
+ let abortController = new AbortController();
2475
+ let intervalRef = null;
2476
+ if (delay == null) {
2477
+ throw new Error("Polling configuration must include delay or interval");
2389
2478
  }
2390
- }
2391
- function assertModalResponseBody(body) {
2392
- if (isObject(body)) {
2393
- if ("content" in body && isArray(body.content)) {
2479
+ const onErrorBehavior = getDomainLayerBehavior(onError, [], registerSubmissionBehavior);
2480
+ let attempts = 0;
2481
+ const poll = () => {
2482
+ attempts += 1;
2483
+ abortController.abort();
2484
+ abortController = new AbortController();
2485
+ const { signal } = abortController;
2486
+ onPoll(url, onErrorBehavior, signal).then((result) => {
2487
+ if (result) {
2488
+ stop();
2489
+ return;
2490
+ }
2491
+ if (attempts >= maxAttempts && !signal.aborted) {
2492
+ void onBehavior(onErrorBehavior);
2493
+ stop();
2494
+ }
2495
+ }).catch(() => {
2496
+ });
2497
+ };
2498
+ const start = () => {
2499
+ attempts = 0;
2500
+ intervalRef = setInterval(poll, delay * 1e3);
2501
+ poll();
2502
+ };
2503
+ const stop = () => {
2504
+ if (!intervalRef) {
2505
+ logEvent("warning", "Attempted to stop polling but it was not started");
2394
2506
  return;
2395
2507
  }
2396
- }
2397
- throw new Error(
2398
- "Incorrect response body in modal response. Expected an object satisfying the type { title?: string, components: Layout[] }."
2399
- );
2400
- }
2401
- function assertSubflowResponseBody(body) {
2402
- const { valid } = (0, import_spec.validateSubflowResponse)(body);
2403
- if (valid) {
2404
- return;
2405
- }
2406
- throw new Error("Incorrect response body in subflow response.");
2407
- }
2408
- function isErrorResponseBody(body) {
2409
- return Boolean(
2410
- isObject(body) && (body.refreshFormUrl || body.refreshUrl || body.validation || body.error || body.analytics)
2411
- );
2412
- }
2413
- function assertStepResponseBody(body) {
2414
- if (!isObject(body)) {
2415
- throw new Error("Incorrect response body in step response. Expected an object.");
2416
- }
2417
- }
2418
- var assertResponseIsValid = (response) => {
2419
- if (!isResponse(response)) {
2420
- throw new Error("Incorrect type of response from fetch. Expected object of type Response.");
2421
- }
2422
- if (response.bodyUsed) {
2423
- throw new Error(
2424
- "The body of the provided Response object has already been used. Every request must respond with a new Response object."
2425
- );
2426
- }
2508
+ clearTimeout(intervalRef);
2509
+ abortController.abort();
2510
+ };
2511
+ return { start, stop };
2427
2512
  };
2428
- var isResponse = (response) => typeof response === "object" && response !== null && "clone" in response && "bodyUsed" in response;
2429
2513
 
2430
- // src/controller/handleErrorResponse.ts
2431
- var handleErrorResponse = async (response, actionId, trackEvent) => {
2432
- const body = await parseResponseBodyAsJsonElement(response.clone());
2433
- if (isErrorResponseBody(body)) {
2434
- const refreshUrl = body.refreshUrl || body.refreshFormUrl;
2435
- const { error, validation, analytics } = body;
2436
- trackEvent("Action Failed", __spreadProps(__spreadValues({}, analytics), { actionId, statusCode: response.status }));
2437
- const errors = { error, validation };
2438
- return refreshUrl ? { type: "refresh", body: { refreshUrl, errors } } : {
2439
- type: "error",
2440
- body: { errors, analytics },
2441
- httpError: { statusCode: response.status }
2442
- };
2514
+ // src/domain/features/refreshAfter/getStepRefreshAfter.ts
2515
+ var ONE_SECOND = 1e3;
2516
+ var getStepRefreshAfter = ({
2517
+ refreshAfter,
2518
+ logEvent,
2519
+ onBehavior
2520
+ }) => {
2521
+ let timeout = null;
2522
+ const targetTime = new Date(refreshAfter).getTime();
2523
+ if (typeof refreshAfter !== "string" || Number.isNaN(targetTime)) {
2524
+ throw new Error(`Invalid refreshAfter value: ${String(refreshAfter)}`);
2443
2525
  }
2444
- trackEvent("Action Failed", { actionId, statusCode: response.status });
2445
- const errorMessage = await parseResponseBodyAsText(response);
2526
+ const start = () => {
2527
+ const timeLeft = Math.max(targetTime - Date.now(), ONE_SECOND);
2528
+ timeout = setTimeout(() => {
2529
+ void onBehavior({ type: "refresh", analytics: { schema: "refreshAfter" } });
2530
+ }, timeLeft);
2531
+ };
2446
2532
  return {
2447
- type: "error",
2448
- httpError: {
2449
- message: errorMessage || void 0,
2450
- statusCode: response.status
2533
+ start,
2534
+ stop: () => {
2535
+ if (!timeout) {
2536
+ logEvent("warning", "Attempted to stop refreshAfter but it was not started");
2537
+ return;
2538
+ }
2539
+ clearTimeout(timeout);
2451
2540
  }
2452
2541
  };
2453
2542
  };
2454
2543
 
2455
- // src/controller/getResponseType.ts
2456
- var responseTypes = ["step", "action", "exit", "modal", "subflow"];
2457
- var getResponseType = (headers, body) => {
2458
- const headerResponseType = getResponseTypeFromHeader(headers);
2459
- if (headerResponseType) {
2460
- return headerResponseType;
2461
- }
2462
- if (isObject(body) && body.action) {
2463
- return "action";
2464
- }
2465
- return "step";
2466
- };
2467
- var getResponseTypeFromHeader = (headers) => {
2468
- if (headers == null ? void 0 : headers.has("X-Df-Response-Type")) {
2469
- const type = headers.get("X-Df-Response-Type");
2470
- assertDFResponseType(type);
2471
- return type;
2472
- }
2473
- if (headers == null ? void 0 : headers.has("X-Df-Exit")) {
2474
- return "exit";
2475
- }
2476
- return void 0;
2477
- };
2478
- function assertDFResponseType(type) {
2479
- if (!responseTypes.includes(type)) {
2480
- throw new Error(
2481
- "Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'error', 'modal', 'subflow'."
2482
- );
2483
- }
2484
- }
2485
-
2486
- // src/controller/makeSafeHttpClient.ts
2487
- var makeSafeHttpClient = (httpClient) => async (...props) => {
2488
- try {
2489
- return await httpClient(...props);
2490
- } catch (e) {
2491
- return null;
2492
- }
2493
- };
2494
-
2495
- // src/controller/executeRequest.ts
2496
- var executeRequest = async (props) => {
2497
- const { exit, request, requestCache, httpClient, trackEvent, logEvent } = props;
2498
- const { url, method, body } = request;
2499
- const response = await getCachedOrFetch(
2500
- [
2501
- url,
2502
- {
2503
- method,
2504
- body: body ? JSON.stringify(body) : void 0,
2505
- headers: { "Content-Type": "application/json" }
2506
- }
2507
- ],
2508
- requestCache,
2509
- httpClient
2510
- );
2511
- if (!response) {
2512
- const extra = { errorMessage: "Network Error" };
2513
- trackEvent("Request Failed", extra);
2514
- logEvent("error", "Dynamic Flow - Request Failed Unexpectedly", extra);
2515
- return { type: "error" };
2516
- }
2517
- if (!response.ok) {
2518
- return handleErrorResponse(response, void 0, trackEvent);
2519
- }
2520
- const responseBody = await parseResponseBodyAsJsonElement(response);
2521
- const responseType = getResponseType(response.headers, responseBody);
2522
- if (exit) {
2523
- return { type: "complete", result: responseBody };
2524
- }
2525
- switch (responseType) {
2526
- case "step": {
2527
- const etag = response.headers.get("etag") || null;
2528
- assertStepResponseBody(responseBody);
2529
- return { type: "replace-step", step: responseBody, etag };
2530
- }
2531
- case "exit": {
2532
- return { type: "complete", result: responseBody };
2533
- }
2534
- case "action": {
2535
- assertActionResponseBody(responseBody);
2536
- return {
2537
- type: "behavior",
2538
- behavior: {
2539
- type: "action",
2540
- action: responseBody.action
2541
- }
2542
- };
2543
- }
2544
- case "subflow": {
2545
- assertSubflowResponseBody(responseBody);
2546
- return {
2547
- type: "behavior",
2548
- behavior: __spreadProps(__spreadValues({}, responseBody), {
2549
- type: "subflow",
2550
- onCompletion: responseBody.onCompletion ? normaliseBehavior(responseBody.onCompletion, []) : void 0,
2551
- onError: responseBody.onError ? normaliseBehavior(responseBody.onError, []) : void 0
2552
- })
2553
- };
2554
- }
2555
- case "modal": {
2556
- assertModalResponseBody(responseBody);
2557
- return { type: "behavior", behavior: __spreadProps(__spreadValues({}, responseBody), { type: "modal" }) };
2558
- }
2559
- default: {
2560
- throw new Error(`Unsupported response type: ${String(responseType)}`);
2561
- }
2562
- }
2563
- };
2564
- var getCachedOrFetch = async (requestParams, requestCache, httpClient) => {
2565
- const cachedPromise = requestCache.get(requestParams);
2566
- if (cachedPromise) {
2567
- const cachedResponse = await cachedPromise;
2568
- if (cachedResponse == null ? void 0 : cachedResponse.ok) {
2569
- return cachedResponse;
2570
- }
2571
- }
2572
- return makeSafeHttpClient(httpClient)(...requestParams);
2573
- };
2574
-
2575
- // src/controller/executeSubmission.ts
2576
- var executeSubmission = async (props) => {
2577
- const { httpClient, requestCache, trackEvent, logEvent } = props;
2578
- const triggerAction = async (action, model, isInitial) => {
2579
- var _a, _b;
2580
- const { exit, url, result = null, id: actionId } = action;
2581
- const trackSubmissionEvent = !isInitial ? trackEvent : () => {
2582
- };
2583
- trackSubmissionEvent("Action Triggered", { actionId });
2584
- if (exit && !url) {
2585
- trackSubmissionEvent("Action Succeeded", { actionId });
2586
- return { type: "complete", result };
2587
- }
2588
- try {
2589
- const command = await executeRequest({
2590
- exit,
2591
- request: createRequestFromAction(action, model),
2592
- requestCache,
2593
- httpClient,
2594
- trackEvent: (name, properties) => {
2595
- trackSubmissionEvent(name, __spreadProps(__spreadValues({}, properties), { actionId }));
2596
- },
2597
- logEvent
2598
- });
2599
- switch (command.type) {
2600
- case "error": {
2601
- trackSubmissionEvent("Action Failed", __spreadValues({
2602
- actionId,
2603
- statusCode: (_a = command.httpError) == null ? void 0 : _a.statusCode
2604
- }, (_b = command.body) == null ? void 0 : _b.analytics));
2605
- return command;
2606
- }
2607
- case "behavior": {
2608
- if (command.behavior.type === "action") {
2609
- trackSubmissionEvent("Action Succeeded", { actionId });
2610
- return await executeRequest({
2611
- request: createRequestFromAction(command.behavior.action, null),
2612
- requestCache,
2613
- httpClient,
2614
- trackEvent,
2615
- logEvent
2616
- });
2617
- }
2618
- trackSubmissionEvent("Action Succeeded", { actionId });
2619
- return command;
2620
- }
2621
- case "complete": {
2622
- trackSubmissionEvent("Action Succeeded", { actionId });
2623
- return __spreadProps(__spreadValues({}, command), { result: recursiveMerge(command.result, result) });
2624
- }
2625
- default: {
2626
- trackSubmissionEvent("Action Succeeded", { actionId });
2627
- return command;
2628
- }
2629
- }
2630
- } catch (error) {
2631
- const errorMessage = getErrorMessage(error);
2632
- trackSubmissionEvent("Action Failed", { actionId, errorMessage });
2633
- logEvent("error", "Dynamic Flow - Action Failed Unexpectedly", { actionId, errorMessage });
2634
- throw error;
2635
- }
2636
- };
2637
- return triggerAction(props.action, props.model, props.isInitial);
2638
- };
2639
- var createRequestFromAction = (action, model) => {
2640
- var _a, _b, _c;
2641
- return __spreadProps(__spreadValues({}, action), {
2642
- url: (_a = action.url) != null ? _a : "",
2643
- method: (_b = action.method) != null ? _b : "POST",
2644
- body: action.method === "GET" ? void 0 : recursiveMerge(model, (_c = action.data) != null ? _c : null)
2645
- });
2646
- };
2647
-
2648
- // src/domain/components/step/ExternalConfirmationComponent.ts
2649
- var createExternalConfirmation = (uid, url, onComponentUpdate) => {
2650
- const update = getInputUpdateFunction(onComponentUpdate);
2651
- return {
2652
- type: "external-confirmation",
2653
- kind: "layout",
2654
- uid,
2655
- url,
2656
- status: "initial",
2657
- onSuccess() {
2658
- update(this, (draft) => {
2659
- draft.status = "success";
2660
- });
2661
- },
2662
- onFailure() {
2663
- if (this.status === "initial") {
2664
- update(this, (draft) => {
2665
- draft.status = "failure";
2666
- });
2667
- }
2668
- },
2669
- onCancel() {
2670
- update(this, (draft) => {
2671
- draft.status = "dismissed";
2672
- });
2673
- }
2674
- };
2675
- };
2676
-
2677
- // src/utils/component-utils.ts
2678
- var getSubmittableData = async (components) => Promise.all(components.map(async (component) => component.getSubmittableValue())).then(
2679
- (values) => values.reduce((acc, value) => recursiveMerge(acc, value), null)
2680
- );
2681
- var getSubmittableDataSync = (components) => components.map((component) => component.getSubmittableValueSync()).reduce((acc, value) => recursiveMerge(acc, value), null);
2682
- var getLocalValues = (components) => components.map((component) => component.getLocalValue()).reduce((acc, value) => recursiveMerge(acc, value), null);
2683
-
2684
- // src/domain/components/step/StepDomainComponent.ts
2685
- var createStepComponent = (stepProps) => {
2686
- const _a = stepProps, { uid, stepPolling, stepRefreshAfter, onComponentUpdate } = _a, rest = __objRest(_a, ["uid", "stepPolling", "stepRefreshAfter", "onComponentUpdate"]);
2687
- const update = getInputUpdateFunction(onComponentUpdate);
2688
- const component = __spreadProps(__spreadValues({
2689
- uid
2690
- }, rest), {
2691
- type: "step",
2692
- kind: "step",
2693
- modals: [],
2694
- dismissModal() {
2695
- var _a2;
2696
- (_a2 = this.modals.at(-1)) == null ? void 0 : _a2.close();
2697
- },
2698
- dismissAllModals() {
2699
- this._update((draft) => {
2700
- draft.modals = draft.modals.map((m) => __spreadProps(__spreadValues({}, m), { open: false }));
2701
- });
2702
- },
2703
- showModal(modal) {
2704
- this._update((draft) => {
2705
- draft.modals = [...draft.modals, modal];
2706
- });
2707
- },
2708
- _update(updateFn) {
2709
- update(this, updateFn);
2710
- },
2711
- getChildren() {
2712
- return this.externalConfirmation ? [...this.layoutComponents, this.externalConfirmation] : this.layoutComponents;
2713
- },
2714
- getModals() {
2715
- return this.modals;
2716
- },
2717
- async getSubmittableValue() {
2718
- return getSubmittableData(this.schemaComponents);
2719
- },
2720
- getSubmittableValueSync() {
2721
- return getSubmittableDataSync(this.schemaComponents);
2722
- },
2723
- getLocalValue() {
2724
- return getLocalValues(this.schemaComponents);
2725
- },
2726
- validate() {
2727
- return this.schemaComponents.every(
2728
- (inputComponent) => inputComponent.isSchemaReferencedInStep ? inputComponent.validate() : true
2729
- );
2730
- },
2731
- setLoadingState(loadingState) {
2732
- this._update((draft) => {
2733
- draft.loadingState = loadingState;
2734
- });
2735
- },
2736
- start() {
2737
- stepPolling == null ? void 0 : stepPolling.start();
2738
- stepRefreshAfter == null ? void 0 : stepRefreshAfter.start();
2739
- },
2740
- stop() {
2741
- stepPolling == null ? void 0 : stepPolling.stop();
2742
- stepRefreshAfter == null ? void 0 : stepRefreshAfter.stop();
2743
- this._update((draft) => {
2744
- draft.modals = [];
2745
- });
2746
- }
2747
- });
2748
- return component;
2749
- };
2750
-
2751
- // src/domain/features/polling/getStepPolling.ts
2752
- var getStepPolling = ({
2753
- pollingConfig,
2754
- logEvent,
2755
- onBehavior,
2756
- onPoll,
2757
- registerSubmissionBehavior
2758
- }) => {
2759
- const { interval, delay = interval, maxAttempts, url, onError } = pollingConfig;
2760
- let abortController = new AbortController();
2761
- let intervalRef = null;
2762
- if (delay == null) {
2763
- throw new Error("Polling configuration must include delay or interval");
2764
- }
2765
- const onErrorBehavior = getDomainLayerBehavior(onError, [], registerSubmissionBehavior);
2766
- let attempts = 0;
2767
- const poll = () => {
2768
- attempts += 1;
2769
- abortController.abort();
2770
- abortController = new AbortController();
2771
- const { signal } = abortController;
2772
- onPoll(url, onErrorBehavior, signal).then((result) => {
2773
- if (result) {
2774
- stop();
2775
- return;
2776
- }
2777
- if (attempts >= maxAttempts && !signal.aborted) {
2778
- void onBehavior(onErrorBehavior);
2779
- stop();
2780
- }
2781
- }).catch(() => {
2782
- });
2783
- };
2784
- const start = () => {
2785
- attempts = 0;
2786
- intervalRef = setInterval(poll, delay * 1e3);
2787
- poll();
2788
- };
2789
- const stop = () => {
2790
- if (!intervalRef) {
2791
- logEvent("warning", "Attempted to stop polling but it was not started");
2792
- return;
2793
- }
2794
- clearTimeout(intervalRef);
2795
- abortController.abort();
2796
- };
2797
- return { start, stop };
2798
- };
2799
-
2800
- // src/domain/features/refreshAfter/getStepRefreshAfter.ts
2801
- var ONE_SECOND = 1e3;
2802
- var getStepRefreshAfter = ({
2803
- refreshAfter,
2804
- logEvent,
2805
- onBehavior
2806
- }) => {
2807
- let timeout = null;
2808
- const targetTime = new Date(refreshAfter).getTime();
2809
- if (typeof refreshAfter !== "string" || Number.isNaN(targetTime)) {
2810
- throw new Error(`Invalid refreshAfter value: ${String(refreshAfter)}`);
2811
- }
2812
- const start = () => {
2813
- const timeLeft = Math.max(targetTime - Date.now(), ONE_SECOND);
2814
- timeout = setTimeout(() => {
2815
- void onBehavior({ type: "refresh", analytics: { schema: "refreshAfter" } });
2816
- }, timeLeft);
2817
- };
2818
- return {
2819
- start,
2820
- stop: () => {
2821
- if (!timeout) {
2822
- logEvent("warning", "Attempted to stop refreshAfter but it was not started");
2823
- return;
2824
- }
2825
- clearTimeout(timeout);
2826
- }
2827
- };
2828
- };
2829
-
2830
- // src/domain/prefetching/request-cache.ts
2831
- var makeRequestCacheWithParent = (parent) => {
2832
- const map = /* @__PURE__ */ new Map();
2833
- const cache = {
2834
- get: (requestParams) => {
2835
- var _a;
2836
- const key = makeKey(requestParams);
2837
- const promise = (_a = map.get(key)) != null ? _a : parent == null ? void 0 : parent.get(requestParams);
2838
- map.delete(key);
2839
- return promise;
2840
- },
2841
- set: (requestParams, responsePromise) => {
2842
- return map.set(makeKey(requestParams), responsePromise);
2843
- }
2844
- };
2845
- return cache;
2846
- };
2847
- var makeRequestCache = (initialValues = []) => {
2848
- const cache = makeRequestCacheWithParent(void 0);
2849
- initialValues.forEach(([requestParams, responsePromise]) => {
2850
- cache.set(requestParams, responsePromise);
2851
- });
2852
- return cache;
2853
- };
2854
- var normaliseRequestCache = (cache) => {
2855
- if (cache === void 0) {
2856
- return makeRequestCache();
2857
- }
2858
- if (isRequestCacheInstance(cache)) {
2859
- return cache;
2860
- }
2861
- return makeRequestCache(cache);
2862
- };
2863
- var isRequestCacheInstance = (cache) => {
2864
- return !cache || !Array.isArray(cache);
2865
- };
2866
- var makeKey = (requestParams) => {
2867
- var _a, _b;
2868
- const [input, init] = requestParams;
2869
- const url = typeof input === "string" || input instanceof URL ? input.toString() : input.url;
2870
- const key = JSON.stringify({
2871
- url,
2872
- method: (_a = init == null ? void 0 : init.method) != null ? _a : "GET",
2873
- headers: (init == null ? void 0 : init.headers) ? Array.from(new Headers(init.headers).entries()) : [],
2874
- body: (_b = init == null ? void 0 : init.body) != null ? _b : null
2875
- });
2876
- return key;
2877
- };
2878
-
2879
2544
  // src/domain/components/utils/isOrWasValid.ts
2880
2545
  var isOrWasValid = (getErrors, previous, current) => {
2881
2546
  const wasValid = getErrors(previous).length === 0 && previous !== null;
@@ -3354,6 +3019,72 @@ var getInitialValidationAsyncState = () => ({
3354
3019
  messages: {}
3355
3020
  });
3356
3021
 
3022
+ // src/controller/response-utils.ts
3023
+ var import_spec = require("@wise/dynamic-flow-types/spec");
3024
+ var parseResponseBodyAsJsonElement = async (response) => {
3025
+ assertResponseIsValid(response);
3026
+ try {
3027
+ return await response.json();
3028
+ } catch (e) {
3029
+ return null;
3030
+ }
3031
+ };
3032
+ var parseResponseBodyAsText = async (response) => {
3033
+ try {
3034
+ return await response.text();
3035
+ } catch (e) {
3036
+ return null;
3037
+ }
3038
+ };
3039
+ function isActionResponseBody(body) {
3040
+ return (0, import_spec.validateActionResponse)(body).valid;
3041
+ }
3042
+ function assertActionResponseBody(body) {
3043
+ if (!isObject(body) || !isObject(body.action)) {
3044
+ throw new Error(
3045
+ "Incorrect response body in action response. Expected an object satisfying the type { action: Action }."
3046
+ );
3047
+ }
3048
+ }
3049
+ function assertModalResponseBody(body) {
3050
+ if (isObject(body)) {
3051
+ if ("content" in body && isArray(body.content)) {
3052
+ return;
3053
+ }
3054
+ }
3055
+ throw new Error(
3056
+ "Incorrect response body in modal response. Expected an object satisfying the type { title?: string, components: Layout[] }."
3057
+ );
3058
+ }
3059
+ function assertSubflowResponseBody(body) {
3060
+ const { valid } = (0, import_spec.validateSubflowResponse)(body);
3061
+ if (valid) {
3062
+ return;
3063
+ }
3064
+ throw new Error("Incorrect response body in subflow response.");
3065
+ }
3066
+ function isErrorResponseBody(body) {
3067
+ return Boolean(
3068
+ isObject(body) && (body.refreshFormUrl || body.refreshUrl || body.validation || body.error || body.analytics)
3069
+ );
3070
+ }
3071
+ function assertStepResponseBody(body) {
3072
+ if (!isObject(body)) {
3073
+ throw new Error("Incorrect response body in step response. Expected an object.");
3074
+ }
3075
+ }
3076
+ var assertResponseIsValid = (response) => {
3077
+ if (!isResponse(response)) {
3078
+ throw new Error("Incorrect type of response from fetch. Expected object of type Response.");
3079
+ }
3080
+ if (response.bodyUsed) {
3081
+ throw new Error(
3082
+ "The body of the provided Response object has already been used. Every request must respond with a new Response object."
3083
+ );
3084
+ }
3085
+ };
3086
+ var isResponse = (response) => typeof response === "object" && response !== null && "clone" in response && "bodyUsed" in response;
3087
+
3357
3088
  // src/domain/features/utils/response-utils.ts
3358
3089
  var getAnalyticsFromErrorResponse = (json) => {
3359
3090
  if (!isErrorResponseBody(json)) {
@@ -5829,235 +5560,535 @@ var createTextInputComponent = (textInputProps, onComponentUpdate) => {
5829
5560
  this._update((draft) => {
5830
5561
  draft.errors = errors;
5831
5562
  });
5832
- return errors.length === 0;
5833
- }
5834
- }, rest);
5835
- return inputComponent;
5563
+ return errors.length === 0;
5564
+ }
5565
+ }, rest);
5566
+ return inputComponent;
5567
+ };
5568
+
5569
+ // src/domain/mappers/schema/stringSchemaToComponent/stringSchemaToTextInputComponent.ts
5570
+ var stringSchemaToTextInputComponent = (schemaMapperProps, mapperProps) => {
5571
+ const { schema, localValue, model, required = false, onPersistAsync } = schemaMapperProps;
5572
+ const {
5573
+ autocapitalization,
5574
+ autocompleteHint,
5575
+ control,
5576
+ default: defaultValue,
5577
+ displayFormat,
5578
+ format,
5579
+ maxLength,
5580
+ minLength,
5581
+ suggestions,
5582
+ validationMessages
5583
+ } = schema;
5584
+ const { getErrorMessageFunctions, onComponentUpdate, onBehavior, onValueChange, logEvent } = mapperProps;
5585
+ const controlForLegacyFormat = getControlForLegacyFormat(format);
5586
+ const errorMessageFunctions = getErrorMessageFunctions(validationMessages);
5587
+ const { performValidationAsync, validationAsyncState } = getValidationAsyncInitialState(
5588
+ schemaMapperProps,
5589
+ mapperProps
5590
+ );
5591
+ const validLocalValue = isString(localValue) ? localValue : null;
5592
+ const validModel = isString(model) ? model : defaultValue != null ? defaultValue : null;
5593
+ const value = onPersistAsync ? validLocalValue : validModel;
5594
+ return createTextInputComponent(
5595
+ __spreadProps(__spreadValues({}, mapCommonSchemaProps(schemaMapperProps)), {
5596
+ autocapitalization,
5597
+ autoComplete: getAutocompleteString(autocompleteHint),
5598
+ checks: schema.hidden ? [] : [
5599
+ getRequiredCheck(required, errorMessageFunctions),
5600
+ getAboveMaxLengthCheck(schema, errorMessageFunctions),
5601
+ getBelowMinLengthCheck(schema, errorMessageFunctions),
5602
+ getNotAdheringToPatternCheck(schema, errorMessageFunctions, { logEvent })
5603
+ ],
5604
+ control: control != null ? control : controlForLegacyFormat,
5605
+ displayFormat,
5606
+ maxLength,
5607
+ minLength,
5608
+ suggestions: mapStringSchemaSuggestions(suggestions, mapperProps.logEvent),
5609
+ value,
5610
+ validationAsyncState,
5611
+ schemaOnChange: getSchemaOnChange(schema, onBehavior),
5612
+ performValidationAsync,
5613
+ onValueChange
5614
+ }),
5615
+ onComponentUpdate
5616
+ );
5617
+ };
5618
+ var getControlForLegacyFormat = (format) => {
5619
+ if (format && ["numeric", "phone-number", "email", "password"].includes(format)) {
5620
+ return format;
5621
+ }
5622
+ return void 0;
5623
+ };
5624
+
5625
+ // src/domain/mappers/schema/stringSchemaToComponent/stringSchemaToComponent.ts
5626
+ var stringSchemaToComponent = (schemaMapperProps, mapperProps) => {
5627
+ const { schema } = schemaMapperProps;
5628
+ if (isStringSchemaWithBase64(schema)) {
5629
+ return stringSchemaToUploadInputComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5630
+ }
5631
+ switch (schema.format) {
5632
+ case "date":
5633
+ return stringSchemaToDateInputComponent(schemaMapperProps, mapperProps);
5634
+ default:
5635
+ return stringSchemaToTextInputComponent(schemaMapperProps, mapperProps);
5636
+ }
5637
+ };
5638
+ var isStringSchemaWithBase64 = (schema) => {
5639
+ return schema.format === "base64url" && !("persistAsync" in schema);
5640
+ };
5641
+
5642
+ // src/domain/mappers/mapSchemaToComponent.ts
5643
+ var mapSchemaToComponent = (schemaMapperProps, mapperProps) => {
5644
+ const { uid, schema } = schemaMapperProps;
5645
+ if (isConstSchema(schema)) {
5646
+ return constSchemaToComponent(uid, __spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5647
+ }
5648
+ if (isSchemaWithPersistAsync(schema)) {
5649
+ return persistAsyncSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5650
+ }
5651
+ if (isAllOfSchema(schema)) {
5652
+ return allOfSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5653
+ }
5654
+ if (isOneOfSchema(schema)) {
5655
+ return oneOfSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5656
+ }
5657
+ if (isBooleanSchema(schema)) {
5658
+ return booleanSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5659
+ }
5660
+ if (isObjectSchema(schema)) {
5661
+ const { format } = schema;
5662
+ if (format === "money") {
5663
+ return objectSchemaToMoneyInputComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5664
+ }
5665
+ if (format != null && Object.keys(schema.properties).length === 0) {
5666
+ return objectSchemaToFormattedValueComponent(
5667
+ __spreadProps(__spreadValues({}, schemaMapperProps), { schema: __spreadProps(__spreadValues({}, schema), { format }) }),
5668
+ mapperProps
5669
+ );
5670
+ }
5671
+ return objectSchemaToObjectComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5672
+ }
5673
+ if (isIntegerSchema(schema)) {
5674
+ return integerSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5675
+ }
5676
+ if (isNumberSchema(schema)) {
5677
+ return numberSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5678
+ }
5679
+ if (isStringSchema(schema)) {
5680
+ return stringSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5681
+ }
5682
+ if (isArraySchema(schema)) {
5683
+ return arraySchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5684
+ }
5685
+ if (isBlobSchema(schema)) {
5686
+ if (!schemaMapperProps.onPersistAsync) {
5687
+ throw new Error(
5688
+ "Blob schemas can only be used as the schema of a persist async configuration."
5689
+ );
5690
+ }
5691
+ return blobSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5692
+ }
5693
+ throw new Error("Not yet supported");
5694
+ };
5695
+
5696
+ // src/domain/mappers/mapStepSchemas.ts
5697
+ var mapStepSchemas = (uid, step, stepLocalValue, mapperProps, referencedSchemaIds) => {
5698
+ const isReferenced = (schemaId) => schemaId != null && referencedSchemaIds.includes(schemaId);
5699
+ return step.schemas.map((schema, i) => {
5700
+ var _a, _b;
5701
+ const schemaComponent = mapSchemaToComponent(
5702
+ {
5703
+ uid: `${uid}.schemas-${i}.${schema.$id}`,
5704
+ schemaId: schema.$id,
5705
+ schema,
5706
+ model: (_a = step.model) != null ? _a : null,
5707
+ localValue: stepLocalValue,
5708
+ validationErrors: (_b = step.errors) == null ? void 0 : _b.validation,
5709
+ required: true
5710
+ },
5711
+ mapperProps
5712
+ );
5713
+ return __spreadProps(__spreadValues({}, schemaComponent), {
5714
+ isSchemaReferencedInStep: isReferenced(schema.$id)
5715
+ });
5716
+ });
5717
+ };
5718
+
5719
+ // src/domain/mappers/mapToolbarToComponent.ts
5720
+ var mapToolbarToComponent = (uid, toolbar, mapperProps) => {
5721
+ if (!toolbar) {
5722
+ return void 0;
5723
+ }
5724
+ const { onBehavior } = mapperProps;
5725
+ return __spreadProps(__spreadValues({}, toolbar), {
5726
+ type: "toolbar",
5727
+ uid: `${uid}.toolbar`,
5728
+ tags: toolbar.tags,
5729
+ items: toolbar.items.map((item) => {
5730
+ const context = item.context ? mapLegacyContext(item.context) : void 0;
5731
+ const behavior = getDomainLayerBehavior(
5732
+ { behavior: item.behavior },
5733
+ [],
5734
+ mapperProps.registerSubmissionBehavior
5735
+ );
5736
+ return __spreadProps(__spreadValues({}, item), {
5737
+ context: context ? mapLegacyContext(context) : void 0,
5738
+ disabled: !!item.disabled,
5739
+ tags: item.tags,
5740
+ onClick: () => {
5741
+ void onBehavior(behavior);
5742
+ }
5743
+ });
5744
+ })
5745
+ });
5836
5746
  };
5837
5747
 
5838
- // src/domain/mappers/schema/stringSchemaToComponent/stringSchemaToTextInputComponent.ts
5839
- var stringSchemaToTextInputComponent = (schemaMapperProps, mapperProps) => {
5840
- const { schema, localValue, model, required = false, onPersistAsync } = schemaMapperProps;
5841
- const {
5842
- autocapitalization,
5843
- autocompleteHint,
5844
- control,
5845
- default: defaultValue,
5846
- displayFormat,
5847
- format,
5848
- maxLength,
5849
- minLength,
5850
- suggestions,
5851
- validationMessages
5852
- } = schema;
5853
- const { getErrorMessageFunctions, onComponentUpdate, onBehavior, onValueChange, logEvent } = mapperProps;
5854
- const controlForLegacyFormat = getControlForLegacyFormat(format);
5855
- const errorMessageFunctions = getErrorMessageFunctions(validationMessages);
5856
- const { performValidationAsync, validationAsyncState } = getValidationAsyncInitialState(
5857
- schemaMapperProps,
5858
- mapperProps
5859
- );
5860
- const validLocalValue = isString(localValue) ? localValue : null;
5861
- const validModel = isString(model) ? model : defaultValue != null ? defaultValue : null;
5862
- const value = onPersistAsync ? validLocalValue : validModel;
5863
- return createTextInputComponent(
5864
- __spreadProps(__spreadValues({}, mapCommonSchemaProps(schemaMapperProps)), {
5865
- autocapitalization,
5866
- autoComplete: getAutocompleteString(autocompleteHint),
5867
- checks: schema.hidden ? [] : [
5868
- getRequiredCheck(required, errorMessageFunctions),
5869
- getAboveMaxLengthCheck(schema, errorMessageFunctions),
5870
- getBelowMinLengthCheck(schema, errorMessageFunctions),
5871
- getNotAdheringToPatternCheck(schema, errorMessageFunctions, { logEvent })
5872
- ],
5873
- control: control != null ? control : controlForLegacyFormat,
5874
- displayFormat,
5875
- maxLength,
5876
- minLength,
5877
- suggestions: mapStringSchemaSuggestions(suggestions, mapperProps.logEvent),
5878
- value,
5879
- validationAsyncState,
5880
- schemaOnChange: getSchemaOnChange(schema, onBehavior),
5881
- performValidationAsync,
5882
- onValueChange
5883
- }),
5884
- onComponentUpdate
5885
- );
5748
+ // src/domain/mappers/utils/groupLayoutByPinned.ts
5749
+ var groupLayoutByPinned = (layouts) => {
5750
+ return layouts.reduce(groupLayout, { pinned: [], nonPinned: [] });
5886
5751
  };
5887
- var getControlForLegacyFormat = (format) => {
5888
- if (format && ["numeric", "phone-number", "email", "password"].includes(format)) {
5889
- return format;
5752
+ var groupLayout = (acc, layout) => {
5753
+ if (layout.type === "button" && layout.pinOrder !== void 0) {
5754
+ return {
5755
+ pinned: [...acc.pinned, layout],
5756
+ nonPinned: acc.nonPinned
5757
+ };
5890
5758
  }
5891
- return void 0;
5759
+ if (hasColumns(layout)) {
5760
+ const leftChildren = groupLayoutByPinned(layout.left);
5761
+ const rightChildren = groupLayoutByPinned(layout.right);
5762
+ return {
5763
+ pinned: [...acc.pinned, ...leftChildren.pinned, ...rightChildren.pinned],
5764
+ nonPinned: [
5765
+ ...acc.nonPinned,
5766
+ __spreadProps(__spreadValues({}, layout), {
5767
+ left: leftChildren.nonPinned,
5768
+ right: rightChildren.nonPinned
5769
+ })
5770
+ ]
5771
+ };
5772
+ }
5773
+ if (hasChildren(layout)) {
5774
+ const childComponents = groupLayoutByPinned(layout.components);
5775
+ return {
5776
+ pinned: [...acc.pinned, ...childComponents.pinned],
5777
+ nonPinned: [
5778
+ ...acc.nonPinned,
5779
+ __spreadProps(__spreadValues({}, layout), {
5780
+ components: childComponents.nonPinned
5781
+ })
5782
+ ]
5783
+ };
5784
+ }
5785
+ return {
5786
+ pinned: [...acc.pinned],
5787
+ nonPinned: [...acc.nonPinned, layout]
5788
+ };
5892
5789
  };
5790
+ var hasChildren = (component) => "components" in component;
5791
+ var hasColumns = (component) => "right" in component && "left" in component;
5893
5792
 
5894
- // src/domain/mappers/schema/stringSchemaToComponent/stringSchemaToComponent.ts
5895
- var stringSchemaToComponent = (schemaMapperProps, mapperProps) => {
5896
- const { schema } = schemaMapperProps;
5897
- if (isStringSchemaWithBase64(schema)) {
5898
- return stringSchemaToUploadInputComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5793
+ // src/controller/getErrorMessage.ts
5794
+ var getErrorMessage = (error) => {
5795
+ return error instanceof Error ? error.message : typeof error === "string" ? error : `Unknown Error: type is ${typeof error}`;
5796
+ };
5797
+
5798
+ // src/controller/handleErrorResponse.ts
5799
+ var handleErrorResponse = async (response, actionId, trackEvent) => {
5800
+ const body = await parseResponseBodyAsJsonElement(response.clone());
5801
+ if (isErrorResponseBody(body)) {
5802
+ const refreshUrl = body.refreshUrl || body.refreshFormUrl;
5803
+ const { error, validation, analytics } = body;
5804
+ trackEvent("Action Failed", __spreadProps(__spreadValues({}, analytics), { actionId, statusCode: response.status }));
5805
+ const errors = { error, validation };
5806
+ return refreshUrl ? { type: "refresh", body: { refreshUrl, errors } } : {
5807
+ type: "error",
5808
+ body: { errors, analytics },
5809
+ httpError: { statusCode: response.status }
5810
+ };
5899
5811
  }
5900
- switch (schema.format) {
5901
- case "date":
5902
- return stringSchemaToDateInputComponent(schemaMapperProps, mapperProps);
5903
- default:
5904
- return stringSchemaToTextInputComponent(schemaMapperProps, mapperProps);
5812
+ trackEvent("Action Failed", { actionId, statusCode: response.status });
5813
+ const errorMessage = await parseResponseBodyAsText(response);
5814
+ return {
5815
+ type: "error",
5816
+ httpError: {
5817
+ message: errorMessage || void 0,
5818
+ statusCode: response.status
5819
+ }
5820
+ };
5821
+ };
5822
+
5823
+ // src/controller/getResponseType.ts
5824
+ var responseTypes = ["step", "action", "exit", "modal", "subflow"];
5825
+ var getResponseType = (headers, body) => {
5826
+ const headerResponseType = getResponseTypeFromHeader(headers);
5827
+ if (headerResponseType) {
5828
+ return headerResponseType;
5829
+ }
5830
+ if (isObject(body) && body.action) {
5831
+ return "action";
5905
5832
  }
5833
+ return "step";
5906
5834
  };
5907
- var isStringSchemaWithBase64 = (schema) => {
5908
- return schema.format === "base64url" && !("persistAsync" in schema);
5835
+ var getResponseTypeFromHeader = (headers) => {
5836
+ if (headers == null ? void 0 : headers.has("X-Df-Response-Type")) {
5837
+ const type = headers.get("X-Df-Response-Type");
5838
+ assertDFResponseType(type);
5839
+ return type;
5840
+ }
5841
+ if (headers == null ? void 0 : headers.has("X-Df-Exit")) {
5842
+ return "exit";
5843
+ }
5844
+ return void 0;
5909
5845
  };
5910
-
5911
- // src/domain/mappers/mapSchemaToComponent.ts
5912
- var mapSchemaToComponent = (schemaMapperProps, mapperProps) => {
5913
- const { uid, schema } = schemaMapperProps;
5914
- if (isConstSchema(schema)) {
5915
- return constSchemaToComponent(uid, __spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5846
+ function assertDFResponseType(type) {
5847
+ if (!responseTypes.includes(type)) {
5848
+ throw new Error(
5849
+ "Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'error', 'modal', 'subflow'."
5850
+ );
5916
5851
  }
5917
- if (isSchemaWithPersistAsync(schema)) {
5918
- return persistAsyncSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5852
+ }
5853
+
5854
+ // src/controller/makeSafeHttpClient.ts
5855
+ var makeSafeHttpClient = (httpClient) => async (...props) => {
5856
+ try {
5857
+ return await httpClient(...props);
5858
+ } catch (e) {
5859
+ return null;
5919
5860
  }
5920
- if (isAllOfSchema(schema)) {
5921
- return allOfSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5861
+ };
5862
+
5863
+ // src/controller/executeRequest.ts
5864
+ var executeRequest = async (props) => {
5865
+ const { exit, request, requestCache, httpClient, trackEvent, logEvent } = props;
5866
+ const { url, method, body } = request;
5867
+ const response = await getCachedOrFetch(
5868
+ [
5869
+ url,
5870
+ {
5871
+ method,
5872
+ body: body ? JSON.stringify(body) : void 0,
5873
+ headers: { "Content-Type": "application/json" }
5874
+ }
5875
+ ],
5876
+ requestCache,
5877
+ httpClient
5878
+ );
5879
+ if (!response) {
5880
+ const extra = { errorMessage: "Network Error" };
5881
+ trackEvent("Request Failed", extra);
5882
+ logEvent("error", "Dynamic Flow - Request Failed Unexpectedly", extra);
5883
+ return { type: "error" };
5922
5884
  }
5923
- if (isOneOfSchema(schema)) {
5924
- return oneOfSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5885
+ if (!response.ok) {
5886
+ return handleErrorResponse(response, void 0, trackEvent);
5925
5887
  }
5926
- if (isBooleanSchema(schema)) {
5927
- return booleanSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5888
+ const responseBody = await parseResponseBodyAsJsonElement(response);
5889
+ const responseType = getResponseType(response.headers, responseBody);
5890
+ if (exit) {
5891
+ return { type: "complete", result: responseBody };
5928
5892
  }
5929
- if (isObjectSchema(schema)) {
5930
- const { format } = schema;
5931
- if (format === "money") {
5932
- return objectSchemaToMoneyInputComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5893
+ switch (responseType) {
5894
+ case "step": {
5895
+ const etag = response.headers.get("etag") || null;
5896
+ assertStepResponseBody(responseBody);
5897
+ return { type: "replace-step", step: responseBody, etag };
5898
+ }
5899
+ case "exit": {
5900
+ return { type: "complete", result: responseBody };
5901
+ }
5902
+ case "action": {
5903
+ assertActionResponseBody(responseBody);
5904
+ return {
5905
+ type: "behavior",
5906
+ behavior: {
5907
+ type: "action",
5908
+ action: responseBody.action
5909
+ }
5910
+ };
5911
+ }
5912
+ case "subflow": {
5913
+ assertSubflowResponseBody(responseBody);
5914
+ return {
5915
+ type: "behavior",
5916
+ behavior: __spreadProps(__spreadValues({}, responseBody), {
5917
+ type: "subflow",
5918
+ onCompletion: responseBody.onCompletion ? normaliseBehavior(responseBody.onCompletion, []) : void 0,
5919
+ onError: responseBody.onError ? normaliseBehavior(responseBody.onError, []) : void 0
5920
+ })
5921
+ };
5933
5922
  }
5934
- if (format != null && Object.keys(schema.properties).length === 0) {
5935
- return objectSchemaToFormattedValueComponent(
5936
- __spreadProps(__spreadValues({}, schemaMapperProps), { schema: __spreadProps(__spreadValues({}, schema), { format }) }),
5937
- mapperProps
5938
- );
5923
+ case "modal": {
5924
+ assertModalResponseBody(responseBody);
5925
+ return { type: "behavior", behavior: __spreadProps(__spreadValues({}, responseBody), { type: "modal" }) };
5926
+ }
5927
+ default: {
5928
+ throw new Error(`Unsupported response type: ${String(responseType)}`);
5939
5929
  }
5940
- return objectSchemaToObjectComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5941
- }
5942
- if (isIntegerSchema(schema)) {
5943
- return integerSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5944
- }
5945
- if (isNumberSchema(schema)) {
5946
- return numberSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5947
- }
5948
- if (isStringSchema(schema)) {
5949
- return stringSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5950
- }
5951
- if (isArraySchema(schema)) {
5952
- return arraySchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5953
5930
  }
5954
- if (isBlobSchema(schema)) {
5955
- if (!schemaMapperProps.onPersistAsync) {
5956
- throw new Error(
5957
- "Blob schemas can only be used as the schema of a persist async configuration."
5958
- );
5931
+ };
5932
+ var getCachedOrFetch = async (requestParams, requestCache, httpClient) => {
5933
+ const cachedPromise = requestCache.get(requestParams);
5934
+ if (cachedPromise) {
5935
+ const cachedResponse = await cachedPromise;
5936
+ if (cachedResponse == null ? void 0 : cachedResponse.ok) {
5937
+ return cachedResponse;
5959
5938
  }
5960
- return blobSchemaToComponent(__spreadProps(__spreadValues({}, schemaMapperProps), { schema }), mapperProps);
5961
5939
  }
5962
- throw new Error("Not yet supported");
5940
+ return makeSafeHttpClient(httpClient)(...requestParams);
5963
5941
  };
5964
5942
 
5965
- // src/domain/mappers/mapStepSchemas.ts
5966
- var mapStepSchemas = (uid, step, stepLocalValue, mapperProps, referencedSchemaIds) => {
5967
- const isReferenced = (schemaId) => schemaId != null && referencedSchemaIds.includes(schemaId);
5968
- return step.schemas.map((schema, i) => {
5943
+ // src/controller/executeSubmission.ts
5944
+ var executeSubmission = async (props) => {
5945
+ const { httpClient, requestCache, trackEvent, logEvent } = props;
5946
+ const triggerAction = async (action, model, isInitial) => {
5969
5947
  var _a, _b;
5970
- const schemaComponent = mapSchemaToComponent(
5971
- {
5972
- uid: `${uid}.schemas-${i}.${schema.$id}`,
5973
- schemaId: schema.$id,
5974
- schema,
5975
- model: (_a = step.model) != null ? _a : null,
5976
- localValue: stepLocalValue,
5977
- validationErrors: (_b = step.errors) == null ? void 0 : _b.validation,
5978
- required: true
5979
- },
5980
- mapperProps
5981
- );
5982
- return __spreadProps(__spreadValues({}, schemaComponent), {
5983
- isSchemaReferencedInStep: isReferenced(schema.$id)
5984
- });
5985
- });
5986
- };
5987
-
5988
- // src/domain/mappers/mapToolbarToComponent.ts
5989
- var mapToolbarToComponent = (uid, toolbar, mapperProps) => {
5990
- if (!toolbar) {
5991
- return void 0;
5992
- }
5993
- const { onBehavior } = mapperProps;
5994
- return __spreadProps(__spreadValues({}, toolbar), {
5995
- type: "toolbar",
5996
- uid: `${uid}.toolbar`,
5997
- tags: toolbar.tags,
5998
- items: toolbar.items.map((item) => {
5999
- const context = item.context ? mapLegacyContext(item.context) : void 0;
6000
- const behavior = getDomainLayerBehavior(
6001
- { behavior: item.behavior },
6002
- [],
6003
- mapperProps.registerSubmissionBehavior
6004
- );
6005
- return __spreadProps(__spreadValues({}, item), {
6006
- context: context ? mapLegacyContext(context) : void 0,
6007
- disabled: !!item.disabled,
6008
- tags: item.tags,
6009
- onClick: () => {
6010
- void onBehavior(behavior);
6011
- }
5948
+ const { exit, url, result = null, id: actionId } = action;
5949
+ const trackSubmissionEvent = !isInitial ? trackEvent : () => {
5950
+ };
5951
+ trackSubmissionEvent("Action Triggered", { actionId });
5952
+ if (exit && !url) {
5953
+ trackSubmissionEvent("Action Succeeded", { actionId });
5954
+ return { type: "complete", result };
5955
+ }
5956
+ try {
5957
+ const command = await executeRequest({
5958
+ exit,
5959
+ request: createRequestFromAction(action, model),
5960
+ requestCache,
5961
+ httpClient,
5962
+ trackEvent: (name, properties) => {
5963
+ trackSubmissionEvent(name, __spreadProps(__spreadValues({}, properties), { actionId }));
5964
+ },
5965
+ logEvent
6012
5966
  });
6013
- })
5967
+ switch (command.type) {
5968
+ case "error": {
5969
+ trackSubmissionEvent("Action Failed", __spreadValues({
5970
+ actionId,
5971
+ statusCode: (_a = command.httpError) == null ? void 0 : _a.statusCode
5972
+ }, (_b = command.body) == null ? void 0 : _b.analytics));
5973
+ return command;
5974
+ }
5975
+ case "behavior": {
5976
+ if (command.behavior.type === "action") {
5977
+ trackSubmissionEvent("Action Succeeded", { actionId });
5978
+ return await executeSubmission({
5979
+ action: command.behavior.action,
5980
+ isInitial: false,
5981
+ model: null,
5982
+ requestCache,
5983
+ httpClient,
5984
+ trackEvent,
5985
+ logEvent
5986
+ });
5987
+ }
5988
+ trackSubmissionEvent("Action Succeeded", { actionId });
5989
+ return command;
5990
+ }
5991
+ case "complete": {
5992
+ trackSubmissionEvent("Action Succeeded", { actionId });
5993
+ return __spreadProps(__spreadValues({}, command), { result: recursiveMerge(command.result, result) });
5994
+ }
5995
+ default: {
5996
+ trackSubmissionEvent("Action Succeeded", { actionId });
5997
+ return command;
5998
+ }
5999
+ }
6000
+ } catch (error) {
6001
+ const errorMessage = getErrorMessage(error);
6002
+ trackSubmissionEvent("Action Failed", { actionId, errorMessage });
6003
+ logEvent("error", "Dynamic Flow - Action Failed Unexpectedly", { actionId, errorMessage });
6004
+ throw error;
6005
+ }
6006
+ };
6007
+ return triggerAction(props.action, props.model, props.isInitial);
6008
+ };
6009
+ var createRequestFromAction = (action, model) => {
6010
+ var _a, _b, _c;
6011
+ return __spreadProps(__spreadValues({}, action), {
6012
+ url: (_a = action.url) != null ? _a : "",
6013
+ method: (_b = action.method) != null ? _b : "POST",
6014
+ body: action.method === "GET" ? void 0 : recursiveMerge(model, (_c = action.data) != null ? _c : null)
6014
6015
  });
6015
6016
  };
6016
6017
 
6017
- // src/domain/mappers/utils/groupLayoutByPinned.ts
6018
- var groupLayoutByPinned = (layouts) => {
6019
- return layouts.reduce(groupLayout, { pinned: [], nonPinned: [] });
6018
+ // src/domain/features/prefetch/request-cache.ts
6019
+ var makeRequestCacheWithParent = (parent) => {
6020
+ const map = /* @__PURE__ */ new Map();
6021
+ const cache = {
6022
+ get: (requestParams) => {
6023
+ var _a;
6024
+ const key = makeKey(requestParams);
6025
+ const promise = (_a = map.get(key)) != null ? _a : parent == null ? void 0 : parent.get(requestParams);
6026
+ map.delete(key);
6027
+ return promise;
6028
+ },
6029
+ set: (requestParams, responsePromise) => {
6030
+ return map.set(makeKey(requestParams), responsePromise);
6031
+ }
6032
+ };
6033
+ return cache;
6020
6034
  };
6021
- var groupLayout = (acc, layout) => {
6022
- if (layout.type === "button" && layout.pinOrder !== void 0) {
6023
- return {
6024
- pinned: [...acc.pinned, layout],
6025
- nonPinned: acc.nonPinned
6026
- };
6027
- }
6028
- if (hasColumns(layout)) {
6029
- const leftChildren = groupLayoutByPinned(layout.left);
6030
- const rightChildren = groupLayoutByPinned(layout.right);
6031
- return {
6032
- pinned: [...acc.pinned, ...leftChildren.pinned, ...rightChildren.pinned],
6033
- nonPinned: [
6034
- ...acc.nonPinned,
6035
- __spreadProps(__spreadValues({}, layout), {
6036
- left: leftChildren.nonPinned,
6037
- right: rightChildren.nonPinned
6038
- })
6039
- ]
6040
- };
6035
+ var makeRequestCache = (initialValues = []) => {
6036
+ const cache = makeRequestCacheWithParent(void 0);
6037
+ initialValues.forEach(([requestParams, responsePromise]) => {
6038
+ cache.set(requestParams, responsePromise);
6039
+ });
6040
+ return cache;
6041
+ };
6042
+ var normaliseRequestCache = (cache) => {
6043
+ if (cache === void 0) {
6044
+ return makeRequestCache();
6041
6045
  }
6042
- if (hasChildren(layout)) {
6043
- const childComponents = groupLayoutByPinned(layout.components);
6044
- return {
6045
- pinned: [...acc.pinned, ...childComponents.pinned],
6046
- nonPinned: [
6047
- ...acc.nonPinned,
6048
- __spreadProps(__spreadValues({}, layout), {
6049
- components: childComponents.nonPinned
6050
- })
6051
- ]
6052
- };
6046
+ if (isRequestCacheInstance(cache)) {
6047
+ return cache;
6053
6048
  }
6054
- return {
6055
- pinned: [...acc.pinned],
6056
- nonPinned: [...acc.nonPinned, layout]
6049
+ return makeRequestCache(cache);
6050
+ };
6051
+ var isRequestCacheInstance = (cache) => {
6052
+ return !cache || !Array.isArray(cache);
6053
+ };
6054
+ var makeKey = (requestParams) => {
6055
+ var _a, _b;
6056
+ const [input, init] = requestParams;
6057
+ const url = typeof input === "string" || input instanceof URL ? input.toString() : input.url;
6058
+ const key = JSON.stringify({
6059
+ url,
6060
+ method: (_a = init == null ? void 0 : init.method) != null ? _a : "GET",
6061
+ headers: (init == null ? void 0 : init.headers) ? Array.from(new Headers(init.headers).entries()) : [],
6062
+ body: (_b = init == null ? void 0 : init.body) != null ? _b : null
6063
+ });
6064
+ return key;
6065
+ };
6066
+
6067
+ // src/domain/features/prefetch/getStepPrefetch.ts
6068
+ var getStepPrefetch = (httpClient, flowRequestCache, submissionBehaviors) => {
6069
+ const requestCache = makeRequestCacheWithParent(flowRequestCache);
6070
+ const start = (model) => {
6071
+ submissionBehaviors.forEach((behavior) => {
6072
+ const request = behavior.type === "action" ? createRequestFromAction(behavior.action, model) : behavior.launchConfig.request;
6073
+ const requestParams = [
6074
+ request.url,
6075
+ {
6076
+ body: JSON.stringify(request.body),
6077
+ method: request.method,
6078
+ headers: { "Content-Type": "application/json" }
6079
+ }
6080
+ ];
6081
+ try {
6082
+ const responsePromise = httpClient(...requestParams).catch(() => null);
6083
+ requestCache.set(requestParams, responsePromise);
6084
+ } catch (e) {
6085
+ }
6086
+ });
6057
6087
  };
6088
+ const stop = () => {
6089
+ };
6090
+ return { requestCache, start, stop };
6058
6091
  };
6059
- var hasChildren = (component) => "components" in component;
6060
- var hasColumns = (component) => "right" in component && "left" in component;
6061
6092
 
6062
6093
  // src/domain/mappers/mapStepToComponent.ts
6063
6094
  var mapStepToComponent = (_a) => {
@@ -6091,7 +6122,6 @@ var mapStepToComponent = (_a) => {
6091
6122
  title,
6092
6123
  tags
6093
6124
  } = step;
6094
- const requestCache = makeRequestCacheWithParent(flowRequestCache);
6095
6125
  const submissionBehaviors = [];
6096
6126
  const registerSubmissionBehavior = (behavior) => {
6097
6127
  if (behavior.type === "action" && behavior.action.prefetch) {
@@ -6115,6 +6145,7 @@ var mapStepToComponent = (_a) => {
6115
6145
  registerSubmissionBehavior
6116
6146
  }) : void 0;
6117
6147
  const stepRefreshAfter = refreshAfter ? getStepRefreshAfter({ refreshAfter, logEvent, onBehavior }) : void 0;
6148
+ const stepPrefetch = getStepPrefetch(restProps.httpClient, flowRequestCache, submissionBehaviors);
6118
6149
  const externalConfirmation = (external == null ? void 0 : external.url) ? createExternalConfirmation(`${uid}-external-confirmation`, external == null ? void 0 : external.url, onComponentUpdate) : void 0;
6119
6150
  const mapperProps = __spreadProps(__spreadValues({}, restProps), { trackEvent, onBehavior, registerSubmissionBehavior });
6120
6151
  const referencedSchemaIds = getReferencedSchemaId(step);
@@ -6151,18 +6182,12 @@ var mapStepToComponent = (_a) => {
6151
6182
  title,
6152
6183
  tags,
6153
6184
  stackBehavior: (_a2 = navigation == null ? void 0 : navigation.stackBehavior) != null ? _a2 : "default",
6154
- requestCache,
6185
+ stepPrefetch,
6155
6186
  step,
6156
6187
  onComponentUpdate,
6157
6188
  trackEvent,
6158
6189
  onBehavior
6159
6190
  });
6160
- executePrefetch({
6161
- httpClient: mapperProps.httpClient,
6162
- model: stepComponent.getSubmittableValueSync(),
6163
- behaviors: submissionBehaviors,
6164
- requestCache
6165
- });
6166
6191
  return stepComponent;
6167
6192
  };
6168
6193
  var getReferencedSchemaId = (step) => {
@@ -6183,25 +6208,6 @@ var mapBackNavigation = (navigation, onBehavior, isNativeBackEnabled) => {
6183
6208
  }
6184
6209
  } : void 0;
6185
6210
  };
6186
- var executePrefetch = (props) => {
6187
- const { httpClient, behaviors: submissionBehaviors, model, requestCache } = props;
6188
- submissionBehaviors.forEach((behavior) => {
6189
- const request = behavior.type === "action" ? createRequestFromAction(behavior.action, model) : behavior.launchConfig.request;
6190
- const requestParams = [
6191
- request.url,
6192
- {
6193
- body: JSON.stringify(request.body),
6194
- method: request.method,
6195
- headers: { "Content-Type": "application/json" }
6196
- }
6197
- ];
6198
- try {
6199
- const responsePromise = httpClient(...requestParams).catch(() => null);
6200
- requestCache.set(requestParams, responsePromise);
6201
- } catch (e) {
6202
- }
6203
- });
6204
- };
6205
6211
  var getLayoutAndFooter = (step, features) => {
6206
6212
  var _a;
6207
6213
  if (step.footer) {
@@ -6807,21 +6813,24 @@ var createFlowController = (props) => {
6807
6813
  return false;
6808
6814
  }
6809
6815
  };
6810
- let isInitialised = false;
6816
+ let initState = "initial";
6811
6817
  if (initialStep) {
6812
- isInitialised = true;
6818
+ initState = "created";
6813
6819
  trackEvent("Initiated");
6814
6820
  createStep(initialStep, null);
6815
6821
  trackEvent("Step Shown", { isFirstStep: true });
6816
6822
  }
6817
6823
  const start = () => {
6818
- if (!isInitialised && initialAction) {
6819
- isInitialised = true;
6824
+ if (initState === "initial" && initialAction) {
6825
+ initState = "created";
6820
6826
  trackEvent("Initiated");
6821
6827
  rootComponent.setLoadingState("submitting");
6822
6828
  void onAction(__spreadValues({ method: "GET" }, initialAction), null);
6823
6829
  }
6824
- rootComponent.start();
6830
+ if (initState === "created") {
6831
+ initState = "started";
6832
+ rootComponent.start();
6833
+ }
6825
6834
  };
6826
6835
  return {
6827
6836
  rootComponent,