@growthbook/edge-utils 0.2.4 → 0.2.6
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/README.md +7 -3
- package/dist/app.js +60 -26
- package/dist/app.js.map +1 -1
- package/dist/attributes.d.ts +2 -2
- package/dist/attributes.js +20 -13
- package/dist/attributes.js.map +1 -1
- package/dist/config.d.ts +5 -1
- package/dist/config.js +26 -17
- package/dist/config.js.map +1 -1
- package/dist/generated/sdkWrapper.d.ts +1 -1
- package/dist/generated/sdkWrapper.js +1 -1
- package/dist/generated/sdkWrapper.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/redirect.js +1 -1
- package/dist/redirect.js.map +1 -1
- package/dist/types.d.ts +10 -1
- package/package.json +2 -2
- package/src/app.ts +65 -21
- package/src/attributes.ts +19 -11
- package/src/config.ts +24 -1
- package/src/generated/sdkWrapper.ts +1 -1
- package/src/index.ts +1 -0
- package/src/redirect.ts +1 -1
- package/src/types.ts +21 -1
package/dist/types.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface Config {
|
|
|
11
11
|
forwardProxyHeaders: boolean;
|
|
12
12
|
useDefaultContentType: boolean;
|
|
13
13
|
processTextHtmlOnly: boolean;
|
|
14
|
+
autoInflate: boolean;
|
|
15
|
+
nocacheOrigin: boolean;
|
|
14
16
|
environment: string;
|
|
15
17
|
maxPayloadSize?: string;
|
|
16
18
|
routes?: Route[];
|
|
@@ -21,6 +23,7 @@ export interface Config {
|
|
|
21
23
|
runCrossOriginUrlRedirectExperiments: ExperimentRunEnvironment;
|
|
22
24
|
injectRedirectUrlScript: boolean;
|
|
23
25
|
maxRedirects: number;
|
|
26
|
+
experimentUrlTargeting?: ExperimentUrlTargeting;
|
|
24
27
|
scriptInjectionPattern: string;
|
|
25
28
|
disableInjections: boolean;
|
|
26
29
|
enableStreaming: boolean;
|
|
@@ -36,6 +39,7 @@ export interface Config {
|
|
|
36
39
|
clientKey: string;
|
|
37
40
|
headers?: Record<string, string>;
|
|
38
41
|
}) => Promise<any>;
|
|
42
|
+
emitTraceHeaders?: boolean;
|
|
39
43
|
apiHost: string;
|
|
40
44
|
clientKey: string;
|
|
41
45
|
decryptionKey?: string;
|
|
@@ -51,12 +55,16 @@ export interface Config {
|
|
|
51
55
|
skipAutoAttributes: boolean;
|
|
52
56
|
}
|
|
53
57
|
export type ExperimentRunEnvironment = "everywhere" | "edge" | "browser" | "skip";
|
|
58
|
+
export type ExperimentUrlTargeting = "request" | "origin";
|
|
59
|
+
export type FetchOptions = {
|
|
60
|
+
additionalHeaders?: Record<string, any>;
|
|
61
|
+
};
|
|
54
62
|
export interface Helpers<Req, Res> {
|
|
55
63
|
getRequestURL: (req: Req) => string;
|
|
56
64
|
getRequestMethod: (req: Req) => string;
|
|
57
65
|
getRequestHeader?: (req: Req, key: string) => string | undefined;
|
|
58
66
|
sendResponse: (ctx: Context<Req, Res>, res?: Res, headers?: Record<string, any>, body?: string, cookies?: Record<string, string>, status?: number) => unknown;
|
|
59
|
-
fetch: (ctx: Context<Req, Res>, url: string, req: Req) => Promise<Res>;
|
|
67
|
+
fetch: (ctx: Context<Req, Res>, url: string, req: Req, options?: FetchOptions) => Promise<Res>;
|
|
60
68
|
proxyRequest: (ctx: Context<Req, Res>, req: Req, res?: Res, next?: any) => Promise<unknown>;
|
|
61
69
|
getCookie?: (req: Req, key: string) => string;
|
|
62
70
|
setCookie?: (res: Res, key: string, value: string) => void;
|
|
@@ -68,6 +76,7 @@ export type BaseHookParams<Req, Res> = {
|
|
|
68
76
|
next?: any;
|
|
69
77
|
requestUrl: string;
|
|
70
78
|
originUrl: string;
|
|
79
|
+
requestCount: number;
|
|
71
80
|
};
|
|
72
81
|
export type OnRouteParams<Req, Res> = BaseHookParams<Req, Res> & {
|
|
73
82
|
route: Route;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growthbook/edge-utils",
|
|
3
3
|
"description": "Edge worker base app",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dev": "node scripts/generate-sdk-wrapper.js && tsc --watch"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@growthbook/growthbook": "^1.
|
|
22
|
+
"@growthbook/growthbook": "^1.6.1",
|
|
23
23
|
"node-html-parser": "^7.0.1",
|
|
24
24
|
"pako": "^2.1.0"
|
|
25
25
|
},
|
package/src/app.ts
CHANGED
|
@@ -5,7 +5,17 @@ import {
|
|
|
5
5
|
setPolyfills,
|
|
6
6
|
StickyBucketService
|
|
7
7
|
} from "@growthbook/growthbook";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
BaseHookParams,
|
|
10
|
+
Context,
|
|
11
|
+
OnRouteParams,
|
|
12
|
+
OnUserAttributesParams,
|
|
13
|
+
OnGrowthBookInitParams,
|
|
14
|
+
OnBeforeOriginFetchParams,
|
|
15
|
+
OnOriginFetchParams,
|
|
16
|
+
OnBodyReadyParams,
|
|
17
|
+
OnBeforeResponseParams
|
|
18
|
+
} from "./types";
|
|
9
19
|
import { getUserAttributes } from "./attributes";
|
|
10
20
|
import { getCspInfo, injectScript } from "./inject";
|
|
11
21
|
import { applyDomMutations } from "./domMutations";
|
|
@@ -42,9 +52,20 @@ export async function edgeApp<Req, Res>(
|
|
|
42
52
|
const setRespCookie = (key: string, value: string) => {
|
|
43
53
|
respCookies[key] = value;
|
|
44
54
|
};
|
|
55
|
+
|
|
56
|
+
// Loop check
|
|
57
|
+
const requestCount = parseInt(context.helpers?.getRequestHeader?.(req, "x-gb-request-count") || "0") + 1;
|
|
58
|
+
if (requestCount > 1) {
|
|
59
|
+
console.error("Edge request loop detected. Count: " + requestCount, requestUrl);
|
|
60
|
+
}
|
|
61
|
+
if (requestCount > context.config.maxRedirects) {
|
|
62
|
+
throw new Error("Edge request loop: max requests reached: " + requestCount);
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
// Initial hook:
|
|
46
66
|
let hookResp: Res | undefined | void;
|
|
47
|
-
|
|
67
|
+
let onRequestParams: BaseHookParams<Req, Res> = { context, req, res, next, requestUrl, originUrl, requestCount };
|
|
68
|
+
hookResp = await context?.hooks?.onRequest?.(onRequestParams);
|
|
48
69
|
if (hookResp) return hookResp;
|
|
49
70
|
|
|
50
71
|
// DOM mutations
|
|
@@ -72,7 +93,8 @@ export async function edgeApp<Req, Res>(
|
|
|
72
93
|
return context.helpers.proxyRequest(context, req, res, next);
|
|
73
94
|
}
|
|
74
95
|
// Custom route behavior via hook:
|
|
75
|
-
|
|
96
|
+
const onRouteParams: OnRouteParams<Req, Res> = { ...onRequestParams, route };
|
|
97
|
+
hookResp = await context?.hooks?.onRoute?.(onRouteParams);
|
|
76
98
|
if (hookResp) return hookResp;
|
|
77
99
|
|
|
78
100
|
/**
|
|
@@ -81,7 +103,8 @@ export async function edgeApp<Req, Res>(
|
|
|
81
103
|
const attributes = getUserAttributes(context, req, requestUrl, setRespCookie);
|
|
82
104
|
|
|
83
105
|
// Hook to allow enriching user attributes, etc
|
|
84
|
-
|
|
106
|
+
const onUserAttributesParams: OnUserAttributesParams<Req, Res> = { ...onRouteParams, attributes };
|
|
107
|
+
hookResp = await context?.hooks?.onUserAttributes?.(onUserAttributesParams);
|
|
85
108
|
if (hookResp) return hookResp;
|
|
86
109
|
|
|
87
110
|
/**
|
|
@@ -116,7 +139,7 @@ export async function edgeApp<Req, Res>(
|
|
|
116
139
|
return () => {
|
|
117
140
|
};
|
|
118
141
|
},
|
|
119
|
-
url: requestUrl,
|
|
142
|
+
url: context.config.experimentUrlTargeting === "origin" ? originUrl: requestUrl,
|
|
120
143
|
disableVisualExperiments: ["skip", "browser"].includes(
|
|
121
144
|
context.config.runVisualEditorExperiments,
|
|
122
145
|
),
|
|
@@ -136,7 +159,8 @@ export async function edgeApp<Req, Res>(
|
|
|
136
159
|
});
|
|
137
160
|
|
|
138
161
|
// Hook to perform any custom logic given the initialized SDK
|
|
139
|
-
|
|
162
|
+
const onGrowthbookInitParams: OnGrowthBookInitParams<Req, Res> = { ...onUserAttributesParams, growthbook };
|
|
163
|
+
hookResp = await context?.hooks?.onGrowthbookInit?.(onGrowthbookInitParams);
|
|
140
164
|
if (hookResp) return hookResp;
|
|
141
165
|
|
|
142
166
|
/**
|
|
@@ -147,14 +171,15 @@ export async function edgeApp<Req, Res>(
|
|
|
147
171
|
req,
|
|
148
172
|
setRespCookie,
|
|
149
173
|
growthbook,
|
|
150
|
-
previousUrl: requestUrl,
|
|
174
|
+
previousUrl: context.config.experimentUrlTargeting === "origin" ? originUrl : requestUrl,
|
|
151
175
|
resetDomChanges,
|
|
152
176
|
setPreRedirectChangeIds: setPreRedirectChangeIds,
|
|
153
177
|
});
|
|
154
178
|
originUrl = getOriginUrl(context, redirectRequestUrl, redirectRequestUrl !== requestUrl);
|
|
155
179
|
|
|
156
180
|
// Pre-origin-fetch hook (after redirect logic):
|
|
157
|
-
|
|
181
|
+
const onBeforeOriginFetchParams: OnBeforeOriginFetchParams<Req, Res> = { ...onGrowthbookInitParams, redirectRequestUrl, originUrl };
|
|
182
|
+
hookResp = await context?.hooks?.onBeforeOriginFetch?.(onBeforeOriginFetchParams);
|
|
158
183
|
if (hookResp) return hookResp;
|
|
159
184
|
|
|
160
185
|
/**
|
|
@@ -165,6 +190,12 @@ export async function edgeApp<Req, Res>(
|
|
|
165
190
|
context,
|
|
166
191
|
originUrl,
|
|
167
192
|
req,
|
|
193
|
+
context.config.emitTraceHeaders ? {
|
|
194
|
+
additionalHeaders: {
|
|
195
|
+
"x-gb-request-count": ("" + requestCount),
|
|
196
|
+
"x-gbuuid": growthbook.getAttributes()?.[context.config.uuidKey],
|
|
197
|
+
}
|
|
198
|
+
} : undefined,
|
|
168
199
|
) as OriginResponse & Res;
|
|
169
200
|
} catch (e) {
|
|
170
201
|
console.error(e);
|
|
@@ -172,12 +203,12 @@ export async function edgeApp<Req, Res>(
|
|
|
172
203
|
const originStatus = originResponse ? parseInt(originResponse.status ? originResponse.status + "" : "400") : 500;
|
|
173
204
|
|
|
174
205
|
// On fetch hook (for custom response processing, etc)
|
|
175
|
-
|
|
206
|
+
const onOriginFetchParams: OnOriginFetchParams<Req, Res> = { ...onBeforeOriginFetchParams, originResponse, originStatus };
|
|
207
|
+
hookResp = await context?.hooks?.onOriginFetch?.(onOriginFetchParams);
|
|
176
208
|
if (hookResp) return hookResp;
|
|
177
209
|
|
|
178
210
|
// Standard error response handling
|
|
179
211
|
if (originStatus >= 500 || !originResponse) {
|
|
180
|
-
console.error("Fetch: 5xx status returned");
|
|
181
212
|
return context.helpers.sendResponse(context, res, {}, "Error fetching page", {}, 500);
|
|
182
213
|
}
|
|
183
214
|
if (originStatus >= 400) {
|
|
@@ -204,18 +235,29 @@ export async function edgeApp<Req, Res>(
|
|
|
204
235
|
resHeaders["content-security-policy"] = csp;
|
|
205
236
|
}
|
|
206
237
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
238
|
+
const autoInflate = context.config.autoInflate; // fastly only!!!
|
|
239
|
+
let body = "";
|
|
240
|
+
const isGzipped = originHeaders["content-encoding"] === "gzip";
|
|
241
|
+
try {
|
|
242
|
+
const buffer = await originResponse.arrayBuffer();
|
|
243
|
+
try {
|
|
244
|
+
if (isGzipped && autoInflate) {
|
|
245
|
+
body = pako.inflate(new Uint8Array(buffer), { to: "string" });
|
|
246
|
+
} else {
|
|
247
|
+
body = new TextDecoder().decode(buffer);
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
if (isGzipped) {
|
|
214
251
|
body = pako.inflate(new Uint8Array(buffer), { to: "string" });
|
|
215
|
-
}
|
|
216
|
-
|
|
252
|
+
} else {
|
|
253
|
+
throw new Error("Failed to decode response as text.");
|
|
217
254
|
}
|
|
218
255
|
}
|
|
256
|
+
if (isGzipped) {
|
|
257
|
+
delete resHeaders["content-encoding"]; // Remove to prevent double decompression
|
|
258
|
+
}
|
|
259
|
+
} catch (e) {
|
|
260
|
+
console.error("Response decoding error", e);
|
|
219
261
|
}
|
|
220
262
|
let setBody = (s: string) => {
|
|
221
263
|
body = s;
|
|
@@ -227,7 +269,8 @@ export async function edgeApp<Req, Res>(
|
|
|
227
269
|
}
|
|
228
270
|
|
|
229
271
|
// Body ready hook (pre-DOM-mutations):
|
|
230
|
-
|
|
272
|
+
const onBodyReadyParams: OnBodyReadyParams<Req, Res> = { ...onOriginFetchParams, originHeaders, resHeaders, body, setBody, root };
|
|
273
|
+
hookResp = await context?.hooks?.onBodyReady?.(onBodyReadyParams);
|
|
231
274
|
if (hookResp) return hookResp;
|
|
232
275
|
|
|
233
276
|
/**
|
|
@@ -257,7 +300,8 @@ export async function edgeApp<Req, Res>(
|
|
|
257
300
|
});
|
|
258
301
|
|
|
259
302
|
// Final hook (post-mutations) before sending back
|
|
260
|
-
|
|
303
|
+
const onBeforeResponseParams: OnBeforeResponseParams<Req, Res> = { ...onOriginFetchParams, originHeaders, resHeaders, body, setBody };
|
|
304
|
+
hookResp = await context?.hooks?.onBeforeResponse?.(onBeforeResponseParams);
|
|
261
305
|
if (hookResp) return hookResp;
|
|
262
306
|
|
|
263
307
|
/**
|
package/src/attributes.ts
CHANGED
|
@@ -7,6 +7,7 @@ export function getUserAttributes<Req, Res>(
|
|
|
7
7
|
req: Req,
|
|
8
8
|
url: string,
|
|
9
9
|
setRespCookie: (key: string, value: string) => void,
|
|
10
|
+
skipUuid: boolean = false,
|
|
10
11
|
): Attributes {
|
|
11
12
|
const { config, helpers } = ctx;
|
|
12
13
|
|
|
@@ -14,16 +15,18 @@ export function getUserAttributes<Req, Res>(
|
|
|
14
15
|
if (config.skipAutoAttributes) {
|
|
15
16
|
return providedAttributes;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!
|
|
21
|
-
|
|
18
|
+
if (!skipUuid) {
|
|
19
|
+
// get any saved attributes from the cookie
|
|
20
|
+
const uuid = getUUID(ctx, req);
|
|
21
|
+
if (config.persistUuid && !config.noAutoCookies) {
|
|
22
|
+
if (!helpers?.setCookie) {
|
|
23
|
+
throw new Error("Missing required dependencies");
|
|
24
|
+
}
|
|
25
|
+
setRespCookie(config.uuidCookieName, uuid);
|
|
22
26
|
}
|
|
23
|
-
setRespCookie(config.uuidCookieName, uuid);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
const autoAttributes = getAutoAttributes(ctx, req, url);
|
|
29
|
+
const autoAttributes = getAutoAttributes(ctx, req, url, skipUuid);
|
|
27
30
|
return { ...autoAttributes, ...providedAttributes };
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -50,7 +53,9 @@ export function getUUID<Req, Res>(ctx: Context<Req, Res>, req: Req) {
|
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
// get the existing UUID from cookie if set, otherwise create one
|
|
53
|
-
return helpers.getCookie(req, config.uuidCookieName)
|
|
56
|
+
return helpers.getCookie(req, config.uuidCookieName)
|
|
57
|
+
|| helpers?.getRequestHeader?.(req, "x-gbuuid")
|
|
58
|
+
|| genUUID();
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
// Infer attributes from the request
|
|
@@ -60,14 +65,17 @@ export function getAutoAttributes<Req, Res>(
|
|
|
60
65
|
ctx: Context<Req, Res>,
|
|
61
66
|
req: Req,
|
|
62
67
|
url: string,
|
|
68
|
+
skipUuid: boolean = false,
|
|
63
69
|
): Attributes {
|
|
64
70
|
const { config, helpers } = ctx;
|
|
65
71
|
|
|
66
72
|
const getHeader = helpers?.getRequestHeader;
|
|
67
73
|
|
|
68
|
-
let autoAttributes: Attributes =
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
let autoAttributes: Attributes = skipUuid
|
|
75
|
+
? {}
|
|
76
|
+
: {
|
|
77
|
+
[config.uuidKey]: getUUID(ctx, req),
|
|
78
|
+
};
|
|
71
79
|
|
|
72
80
|
const ua = getHeader?.(req, "user-agent") || "";
|
|
73
81
|
autoAttributes.browser = ua.match(/Edg/)
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Config, Context, ExperimentRunEnvironment } from "./types";
|
|
1
|
+
import { Config, Context, ExperimentRunEnvironment, ExperimentUrlTargeting } from "./types";
|
|
2
2
|
|
|
3
3
|
type Req = any; // placeholder
|
|
4
4
|
type Res = any; // placeholder
|
|
@@ -10,6 +10,8 @@ export const defaultContext: Context<Req, Res> = {
|
|
|
10
10
|
followRedirects: true,
|
|
11
11
|
useDefaultContentType: false,
|
|
12
12
|
processTextHtmlOnly: true,
|
|
13
|
+
autoInflate: false,
|
|
14
|
+
nocacheOrigin: false,
|
|
13
15
|
environment: "production",
|
|
14
16
|
maxPayloadSize: "2mb",
|
|
15
17
|
runVisualEditorExperiments: "everywhere",
|
|
@@ -19,10 +21,12 @@ export const defaultContext: Context<Req, Res> = {
|
|
|
19
21
|
runCrossOriginUrlRedirectExperiments: "browser",
|
|
20
22
|
injectRedirectUrlScript: true,
|
|
21
23
|
maxRedirects: 5,
|
|
24
|
+
experimentUrlTargeting: "request",
|
|
22
25
|
scriptInjectionPattern: "</head>",
|
|
23
26
|
disableInjections: false,
|
|
24
27
|
enableStreaming: false,
|
|
25
28
|
enableStickyBucketing: false,
|
|
29
|
+
emitTraceHeaders: true,
|
|
26
30
|
apiHost: "",
|
|
27
31
|
clientKey: "",
|
|
28
32
|
persistUuid: false,
|
|
@@ -57,6 +61,8 @@ export interface ConfigEnv {
|
|
|
57
61
|
FOLLOW_REDIRECTS?: string;
|
|
58
62
|
USE_DEFAULT_CONTENT_TYPE?: string;
|
|
59
63
|
PROCESS_TEXT_HTML_ONLY?: string;
|
|
64
|
+
AUTO_INFLATE?: string;
|
|
65
|
+
NOCACHE_ORIGIN?: string;
|
|
60
66
|
NODE_ENV?: string;
|
|
61
67
|
MAX_PAYLOAD_SIZE?: string;
|
|
62
68
|
|
|
@@ -71,6 +77,8 @@ export interface ConfigEnv {
|
|
|
71
77
|
INJECT_REDIRECT_URL_SCRIPT?: string;
|
|
72
78
|
MAX_REDIRECTS?: string;
|
|
73
79
|
|
|
80
|
+
EXPERIMENT_URL_TARGETING?: ExperimentUrlTargeting;
|
|
81
|
+
|
|
74
82
|
SCRIPT_INJECTION_PATTERN?: string;
|
|
75
83
|
DISABLE_INJECTIONS?: string;
|
|
76
84
|
|
|
@@ -80,6 +88,7 @@ export interface ConfigEnv {
|
|
|
80
88
|
|
|
81
89
|
CONTENT_SECURITY_POLICY?: string;
|
|
82
90
|
NONCE?: string;
|
|
91
|
+
EMIT_TRACE_HEADERS?: string;
|
|
83
92
|
|
|
84
93
|
GROWTHBOOK_API_HOST?: string;
|
|
85
94
|
GROWTHBOOK_CLIENT_KEY?: string;
|
|
@@ -116,6 +125,12 @@ export function getConfig(env: ConfigEnv): Config {
|
|
|
116
125
|
config.processTextHtmlOnly = ["true", "1"].includes(
|
|
117
126
|
env.PROCESS_TEXT_HTML_ONLY ?? "" + defaultContext.config.processTextHtmlOnly,
|
|
118
127
|
);
|
|
128
|
+
config.autoInflate = ["true", "1"].includes(
|
|
129
|
+
env.AUTO_INFLATE ?? "" + defaultContext.config.autoInflate,
|
|
130
|
+
);
|
|
131
|
+
config.nocacheOrigin = ["true", "1"].includes(
|
|
132
|
+
env.NOCACHE_ORIGIN ?? "" + defaultContext.config.nocacheOrigin,
|
|
133
|
+
);
|
|
119
134
|
config.environment = env.NODE_ENV ?? defaultContext.config.environment;
|
|
120
135
|
config.maxPayloadSize =
|
|
121
136
|
env.MAX_PAYLOAD_SIZE ?? defaultContext.config.maxPayloadSize;
|
|
@@ -152,6 +167,10 @@ export function getConfig(env: ConfigEnv): Config {
|
|
|
152
167
|
env.MAX_REDIRECTS || "" + defaultContext.config.maxRedirects,
|
|
153
168
|
);
|
|
154
169
|
|
|
170
|
+
config.experimentUrlTargeting = (env.EXPERIMENT_URL_TARGETING ??
|
|
171
|
+
defaultContext.config
|
|
172
|
+
.experimentUrlTargeting) as ExperimentUrlTargeting;
|
|
173
|
+
|
|
155
174
|
config.scriptInjectionPattern =
|
|
156
175
|
env.SCRIPT_INJECTION_PATTERN ||
|
|
157
176
|
defaultContext.config.scriptInjectionPattern;
|
|
@@ -175,6 +194,10 @@ export function getConfig(env: ConfigEnv): Config {
|
|
|
175
194
|
|
|
176
195
|
config.crypto = crypto;
|
|
177
196
|
|
|
197
|
+
config.emitTraceHeaders = ["true", "1"].includes(
|
|
198
|
+
env.EMIT_TRACE_HEADERS ?? "" + defaultContext.config.emitTraceHeaders,
|
|
199
|
+
);
|
|
200
|
+
|
|
178
201
|
// growthbook
|
|
179
202
|
config.apiHost = (env.GROWTHBOOK_API_HOST ?? "").replace(/\/*$/, "");
|
|
180
203
|
config.clientKey = env.GROWTHBOOK_CLIENT_KEY ?? "";
|