@growthbook/edge-utils 0.0.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app.d.ts +3 -0
  3. package/dist/app.js +171 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/attributes.d.ts +5 -0
  6. package/dist/attributes.js +76 -0
  7. package/dist/attributes.js.map +1 -0
  8. package/dist/config.d.ts +33 -0
  9. package/dist/config.js +91 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/domMutations.d.ts +6 -0
  12. package/dist/domMutations.js +151 -0
  13. package/dist/domMutations.js.map +1 -0
  14. package/dist/generated/sdkWrapper.d.ts +1 -0
  15. package/dist/generated/sdkWrapper.js +5 -0
  16. package/dist/generated/sdkWrapper.js.map +1 -0
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.js +16 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/inject.d.ts +16 -0
  21. package/dist/inject.js +136 -0
  22. package/dist/inject.js.map +1 -0
  23. package/dist/redirect.d.ts +11 -0
  24. package/dist/redirect.js +43 -0
  25. package/dist/redirect.js.map +1 -0
  26. package/dist/routing.d.ts +2 -0
  27. package/dist/routing.js +47 -0
  28. package/dist/routing.js.map +1 -0
  29. package/dist/stickyBucketService.d.ts +14 -0
  30. package/dist/stickyBucketService.js +49 -0
  31. package/dist/stickyBucketService.js.map +1 -0
  32. package/dist/types.d.ts +60 -0
  33. package/dist/types.js +4 -0
  34. package/dist/types.js.map +1 -0
  35. package/package.json +31 -0
  36. package/scripts/generate-sdk-wrapper.js +22 -0
  37. package/src/app.ts +210 -0
  38. package/src/attributes.ts +97 -0
  39. package/src/config.ts +166 -0
  40. package/src/domMutations.ts +157 -0
  41. package/src/generated/sdkWrapper.ts +2 -0
  42. package/src/index.ts +13 -0
  43. package/src/inject.ts +230 -0
  44. package/src/redirect.ts +53 -0
  45. package/src/routing.ts +44 -0
  46. package/src/stickyBucketService.ts +48 -0
  47. package/src/types.ts +98 -0
  48. package/tsconfig.json +27 -0
