Skip to content

Commit c9385e6

Browse files
feat(js,react): custom fetch option (#651)
* feat(js,react): custom fetch option * @flows/react@1.22.2-canary.0 * @flows/js@1.21.1-canary.1 * fix: hook deps * feat: improvements --------- Co-authored-by: flows-bot[bot] <170794745+flows-bot[bot]@users.noreply.github.com>
1 parent 98ad767 commit c9385e6

20 files changed

Lines changed: 225 additions & 91 deletions

File tree

pnpm-lock.yaml

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workspaces/e2e/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"react-router": "^7.14.1"
1818
},
1919
"devDependencies": {
20-
"@playwright/test": "^1.59.0",
20+
"@playwright/test": "^1.59.1",
2121
"@types/node": "^24",
2222
"@types/react": "^19.2.14",
2323
"@types/react-dom": "^19.2.3",

workspaces/e2e/pages/js.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Action as IAction } from "@flows/js";
1+
import type { CustomFetch, Action as IAction, LanguageOption } from "@flows/js";
22
import {
33
init,
44
resetAllWorkflowsProgress,
@@ -13,13 +13,26 @@ import * as _surveyComponents from "@flows/js-components/survey-components";
1313
import "@flows/js-components/index.css";
1414
import type { StateMemory as IStateMemory } from "@flows/js";
1515
import { startWorkflow } from "@flows/js";
16-
import type { LanguageOption } from "@flows/shared";
1716
import { css, LitElement } from "lit";
1817
import { property } from "lit/decorators.js";
1918
import type { FlowsProperties } from "@flows/js";
2019
import { html, unsafeStatic } from "lit/static-html.js";
2120

21+
const customFetchFn: CustomFetch = (url, options) => {
22+
return fetch(url, {
23+
...options,
24+
headers: {
25+
...(options?.headers as Record<string, string>),
26+
"x-test-header": "my-custom-value",
27+
},
28+
});
29+
};
30+
2231
const apiUrl = new URLSearchParams(window.location.search).get("apiUrl") ?? undefined;
32+
const customFetch =
33+
new URLSearchParams(window.location.search).get("customFetch") === "true"
34+
? customFetchFn
35+
: undefined;
2336
const noCurrentBlocks =
2437
new URLSearchParams(window.location.search).get("noCurrentBlocks") === "true";
2538
const language = new URLSearchParams(window.location.search).get("language") as LanguageOption;
@@ -117,6 +130,7 @@ init({
117130
userId: "testUserId",
118131
language,
119132
apiUrl,
133+
customFetch,
120134
userProperties: {
121135
email: "test@flows.sh",
122136
age: 10,

workspaces/e2e/pages/react.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type {
33
StateMemory as IStateMemory,
44
Action as IAction,
55
LinkComponentType,
6+
CustomFetch,
7+
LanguageOption,
68
} from "@flows/react";
79
import {
810
FlowsProvider,
@@ -23,9 +25,22 @@ import * as components from "@flows/react-components";
2325
import * as tourComponents from "@flows/react-components/tour";
2426
import * as surveyComponents from "@flows/react-components/survey";
2527
import "@flows/react-components/index.css";
26-
import type { LanguageOption } from "@flows/shared";
28+
29+
const customFetchFn: CustomFetch = (url, options) => {
30+
return fetch(url, {
31+
...options,
32+
headers: {
33+
...(options?.headers as Record<string, string>),
34+
"x-test-header": "my-custom-value",
35+
},
36+
});
37+
};
2738

2839
const apiUrl = new URLSearchParams(window.location.search).get("apiUrl") ?? undefined;
40+
const customFetch =
41+
new URLSearchParams(window.location.search).get("customFetch") === "true"
42+
? customFetchFn
43+
: undefined;
2944
const noUserId = new URLSearchParams(window.location.search).get("noUserId") === "true";
3045
const noCurrentBlocks =
3146
new URLSearchParams(window.location.search).get("noCurrentBlocks") === "true";
@@ -149,6 +164,7 @@ createRoot(document.getElementById("root")!).render(
149164
age: 10,
150165
}}
151166
apiUrl={apiUrl}
167+
customFetch={customFetch}
152168
components={{ ...components, Card, BlockTrigger, StateMemory, Action }}
153169
tourComponents={{ ...tourComponents, Card, Action }}
154170
surveyComponents={{ ...surveyComponents }}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { test } from "@playwright/test";
2+
import { mockBlocksEndpoint } from "./utils";
3+
import { randomUUID } from "crypto";
4+
import type { Block } from "@flows/shared";
5+
6+
test.beforeEach(async ({ page }) => {
7+
await page.routeWebSocket(
8+
(url) => url.pathname === "/ws/sdk/block-updates",
9+
() => {},
10+
);
11+
});
12+
13+
const getBlock = (): Block => ({
14+
id: randomUUID(),
15+
workflowId: randomUUID(),
16+
type: "component",
17+
componentType: "BasicsV2Modal",
18+
data: { title: "Workflow block", body: "" },
19+
exitNodes: ["continue"],
20+
slottable: false,
21+
propertyMeta: [
22+
{
23+
type: "action",
24+
key: "primaryButton",
25+
value: { label: "Continue" },
26+
},
27+
],
28+
});
29+
30+
const run = (packageName: string) => {
31+
test(`${packageName} - should add custom header with custom fetch`, async ({ page }) => {
32+
await mockBlocksEndpoint(page, [getBlock()]);
33+
const blocksReq = page.waitForRequest((req) => {
34+
return (
35+
req.url() === "https://api.flows-cloud.com/v2/sdk/blocks" &&
36+
req.headers()["x-test-header"] === "my-custom-value"
37+
);
38+
});
39+
await page.goto(`/${packageName}.html?customFetch=true`);
40+
await blocksReq;
41+
const eventReq = page.waitForRequest((req) => {
42+
return (
43+
req.url() === "https://api.flows-cloud.com/v2/sdk/events" &&
44+
req.headers()["x-test-header"] === "my-custom-value"
45+
);
46+
});
47+
await page.getByText("Continue", { exact: true }).click();
48+
await eventReq;
49+
});
50+
test(`${packageName} - custom header should not be present without custom fetch`, async ({
51+
page,
52+
}) => {
53+
await mockBlocksEndpoint(page, [getBlock()]);
54+
const blocksReq = page.waitForRequest((req) => {
55+
return (
56+
req.url() === "https://api.flows-cloud.com/v2/sdk/blocks" &&
57+
req.headers()["x-test-header"] === undefined
58+
);
59+
});
60+
await page.goto(`/${packageName}.html?customFetch=false`);
61+
await blocksReq;
62+
const eventReq = page.waitForRequest((req) => {
63+
return (
64+
req.url() === "https://api.flows-cloud.com/v2/sdk/events" &&
65+
req.headers()["x-test-header"] === undefined
66+
);
67+
});
68+
await page.getByText("Continue", { exact: true }).click();
69+
await eventReq;
70+
});
71+
};
72+
73+
run("js");
74+
run("react");

workspaces/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"product-adoption",
2222
"product-tours"
2323
],
24-
"version": "1.21.1-canary.0",
24+
"version": "1.21.1-canary.1",
2525
"repository": {
2626
"type": "git",
2727
"url": "git+https://github.com/RBND-studio/flows-sdk.git"

workspaces/js/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type {
44
BlockState,
55
FlowsProperties,
66
StateMemory,
7+
CustomFetch,
78
// Localization
89
Locale,
910
LanguageOption,

workspaces/js/src/init.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,8 @@ let locationChangeInterval: number | null = null;
1616
export const init = ({ debug, onDebugShortcut, ...options }: FlowsOptions): void => {
1717
const apiUrl = options.apiUrl ?? "https://api.flows-cloud.com";
1818
config.value = { ...options, apiUrl };
19-
const { environment, organizationId, userId, userProperties, language } = options;
2019

21-
connectToWebsocketAndFetchBlocks({
22-
apiUrl,
23-
environment,
24-
organizationId,
25-
userId,
26-
userProperties,
27-
language,
28-
});
20+
connectToWebsocketAndFetchBlocks();
2921

3022
if (locationChangeInterval !== null) clearInterval(locationChangeInterval);
3123
locationChangeInterval = window.setInterval(() => {

workspaces/js/src/lib/api.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ type SendEventProps = Pick<
1111
export const sendEvent = async (props: SendEventProps): Promise<void> => {
1212
const configuration = config.value;
1313
if (!configuration) return;
14-
const { environment, organizationId, userId, apiUrl } = configuration;
15-
await getApi(apiUrl, packageAndVersion).sendEvent({
14+
const { environment, organizationId, userId, apiUrl, customFetch } = configuration;
15+
await getApi({ apiUrl, version: packageAndVersion, customFetch }).sendEvent({
1616
...props,
1717
environment,
1818
organizationId,
@@ -25,8 +25,8 @@ export const postSurvey = async (
2525
) => {
2626
const configuration = config.value;
2727
if (!configuration) return;
28-
const { apiUrl, environment, organizationId, userId } = configuration;
29-
await getApi(apiUrl, packageAndVersion).postSurvey({
28+
const { apiUrl, environment, organizationId, userId, customFetch } = configuration;
29+
await getApi({ apiUrl, version: packageAndVersion, customFetch }).postSurvey({
3030
...props,
3131
environment,
3232
organizationId,
@@ -54,6 +54,10 @@ export const fetchWorkflows = async (): Promise<WorkflowsResponse> => {
5454
return { workflows: [] };
5555
}
5656

57-
const { environment, organizationId, userId, apiUrl } = configuration;
58-
return getApi(apiUrl, packageAndVersion).getWorkflows({ environment, organizationId, userId });
57+
const { environment, organizationId, userId, apiUrl, customFetch } = configuration;
58+
return getApi({ apiUrl, version: packageAndVersion, customFetch }).getWorkflows({
59+
environment,
60+
organizationId,
61+
userId,
62+
});
5963
};

workspaces/js/src/lib/blocks.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,20 @@ import {
22
applyUpdateMessageToBlocksState,
33
getApi,
44
getUserLanguage,
5-
type LanguageOption,
65
log,
76
parseWebsocketMessage,
8-
type UserProperties,
97
} from "@flows/shared";
10-
import { blocks, blocksError, blocksState, pendingMessages } from "../store";
8+
import { blocks, blocksError, blocksState, config, pendingMessages } from "../store";
119
import { type Disconnect, websocket } from "./websocket";
1210
import { packageAndVersion } from "./constants";
1311

14-
interface Props {
15-
apiUrl: string;
16-
environment: string;
17-
organizationId: string;
18-
userId: string;
19-
userProperties?: UserProperties;
20-
language?: LanguageOption;
21-
}
22-
2312
let disconnect: Disconnect | null = null;
2413

25-
export const connectToWebsocketAndFetchBlocks = (props: Props): void => {
26-
const { environment, organizationId, userId, apiUrl } = props;
14+
export const connectToWebsocketAndFetchBlocks = (): void => {
15+
const configuration = config.value;
16+
if (!configuration) return;
17+
18+
const { environment, organizationId, userId, apiUrl, customFetch } = configuration;
2719
const params = { environment, organizationId, userId };
2820
const wsUrl = (() => {
2921
const wsBase = apiUrl.replace("https://", "wss://").replace("http://", "ws://");
@@ -32,11 +24,11 @@ export const connectToWebsocketAndFetchBlocks = (props: Props): void => {
3224

3325
const fetchBlocks = (): void => {
3426
blocksError.value = false;
35-
void getApi(apiUrl, packageAndVersion)
27+
void getApi({ apiUrl, version: packageAndVersion, customFetch })
3628
.getBlocks({
3729
...params,
38-
language: getUserLanguage(props.language),
39-
userProperties: props.userProperties,
30+
language: getUserLanguage(configuration.language),
31+
userProperties: configuration.userProperties,
4032
})
4133
.then((res) => {
4234
const blocksWithUpdates = pendingMessages.value.reduce(

0 commit comments

Comments
 (0)