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