@@ -0,0 +1,60 @@
1
+ import { Attributes, FeatureApiResponse, LocalStorageCompat, StickyBucketService, TrackingCallback } from "@growthbook/growthbook";
2
+ export interface Context<Req = unknown, Res = unknown> {
3
+ config: Config;
4
+ helpers: Helpers<Req, Res>;
5
+ }
6
+ export interface Config {
7
+ proxyTarget: string;
8
+ forwardProxyHeaders: boolean;
9
+ environment: string;
10
+ maxPayloadSize?: string;
11
+ routes?: Route[];
12
+ runVisualEditorExperiments: ExperimentRunEnvironment;
13
+ disableJsInjection: boolean;
14
+ runUrlRedirectExperiments: ExperimentRunEnvironment;
15
+ runCrossOriginUrlRedirectExperiments: ExperimentRunEnvironment;
16
+ injectRedirectUrlScript: boolean;
17
+ maxRedirects: number;
18
+ scriptInjectionPattern: string;
19
+ disableInjections: boolean;
20
+ enableStreaming: boolean;
21
+ enableStickyBucketing: boolean;
22
+ stickyBucketPrefix?: string;
23
+ contentSecurityPolicy?: string;
24
+ nonce?: string;
25
+ crypto?: any;
26
+ localStorage?: LocalStorageCompat;
27
+ growthbook: {
28
+ apiHost: string;
29
+ clientKey: string;
30
+ decryptionKey?: string;
31
+ trackingCallback?: string;
32
+ edgeTrackingCallback?: TrackingCallback;
33
+ attributes?: Attributes;
34
+ edgeStickyBucketService?: StickyBucketService;
35
+ payload?: FeatureApiResponse;
36
+ };
37
+ persistUuid: boolean;
38
+ uuidCookieName: string;
39
+ uuidKey: string;
40
+ skipAutoAttributes: boolean;
41
+ }
42
+ export type ExperimentRunEnvironment = "everywhere" | "edge" | "browser" | "skip";
43
+ export interface Helpers<Req, Res> {
44
+ getRequestURL?: (req: Req) => string;
45
+ getRequestMethod?: (req: Req) => string;
46
+ getRequestHeader?: (req: Req, key: string) => string | undefined;
47
+ sendResponse?: (ctx: Context<Req, Res>, res?: Res, headers?: Record<string, any>, body?: string, cookies?: Record<string, string>, status?: number) => unknown;
48
+ fetch?: (ctx: Context<Req, Res>, url: string) => Promise<Res>;
49
+ proxyRequest?: (ctx: Context<Req, Res>, req: Req, res?: Res, next?: any) => Promise<unknown>;
50
+ getCookie?: (req: Req, key: string) => string;
51
+ setCookie?: (res: Res, key: string, value: string) => void;
52
+ }
53
+ export type Route = {
54
+ pattern: string;
55
+ type?: "regex" | "simple";
56
+ behavior?: "intercept" | "proxy" | "error";
57
+ includeFileExtensions?: boolean;
58
+ statusCode?: number;
59
+ body?: string;
60
+ };
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,uDAAuD"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@growthbook/edge-utils",
3
+ "description": "Edge worker base app",
4
+ "version": "0.0.1",
5
+ "main": "dist/index.js",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/growthbook/growthbook-proxy.git",
10
+ "directory": "packages/shared/edge-utils"
11
+ },
12
+ "author": "Bryce Fitzsimons",
13
+ "scripts": {
14
+ "build:clean": "rimraf -rf dist",
15
+ "build:typescript": "tsc",
16
+ "build": "yarn build:clean && yarn generate-sdk-wrapper && yarn build:typescript",
17
+ "generate-sdk-wrapper": "node scripts/generate-sdk-wrapper.js",
18
+ "type-check": "tsc --pretty --noEmit",
19
+ "dev": "node scripts/generate-sdk-wrapper.js && tsc --watch"
20
+ },
21
+ "dependencies": {
22
+ "@growthbook/growthbook": "^1.0.0",
23
+ "node-html-parser": "^6.1.13"
24
+ },
25
+ "devDependencies": {
26
+ "@types/jsdom": "^21.1.6",
27
+ "@types/node": "^20.8.2",
28
+ "rimraf": "^5.0.5",
29
+ "typescript": "5.2.2"
30
+ }
31
+ }
@@ -0,0 +1,22 @@
1
+ const fs = require("fs");
2
+
3
+ // Read the vendor .js file
4
+ const vendorScriptPath =
5
+ "./node_modules/@growthbook/growthbook/dist/bundles/auto.min.js";
6
+ const vendorScriptContent = fs
7
+ .readFileSync(vendorScriptPath, "utf8")
8
+ .replace(/\\/g, "\\\\") // Escape backslashes
9
+ .replace(/'/g, "\\'") // Escape single quotes
10
+ .replace(/"/g, '\\"') // Escape double quotes
11
+ .replace(/\n/g, "\\n"); // Escape newlines
12
+
13
+ // Generate TypeScript file with embedded content
14
+ const outputFile = "./src/generated/sdkWrapper.ts";
15
+ fs.writeFileSync(
16
+ outputFile,
17
+ `
18
+ export const sdkWrapper = "${vendorScriptContent}";
19
+ `,
20
+ );
21
+
22
+ console.log("Vendor script file generated successfully.");
package/src/app.ts ADDED
@@ -0,0 +1,210 @@
1
+ import {
2
+ AutoExperimentVariation,
3
+ GrowthBook,
4
+ setPolyfills,
5
+ StickyBucketService,
6
+ } from "@growthbook/growthbook";
7
+ import { Context } from "./types";
8
+ import { getUserAttributes } from "./attributes";
9
+ import { getCspInfo, injectScript } from "./inject";
10
+ import { applyDomMutations } from "./domMutations";
11
+ import redirect from "./redirect";
12
+ import { getRoute } from "./routing";
13
+ import { EdgeStickyBucketService } from "./stickyBucketService";
14
+
15
+ export async function edgeApp<Req, Res>(
16
+ context: Context<Req, Res>,
17
+ req: Req,
18
+ res?: Res,
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ next?: any,
21
+ ) {
22
+ // todo: import default helpers, overwrite with context helpers
23
+
24
+ let url = context.helpers.getRequestURL?.(req) || "";
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ let headers: Record<string, any> = {
28
+ "Content-Type": "text/html",
29
+ };
30
+ const cookies: Record<string, string> = {};
31
+ const setCookie = (key: string, value: string) => {
32
+ cookies[key] = value;
33
+ };
34
+ const { csp, nonce } = getCspInfo(context as Context<unknown, unknown>);
35
+ if (csp) {
36
+ headers["Content-Security-Policy"] = csp;
37
+ }
38
+ let body = "";
39
+
40
+ // Non GET requests are proxied
41
+ if (context.helpers.getRequestMethod?.(req) !== "GET") {
42
+ return context.helpers.proxyRequest?.(context, req, res, next);
43
+ }
44
+ // Check the url for routing rules (default behavior is intercept)
45
+ const route = getRoute(context as Context<unknown, unknown>, url);
46
+ if (route.behavior === "error") {
47
+ return context.helpers.sendResponse?.(
48
+ context,
49
+ res,
50
+ headers,
51
+ route.body || "",
52
+ cookies,
53
+ route.statusCode,
54
+ );
55
+ }
56
+ if (route.behavior === "proxy") {
57
+ return context.helpers.proxyRequest?.(context, req, res, next);
58
+ }
59
+
60
+ const attributes = getUserAttributes(context, req, url, setCookie);
61
+
62
+ let domChanges: AutoExperimentVariation[] = [];
63
+ const resetDomChanges = () => (domChanges = []);
64
+
65
+ let preRedirectChangeIds: string[] = [];
66
+ const setPreRedirectChangeIds = (changeIds: string[]) =>
67
+ (preRedirectChangeIds = changeIds);
68
+
69
+ context.config.localStorage &&
70
+ setPolyfills({ localStorage: context.config.localStorage });
71
+ context.config.crypto &&
72
+ setPolyfills({ SubtleCrypto: context.config.crypto });
73
+
74
+ let stickyBucketService:
75
+ | EdgeStickyBucketService<Req, Res>
76
+ | StickyBucketService
77
+ | undefined = undefined;
78
+ if (context.config.enableStickyBucketing) {
79
+ stickyBucketService =
80
+ context.config.growthbook.edgeStickyBucketService ??
81
+ new EdgeStickyBucketService<Req, Res>({
82
+ context,
83
+ prefix: context.config.stickyBucketPrefix,
84
+ req,
85
+ });
86
+ }
87
+ const growthbook = new GrowthBook({
88
+ apiHost: context.config.growthbook.apiHost,
89
+ clientKey: context.config.growthbook.clientKey,
90
+ decryptionKey: context.config.growthbook.decryptionKey,
91
+ attributes,
92
+ applyDomChangesCallback: (changes: AutoExperimentVariation) => {
93
+ domChanges.push(changes);
94
+ return () => {};
95
+ },
96
+ url,
97
+ disableVisualExperiments: ["skip", "browser"].includes(
98
+ context.config.runVisualEditorExperiments,
99
+ ),
100
+ disableJsInjection: context.config.disableJsInjection,
101
+ disableUrlRedirectExperiments: ["skip", "browser"].includes(
102
+ context.config.runUrlRedirectExperiments,
103
+ ),
104
+ disableCrossOriginUrlRedirectExperiments: ["skip", "browser"].includes(
105
+ context.config.runCrossOriginUrlRedirectExperiments,
106
+ ),
107
+ stickyBucketService,
108
+ trackingCallback: context.config.disableInjections
109
+ ? context.config.growthbook.edgeTrackingCallback
110
+ : undefined,
111
+ debug: true, // todo: remove
112
+ });
113
+
114
+ await growthbook.init({
115
+ payload: context.config.growthbook.payload,
116
+ });
117
+
118
+ const oldUrl = url;
119
+ url = await redirect({
120
+ context: context as Context<unknown, unknown>,
121
+ req,
122
+ setCookie,
123
+ growthbook,
124
+ previousUrl: url,
125
+ resetDomChanges,
126
+ setPreRedirectChangeIds: setPreRedirectChangeIds,
127
+ });
128
+
129
+ const originUrl = getOriginUrl(context as Context<unknown, unknown>, url);
130
+
131
+ /* eslint-disable @typescript-eslint/no-explicit-any */
132
+ let fetchedResponse:
133
+ | (Res & { ok: boolean; headers: Record<string, any>; text: any })
134
+ | undefined = undefined;
135
+ try {
136
+ fetchedResponse = (await context.helpers.fetch?.(
137
+ context as Context<Req, Res>,
138
+ originUrl,
139
+ /* eslint-disable @typescript-eslint/no-explicit-any */
140
+ )) as Res & { ok: boolean; headers: Record<string, any>; text: any };
141
+ if (!fetchedResponse?.ok) {
142
+ throw new Error("Fetch: non-2xx status returned");
143
+ }
144
+ } catch (e) {
145
+ console.error(e);
146
+ return context.helpers.sendResponse?.(
147
+ context,
148
+ res,
149
+ headers,
150
+ "Error fetching page",
151
+ cookies,
152
+ 500,
153
+ );
154
+ }
155
+ if (context.config.forwardProxyHeaders && fetchedResponse?.headers) {
156
+ headers = { ...fetchedResponse.headers, ...headers };
157
+ }
158
+ body = await fetchedResponse.text();
159
+
160
+ body = await applyDomMutations({
161
+ body,
162
+ nonce,
163
+ domChanges,
164
+ });
165
+
166
+ body = injectScript({
167
+ context: context as Context<unknown, unknown>,
168
+ body,
169
+ nonce,
170
+ growthbook,
171
+ attributes,
172
+ preRedirectChangeIds,
173
+ url,
174
+ oldUrl,
175
+ });
176
+
177
+ return context.helpers.sendResponse?.(context, res, headers, body, cookies);
178
+ }
179
+
180
+ export function getOriginUrl(context: Context, currentURL: string): string {
181
+ const proxyTarget = context.config.proxyTarget;
182
+ const currentParsedURL = new URL(currentURL);
183
+ const proxyParsedURL = new URL(proxyTarget);
184
+
185
+ const protocol = proxyParsedURL.protocol
186
+ ? proxyParsedURL.protocol
187
+ : currentParsedURL.protocol;
188
+ const hostname = proxyParsedURL.hostname
189
+ ? proxyParsedURL.hostname
190
+ : currentParsedURL.hostname;
191
+ const port = proxyParsedURL.port
192
+ ? proxyParsedURL.port
193
+ : protocol === "http:"
194
+ ? "80"
195
+ : "443";
196
+
197
+ let newURL = `${protocol}//${hostname}`;
198
+ if ((protocol === "http" && port !== "80") || port !== "443") {
199
+ newURL += `:${port}`;
200
+ }
201
+ newURL += `${currentParsedURL.pathname}`;
202
+ if (currentParsedURL.search) {
203
+ newURL += currentParsedURL.search;
204
+ }
205
+ if (currentParsedURL.hash) {
206
+ newURL += currentParsedURL.hash;
207
+ }
208
+
209
+ return newURL;
210
+ }
@@ -0,0 +1,97 @@
1
+ import { Attributes } from "@growthbook/growthbook";
2
+ import { Context } from "./types";
3
+
4
+ // Get the user's attributes by merging the UUID cookie with any auto-attributes
5
+ export function getUserAttributes<Req, Res>(
6
+ ctx: Context<Req, Res>,
7
+ req: Req,
8
+ url: string,
9
+ setCookie: (key: string, value: string) => void,
10
+ ): Attributes {
11
+ const { config, helpers } = ctx;
12
+
13
+ const providedAttributes = config.growthbook.attributes || {};
14
+ if (config.skipAutoAttributes) {
15
+ return providedAttributes;
16
+ }
17
+ // get any saved attributes from the cookie
18
+ const uuid = getUUID(ctx, req);
19
+ if (config.persistUuid) {
20
+ if (!helpers?.setCookie) {
21
+ throw new Error("Missing required dependencies");
22
+ }
23
+ setCookie(config.uuidCookieName, uuid);
24
+ }
25
+
26
+ const autoAttributes = getAutoAttributes(ctx, req, url);
27
+ return { ...autoAttributes, ...providedAttributes };
28
+ }
29
+
30
+ // Get or create a UUID for the user:
31
+ // - Try to get the UUID from the cookie
32
+ // - Or create a new one and store in the cookie
33
+ export function getUUID<Req, Res>(ctx: Context<Req, Res>, req: Req) {
34
+ const { config, helpers } = ctx;
35
+
36
+ const crypto = config?.crypto || globalThis?.crypto;
37
+
38
+ if (!crypto || !helpers?.getCookie) {
39
+ throw new Error("Missing required dependencies");
40
+ }
41
+
42
+ const genUUID = () => {
43
+ if (crypto.randomUUID) return crypto.randomUUID();
44
+ return ("" + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
45
+ (
46
+ (c as unknown as number) ^
47
+ (crypto.getRandomValues(new Uint8Array(1))[0] &
48
+ (15 >> ((c as unknown as number) / 4)))
49
+ ).toString(16),
50
+ );
51
+ };
52
+
53
+ // get the existing UUID from cookie if set, otherwise create one
54
+ return helpers.getCookie(req, config.uuidCookieName) || genUUID();
55
+ }
56
+
57
+ // Infer attributes from the request
58
+ // - UUID will come from the cookie or be generated
59
+ // - Other attributes come from the request headers and URL
60
+ export function getAutoAttributes<Req, Res>(
61
+ ctx: Context<Req, Res>,
62
+ req: Req,
63
+ url: string,
64
+ ): Attributes {
65
+ const { config, helpers } = ctx;
66
+
67
+ const getHeader = helpers?.getRequestHeader;
68
+
69
+ const autoAttributes: Attributes = {
70
+ [config.uuidKey]: getUUID(ctx, req),
71
+ };
72
+
73
+ const ua = getHeader?.(req, "user-agent") || "";
74
+ autoAttributes.browser = ua.match(/Edg/)
75
+ ? "edge"
76
+ : ua.match(/Chrome/)
77
+ ? "chrome"
78
+ : ua.match(/Firefox/)
79
+ ? "firefox"
80
+ : ua.match(/Safari/)
81
+ ? "safari"
82
+ : "unknown";
83
+ autoAttributes.deviceType = ua.match(/Mobi/) ? "mobile" : "desktop";
84
+
85
+ autoAttributes.url = url;
86
+
87
+ try {
88
+ const urlObj = new URL(url);
89
+ autoAttributes.path = urlObj.pathname;
90
+ autoAttributes.host = urlObj.host;
91
+ autoAttributes.query = urlObj.search;
92
+ } catch (e) {
93
+ // ignore
94
+ }
95
+
96
+ return autoAttributes;
97
+ }
package/src/config.ts ADDED
@@ -0,0 +1,166 @@
1
+ import { Config, Context, ExperimentRunEnvironment } from "./types";
2
+
3
+ export const defaultContext: Context = {
4
+ config: {
5
+ proxyTarget: "/",
6
+ forwardProxyHeaders: true,
7
+ environment: "production",
8
+ maxPayloadSize: "2mb",
9
+ runVisualEditorExperiments: "everywhere",
10
+ disableJsInjection: false,
11
+ runUrlRedirectExperiments: "browser",
12
+ runCrossOriginUrlRedirectExperiments: "browser",
13
+ injectRedirectUrlScript: true,
14
+ maxRedirects: 5,
15
+ scriptInjectionPattern: "</head>",
16
+ disableInjections: false,
17
+ enableStreaming: false,
18
+ enableStickyBucketing: false,
19
+ growthbook: {
20
+ apiHost: "",
21
+ clientKey: "",
22
+ },
23
+ persistUuid: false,
24
+ uuidCookieName: "gbuuid",
25
+ uuidKey: "id",
26
+ skipAutoAttributes: false,
27
+ },
28
+ helpers: {},
29
+ };
30
+
31
+ export interface ConfigEnv {
32
+ PROXY_TARGET?: string;
33
+ FORWARD_PROXY_HEADERS?: string;
34
+ NODE_ENV?: string;
35
+ MAX_PAYLOAD_SIZE?: string;
36
+
37
+ ROUTES?: string;
38
+
39
+ RUN_VISUAL_EDITOR_EXPERIMENTS?: ExperimentRunEnvironment;
40
+ DISABLE_JS_INJECTION?: string;
41
+
42
+ RUN_URL_REDIRECT_EXPERIMENTS?: ExperimentRunEnvironment;
43
+ RUN_CROSS_ORIGIN_URL_REDIRECT_EXPERIMENTS?: ExperimentRunEnvironment;
44
+ INJECT_REDIRECT_URL_SCRIPT?: string;
45
+ MAX_REDIRECTS?: string;
46
+
47
+ SCRIPT_INJECTION_PATTERN?: string;
48
+ DISABLE_INJECTIONS?: string;
49
+
50
+ ENABLE_STREAMING?: string;
51
+ ENABLE_STICKY_BUCKETING?: string;
52
+ STICKY_BUCKET_PREFIX?: string;
53
+
54
+ CONTENT_SECURITY_POLICY?: string;
55
+ NONCE?: string;
56
+
57
+ GROWTHBOOK_API_HOST?: string;
58
+ GROWTHBOOK_CLIENT_KEY?: string;
59
+ GROWTHBOOK_DECRYPTION_KEY?: string;
60
+ GROWTHBOOK_TRACKING_CALLBACK?: string;
61
+ GROWTHBOOK_PAYLOAD?: string;
62
+
63
+ PERSIST_UUID?: string;
64
+ UUID_COOKIE_NAME?: string;
65
+ UUID_KEY?: string;
66
+
67
+ SKIP_AUTO_ATTRIBUTES?: string;
68
+
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ [key: string]: any;
71
+ }
72
+
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ export function getConfig(env: ConfigEnv): Config {
75
+ const config = defaultContext.config;
76
+
77
+ config.proxyTarget = env.PROXY_TARGET ?? defaultContext.config.proxyTarget;
78
+ config.forwardProxyHeaders = ["true", "1"].includes(
79
+ env.FORWARD_PROXY_HEADERS ?? "" + defaultContext.config.forwardProxyHeaders,
80
+ );
81
+ config.environment = env.NODE_ENV ?? defaultContext.config.environment;
82
+ config.maxPayloadSize =
83
+ env.MAX_PAYLOAD_SIZE ?? defaultContext.config.maxPayloadSize;
84
+
85
+ try {
86
+ config.routes = JSON.parse(env.ROUTES || "[]");
87
+ } catch (e) {
88
+ console.error("Error parsing ROUTES", e);
89
+ config.routes = [];
90
+ }
91
+
92
+ config.runVisualEditorExperiments = (env.RUN_VISUAL_EDITOR_EXPERIMENTS ??
93
+ defaultContext.config
94
+ .runVisualEditorExperiments) as ExperimentRunEnvironment;
95
+ config.disableJsInjection = ["true", "1"].includes(
96
+ env.DISABLE_JS_INJECTION ?? "" + defaultContext.config.disableJsInjection,
97
+ );
98
+
99
+ config.runUrlRedirectExperiments = (env.RUN_URL_REDIRECT_EXPERIMENTS ??
100
+ defaultContext.config
101
+ .runUrlRedirectExperiments) as ExperimentRunEnvironment;
102
+ config.runCrossOriginUrlRedirectExperiments =
103
+ (env.RUN_CROSS_ORIGIN_URL_REDIRECT_EXPERIMENTS ??
104
+ defaultContext.config
105
+ .runCrossOriginUrlRedirectExperiments) as ExperimentRunEnvironment;
106
+ config.injectRedirectUrlScript = ["true", "1"].includes(
107
+ env.INJECT_REDIRECT_URL_SCRIPT ??
108
+ "" + defaultContext.config.injectRedirectUrlScript,
109
+ );
110
+ config.maxRedirects = parseInt(
111
+ env.MAX_REDIRECTS || "" + defaultContext.config.maxRedirects,
112
+ );
113
+
114
+ config.scriptInjectionPattern =
115
+ env.SCRIPT_INJECTION_PATTERN ||
116
+ defaultContext.config.scriptInjectionPattern;
117
+ config.disableInjections = ["true", "1"].includes(
118
+ env.DISABLE_INJECTIONS ?? "" + defaultContext.config.disableInjections,
119
+ );
120
+
121
+ config.enableStreaming = ["true", "1"].includes(
122
+ env.ENABLE_STREAMING ?? "" + defaultContext.config.enableStreaming,
123
+ );
124
+ config.enableStickyBucketing = ["true", "1"].includes(
125
+ env.ENABLE_STICKY_BUCKETING ??
126
+ "" + defaultContext.config.enableStickyBucketing,
127
+ );
128
+ "STICKY_BUCKET_PREFIX" in env &&
129
+ (config.stickyBucketPrefix = env.STICKY_BUCKET_PREFIX);
130
+
131
+ config.contentSecurityPolicy = env.CONTENT_SECURITY_POLICY || "";
132
+ // warning: for testing only; nonce should be unique per request
133
+ config.nonce = env.NONCE || undefined;
134
+
135
+ config.crypto = crypto;
136
+
137
+ // config.growthbook
138
+ config.growthbook.apiHost = (env.GROWTHBOOK_API_HOST ?? "").replace(
139
+ /\/*$/,
140
+ "",
141
+ );
142
+ config.growthbook.clientKey = env.GROWTHBOOK_CLIENT_KEY ?? "";
143
+ "GROWTHBOOK_DECRYPTION_KEY" in env &&
144
+ (config.growthbook.decryptionKey = env.GROWTHBOOK_DECRYPTION_KEY);
145
+ "GROWTHBOOK_TRACKING_CALLBACK" in env &&
146
+ (config.growthbook.trackingCallback = env.GROWTHBOOK_TRACKING_CALLBACK);
147
+ try {
148
+ "GROWTHBOOK_PAYLOAD" in env &&
149
+ (config.growthbook.payload = JSON.parse(env.GROWTHBOOK_PAYLOAD || ""));
150
+ } catch (e) {
151
+ console.error("Error parsing GROWTHBOOK_PAYLOAD", e);
152
+ }
153
+
154
+ config.persistUuid = ["true", "1"].includes(
155
+ env.PERSIST_UUID ?? "" + defaultContext.config.persistUuid,
156
+ );
157
+ config.uuidCookieName =
158
+ env.UUID_COOKIE_NAME || defaultContext.config.uuidCookieName;
159
+ config.uuidKey = env.UUID_KEY || defaultContext.config.uuidKey;
160
+
161
+ config.skipAutoAttributes = ["true", "1"].includes(
162
+ env.SKIP_AUTO_ATTRIBUTES ?? "" + defaultContext.config.skipAutoAttributes,
163
+ );
164
+
165
+ return config;
166
+ }