@growthbook/proxy
Advanced tools
Comparing version 1.1.3 to 1.1.5
@@ -50,2 +50,3 @@ "use strict"; | ||
const logger_1 = __importStar(require("./services/logger")); | ||
const stickyBucket_1 = require("./services/stickyBucket"); | ||
let build; | ||
@@ -90,2 +91,3 @@ function getBuild() { | ||
enableRemoteEval: true, | ||
enableStickyBucketing: false, | ||
proxyAllRequests: false, | ||
@@ -107,2 +109,5 @@ }; | ||
ctx.enableCache && (yield (0, cache_1.initializeCache)(ctx)); | ||
ctx.enableRemoteEval && | ||
ctx.enableStickyBucketing && | ||
(yield (0, stickyBucket_1.initializeStickyBucketService)(ctx)); | ||
ctx.enableEventStream && (0, eventStreamManager_1.initializeEventStreamManager)(ctx); | ||
@@ -109,0 +114,0 @@ // set up handlers |
@@ -20,2 +20,3 @@ "use strict"; | ||
const cache_1 = require("../services/cache"); | ||
const stickyBucket_1 = require("../services/stickyBucket"); | ||
const registrar_1 = require("../services/registrar"); | ||
@@ -135,3 +136,3 @@ const apiKeyMiddleware_1 = require("../middleware/apiKeyMiddleware"); | ||
const url = (_f = req.body) === null || _f === void 0 ? void 0 : _f.url; | ||
payload = (0, proxy_eval_1.evaluateFeatures)({ | ||
payload = yield (0, proxy_eval_1.evaluateFeatures)({ | ||
payload, | ||
@@ -142,2 +143,3 @@ attributes, | ||
url, | ||
stickyBucketService: stickyBucket_1.stickyBucketService, | ||
ctx: (_g = req.app.locals) === null || _g === void 0 ? void 0 : _g.ctx, | ||
@@ -144,0 +146,0 @@ }); |
@@ -45,16 +45,16 @@ "use strict"; | ||
exports.default = () => __awaiter(void 0, void 0, void 0, function* () { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t; | ||
const context = { | ||
growthbookApiHost: process.env.GROWTHBOOK_API_HOST, | ||
growthbookApiHost: ((_a = process.env.GROWTHBOOK_API_HOST) !== null && _a !== void 0 ? _a : "").replace(/\/*$/, ""), | ||
secretApiKey: process.env.SECRET_API_KEY, | ||
environment: process.env.NODE_ENV, | ||
enableAdmin: ["true", "1"].includes((_a = process.env.ENABLE_ADMIN) !== null && _a !== void 0 ? _a : "0"), | ||
enableAdmin: ["true", "1"].includes((_b = process.env.ENABLE_ADMIN) !== null && _b !== void 0 ? _b : "0"), | ||
adminKey: process.env.ADMIN_KEY, | ||
multiOrg: ["true", "1"].includes((_b = process.env.MULTI_ORG) !== null && _b !== void 0 ? _b : "0"), | ||
verboseDebugging: ["true", "1"].includes((_c = process.env.VERBOSE_DEBUGGING) !== null && _c !== void 0 ? _c : "0"), | ||
maxPayloadSize: (_d = process.env.MAX_PAYLOAD_SIZE) !== null && _d !== void 0 ? _d : exports.MAX_PAYLOAD_SIZE, | ||
multiOrg: ["true", "1"].includes((_c = process.env.MULTI_ORG) !== null && _c !== void 0 ? _c : "0"), | ||
verboseDebugging: ["true", "1"].includes((_d = process.env.VERBOSE_DEBUGGING) !== null && _d !== void 0 ? _d : "0"), | ||
maxPayloadSize: (_e = process.env.MAX_PAYLOAD_SIZE) !== null && _e !== void 0 ? _e : exports.MAX_PAYLOAD_SIZE, | ||
// SDK Connections settings: | ||
createConnectionsFromEnv: ["true", "1"].includes((_e = process.env.CREATE_CONNECTIONS_FROM_ENV) !== null && _e !== void 0 ? _e : "1"), | ||
pollForConnections: ["true", "1"].includes((_f = process.env.POLL_FOR_CONNECTIONS) !== null && _f !== void 0 ? _f : "1"), | ||
connectionPollingFrequency: parseInt((_g = process.env.CONNECTION_POLLING_FREQUENCY) !== null && _g !== void 0 ? _g : "60000"), | ||
createConnectionsFromEnv: ["true", "1"].includes((_f = process.env.CREATE_CONNECTIONS_FROM_ENV) !== null && _f !== void 0 ? _f : "1"), | ||
pollForConnections: ["true", "1"].includes((_g = process.env.POLL_FOR_CONNECTIONS) !== null && _g !== void 0 ? _g : "1"), | ||
connectionPollingFrequency: parseInt((_h = process.env.CONNECTION_POLLING_FREQUENCY) !== null && _h !== void 0 ? _h : "60000"), | ||
// Cache settings: | ||
@@ -65,3 +65,3 @@ cacheSettings: { | ||
expiresTTL: parseInt(process.env.CACHE_EXPIRES_TTL || "600"), | ||
allowStale: ["true", "1"].includes((_h = process.env.CACHE_ALLOW_STALE) !== null && _h !== void 0 ? _h : "1"), | ||
allowStale: ["true", "1"].includes((_j = process.env.CACHE_ALLOW_STALE) !== null && _j !== void 0 ? _j : "1"), | ||
connectionUrl: process.env.CACHE_CONNECTION_URL, | ||
@@ -73,5 +73,5 @@ useAdditionalMemoryCache: true, | ||
// Redis only - pub/sub: | ||
publishPayloadToChannel: ["true", "1"].includes((_j = process.env.PUBLISH_PAYLOAD_TO_CHANNEL) !== null && _j !== void 0 ? _j : "0"), | ||
publishPayloadToChannel: ["true", "1"].includes((_k = process.env.PUBLISH_PAYLOAD_TO_CHANNEL) !== null && _k !== void 0 ? _k : "0"), | ||
// Redis only - cluster: | ||
useCluster: ["true", "1"].includes((_k = process.env.USE_CLUSTER) !== null && _k !== void 0 ? _k : "0"), | ||
useCluster: ["true", "1"].includes((_l = process.env.USE_CLUSTER) !== null && _l !== void 0 ? _l : "0"), | ||
clusterRootNodesJSON: process.env.CLUSTER_ROOT_NODES_JSON | ||
@@ -85,8 +85,23 @@ ? JSON.parse(process.env.CLUSTER_ROOT_NODES_JSON) | ||
// SSE settings: | ||
enableEventStream: ["true", "1"].includes((_l = process.env.ENABLE_EVENT_STREAM) !== null && _l !== void 0 ? _l : "1"), | ||
enableEventStreamHeaders: ["true", "1"].includes((_m = process.env.ENABLE_EVENT_STREAM_HEADERS) !== null && _m !== void 0 ? _m : "1"), | ||
eventStreamMaxDurationMs: parseInt((_o = process.env.EVENT_STREAM_MAX_DURATION_MS) !== null && _o !== void 0 ? _o : "60000"), | ||
eventStreamPingIntervalMs: parseInt((_p = process.env.EVENT_STREAM_PING_INTERVAL_MS) !== null && _p !== void 0 ? _p : "30000"), | ||
enableEventStream: ["true", "1"].includes((_m = process.env.ENABLE_EVENT_STREAM) !== null && _m !== void 0 ? _m : "1"), | ||
enableEventStreamHeaders: ["true", "1"].includes((_o = process.env.ENABLE_EVENT_STREAM_HEADERS) !== null && _o !== void 0 ? _o : "1"), | ||
eventStreamMaxDurationMs: parseInt((_p = process.env.EVENT_STREAM_MAX_DURATION_MS) !== null && _p !== void 0 ? _p : "60000"), | ||
eventStreamPingIntervalMs: parseInt((_q = process.env.EVENT_STREAM_PING_INTERVAL_MS) !== null && _q !== void 0 ? _q : "30000"), | ||
// Remote eval settings: | ||
enableRemoteEval: ["true", "1"].includes((_q = process.env.ENABLE_REMOTE_EVAL) !== null && _q !== void 0 ? _q : "1"), | ||
enableRemoteEval: ["true", "1"].includes((_r = process.env.ENABLE_REMOTE_EVAL) !== null && _r !== void 0 ? _r : "1"), | ||
// Sticky Bucket settings (for remote eval): | ||
enableStickyBucketing: ["true", "1"].includes((_s = process.env.ENABLE_STICKY_BUCKETING) !== null && _s !== void 0 ? _s : "0"), | ||
stickyBucketSettings: { | ||
engine: (process.env.STICKY_BUCKET_ENGINE || | ||
"none"), | ||
connectionUrl: process.env.STICKY_BUCKET_CONNECTION_URL, | ||
// Redis only - cluster: | ||
useCluster: ["true", "1"].includes((_t = process.env.STICKY_BUCKET_USE_CLUSTER) !== null && _t !== void 0 ? _t : "0"), | ||
clusterRootNodesJSON: process.env.STICKY_BUCKET_CLUSTER_ROOT_NODES_JSON | ||
? JSON.parse(process.env.STICKY_BUCKET_CLUSTER_ROOT_NODES_JSON) | ||
: undefined, | ||
clusterOptionsJSON: process.env.STICKY_BUCKET_CLUSTER_OPTIONS_JSON | ||
? JSON.parse(process.env.STICKY_BUCKET_CLUSTER_OPTIONS_JSON) | ||
: undefined, | ||
}, | ||
}; | ||
@@ -93,0 +108,0 @@ // Express configuration consts: |
@@ -8,4 +8,4 @@ import { Context } from "../../types"; | ||
}): Promise<{ | ||
payload: any; | ||
payload: {}; | ||
oldEntry: import("../cache").CacheEntry | undefined; | ||
} | undefined>; |
@@ -79,8 +79,22 @@ "use strict"; | ||
promise = fetch(url, fetchOptions) | ||
.then((resp) => resp.json()) | ||
.catch((e) => logger_1.default.error(e, "Refresh stale cache error")) | ||
.then((resp) => { | ||
if (!resp.ok) { | ||
throw new Error(`HTTP error: ${resp.status}`); | ||
} | ||
return resp.json(); | ||
}) | ||
.catch((e) => { | ||
logger_1.default.error(e, "Refresh stale cache error"); | ||
return Promise.reject(e); | ||
}) | ||
.finally(() => delete activeFetches[url]); | ||
activeFetches[url] = promise; | ||
} | ||
const payload = yield promise; | ||
let payload = null; | ||
try { | ||
payload = yield promise; | ||
} | ||
catch (e) { | ||
// ignore, logging handled in promise | ||
} | ||
if (payload) { | ||
@@ -87,0 +101,0 @@ logger_1.default.debug("cache STALE, refreshing cache..."); |
@@ -95,3 +95,3 @@ "use strict"; | ||
const maxPages = 10; | ||
let respConnections = {}; | ||
const respConnections = {}; | ||
while (page <= maxPages) { | ||
@@ -98,0 +98,0 @@ page++; |
@@ -6,2 +6,3 @@ import { Express } from "express"; | ||
import { FeaturesCache, CacheSettings } from "./services/cache"; | ||
import { StickyBucketSettings } from "./services/stickyBucket"; | ||
export interface GrowthBookProxy { | ||
@@ -29,2 +30,5 @@ app: Express; | ||
} & CacheSettings; | ||
stickyBucketSettings?: { | ||
engine: StickyBucketEngine; | ||
} & StickyBucketSettings; | ||
enableHealthCheck?: boolean; | ||
@@ -40,2 +44,3 @@ enableCors?: boolean; | ||
enableRemoteEval?: boolean; | ||
enableStickyBucketing?: boolean; | ||
proxyAllRequests?: boolean; | ||
@@ -47,1 +52,2 @@ environment?: "development" | "production"; | ||
export type CacheEngine = "memory" | "redis" | "mongo"; | ||
export type StickyBucketEngine = "redis" | "none"; |
@@ -7,3 +7,3 @@ { | ||
"description": "GrowthBook proxy server for caching, realtime updates, telemetry, etc", | ||
"version": "1.1.3", | ||
"version": "1.1.5", | ||
"main": "dist/app.js", | ||
@@ -35,3 +35,3 @@ "license": "MIT", | ||
"uuid": "^9.0.0", | ||
"@growthbook/proxy-eval": "^1.0.0" | ||
"@growthbook/proxy-eval": "^1.0.2" | ||
}, | ||
@@ -47,6 +47,7 @@ "devDependencies": { | ||
"nodemon": "^3.0.1", | ||
"pino-pretty": "^10.2.3", | ||
"pino-pretty": "^10.3.1", | ||
"rimraf": "^5.0.5", | ||
"typescript": "5.2.2" | ||
"typescript": "5.2.2", | ||
"@growthbook/growthbook": "^0.34.0" | ||
} | ||
} |
@@ -17,2 +17,3 @@ import fs from "fs"; | ||
import logger, { initializeLogger } from "./services/logger"; | ||
import { initializeStickyBucketService } from "./services/stickyBucket"; | ||
@@ -61,2 +62,3 @@ export { Context, GrowthBookProxy, CacheEngine } from "./types"; | ||
enableRemoteEval: true, | ||
enableStickyBucketing: false, | ||
proxyAllRequests: false, | ||
@@ -77,3 +79,2 @@ }; | ||
// initialize | ||
@@ -83,2 +84,5 @@ initializeLogger(ctx); | ||
ctx.enableCache && (await initializeCache(ctx)); | ||
ctx.enableRemoteEval && | ||
ctx.enableStickyBucketing && | ||
(await initializeStickyBucketService(ctx)); | ||
ctx.enableEventStream && initializeEventStreamManager(ctx); | ||
@@ -85,0 +89,0 @@ |
@@ -5,2 +5,3 @@ import express, { NextFunction, Request, Response } from "express"; | ||
import { featuresCache } from "../services/cache"; | ||
import { stickyBucketService } from "../services/stickyBucket"; | ||
import { registrar } from "../services/registrar"; | ||
@@ -148,3 +149,3 @@ import { apiKeyMiddleware } from "../middleware/apiKeyMiddleware"; | ||
payload = evaluateFeatures({ | ||
payload = await evaluateFeatures({ | ||
payload, | ||
@@ -155,2 +156,3 @@ attributes, | ||
url, | ||
stickyBucketService, | ||
ctx: req.app.locals?.ctx, | ||
@@ -157,0 +159,0 @@ }); |
import express from "express"; | ||
import * as spdy from "spdy"; | ||
import dotenv from "dotenv"; | ||
import { CacheEngine, Context } from "./types"; | ||
import { CacheEngine, Context, StickyBucketEngine } from "./types"; | ||
dotenv.config({ path: "./.env.local" }); | ||
@@ -11,3 +11,6 @@ | ||
const context: Partial<Context> = { | ||
growthbookApiHost: process.env.GROWTHBOOK_API_HOST, | ||
growthbookApiHost: (process.env.GROWTHBOOK_API_HOST ?? "").replace( | ||
/\/*$/, | ||
"", | ||
), | ||
secretApiKey: process.env.SECRET_API_KEY, | ||
@@ -23,5 +26,11 @@ environment: process.env.NODE_ENV as Context["environment"], | ||
// SDK Connections settings: | ||
createConnectionsFromEnv: ["true", "1"].includes(process.env.CREATE_CONNECTIONS_FROM_ENV ?? "1"), | ||
pollForConnections: ["true", "1"].includes(process.env.POLL_FOR_CONNECTIONS ?? "1"), | ||
connectionPollingFrequency: parseInt(process.env.CONNECTION_POLLING_FREQUENCY ?? "60000"), | ||
createConnectionsFromEnv: ["true", "1"].includes( | ||
process.env.CREATE_CONNECTIONS_FROM_ENV ?? "1", | ||
), | ||
pollForConnections: ["true", "1"].includes( | ||
process.env.POLL_FOR_CONNECTIONS ?? "1", | ||
), | ||
connectionPollingFrequency: parseInt( | ||
process.env.CONNECTION_POLLING_FREQUENCY ?? "60000", | ||
), | ||
// Cache settings: | ||
@@ -68,2 +77,21 @@ cacheSettings: { | ||
), | ||
// Sticky Bucket settings (for remote eval): | ||
enableStickyBucketing: ["true", "1"].includes( | ||
process.env.ENABLE_STICKY_BUCKETING ?? "0", | ||
), | ||
stickyBucketSettings: { | ||
engine: (process.env.STICKY_BUCKET_ENGINE || | ||
"none") as StickyBucketEngine, | ||
connectionUrl: process.env.STICKY_BUCKET_CONNECTION_URL, | ||
// Redis only - cluster: | ||
useCluster: ["true", "1"].includes( | ||
process.env.STICKY_BUCKET_USE_CLUSTER ?? "0", | ||
), | ||
clusterRootNodesJSON: process.env.STICKY_BUCKET_CLUSTER_ROOT_NODES_JSON | ||
? JSON.parse(process.env.STICKY_BUCKET_CLUSTER_ROOT_NODES_JSON) | ||
: undefined, | ||
clusterOptionsJSON: process.env.STICKY_BUCKET_CLUSTER_OPTIONS_JSON | ||
? JSON.parse(process.env.STICKY_BUCKET_CLUSTER_OPTIONS_JSON) | ||
: undefined, | ||
}, | ||
}; | ||
@@ -70,0 +98,0 @@ |
@@ -56,4 +56,12 @@ import * as https from "https"; | ||
promise = fetch(url, fetchOptions) | ||
.then((resp) => resp.json()) | ||
.catch((e) => logger.error(e, "Refresh stale cache error")) | ||
.then((resp) => { | ||
if (!resp.ok) { | ||
throw new Error(`HTTP error: ${resp.status}`); | ||
} | ||
return resp.json(); | ||
}) | ||
.catch((e) => { | ||
logger.error(e, "Refresh stale cache error"); | ||
return Promise.reject(e); | ||
}) | ||
.finally(() => delete activeFetches[url]); | ||
@@ -64,3 +72,8 @@ | ||
const payload = await promise; | ||
let payload: unknown = null; | ||
try { | ||
payload = await promise; | ||
} catch (e) { | ||
// ignore, logging handled in promise | ||
} | ||
@@ -67,0 +80,0 @@ if (payload) { |
@@ -106,7 +106,11 @@ import https from "https"; | ||
const maxPages = 10; | ||
let respConnections: { [key: string]: Partial<Connection> } = {}; | ||
const respConnections: { [key: string]: Partial<Connection> } = {}; | ||
while (page <= maxPages) { | ||
page++; | ||
const url = `${this.growthbookApiHost}/api/v1/sdk-connections?withProxy=1&limit=${limit}&offset=${offset}${this.multiOrg ? "&multiOrg=1" : ""}`; | ||
const url = `${ | ||
this.growthbookApiHost | ||
}/api/v1/sdk-connections?withProxy=1&limit=${limit}&offset=${offset}${ | ||
this.multiOrg ? "&multiOrg=1" : "" | ||
}`; | ||
const headers = { | ||
@@ -129,10 +133,12 @@ Authorization: `Bearer ${this.secretApiKey}`, | ||
let data: { | ||
connections: ConnectionDoc[]; | ||
limit?: number; | ||
offset?: number; | ||
total?: number; | ||
hasMore?: boolean; | ||
nextOffset?: number | null; | ||
} | undefined = undefined; | ||
let data: | ||
| { | ||
connections: ConnectionDoc[]; | ||
limit?: number; | ||
offset?: number; | ||
total?: number; | ||
hasMore?: boolean; | ||
nextOffset?: number | null; | ||
} | ||
| undefined = undefined; | ||
try { | ||
@@ -139,0 +145,0 @@ data = await resp.json(); |
@@ -6,2 +6,3 @@ import { Express } from "express"; | ||
import { FeaturesCache, CacheSettings } from "./services/cache"; | ||
import { StickyBucketSettings } from "./services/stickyBucket"; | ||
@@ -31,2 +32,5 @@ export interface GrowthBookProxy { | ||
} & CacheSettings; | ||
stickyBucketSettings?: { | ||
engine: StickyBucketEngine; | ||
} & StickyBucketSettings; | ||
enableHealthCheck?: boolean; | ||
@@ -42,2 +46,3 @@ enableCors?: boolean; | ||
enableRemoteEval?: boolean; | ||
enableStickyBucketing?: boolean; | ||
proxyAllRequests?: boolean; | ||
@@ -50,1 +55,2 @@ environment?: "development" | "production"; | ||
export type CacheEngine = "memory" | "redis" | "mongo"; | ||
export type StickyBucketEngine = "redis" | "none"; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 6 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
255790
120
4647
12
50