Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@instantdomain/bandit

Package Overview
Dependencies
Maintainers
5
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@instantdomain/bandit - npm Package Compare versions

Comparing version 1.0.9 to 1.0.10

17

dist/components/Default.js

@@ -1,16 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Default = void 0;
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importDefault(require("react"));
var constants = tslib_1.__importStar(require("../lib/constants"));
var hooks_1 = require("../lib/hooks");
/**
* A `Default` renders its children when the default variant is being presented
*/
function Default(props) {
var variant = (0, hooks_1.useInstantBandit)().variant;
var matchesDefaultVariant = (variant.name === constants.DEFAULT_VARIANT_NAME);
return (react_1.default.createElement(react_1.default.Fragment, null, matchesDefaultVariant && props.children));
}
exports.Default = Default;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Default=void 0;var a=require("tslib"),b=a.__importDefault(require("react")),c=a.__importStar(require("../lib/constants")),d=require("../lib/hooks");exports.Default=function(a){var e=(0,d.useInstantBandit)().variant.name===c.DEFAULT_VARIANT_NAME;return b.default.createElement(b.default.Fragment,null,e&&a.children)}

@@ -1,185 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstantBandit = exports.InstantBanditComponent = void 0;
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importStar(require("react"));
var constants_1 = require("../lib/constants");
var types_1 = require("../lib/types");
var contexts_1 = require("../lib/contexts");
var utils_1 = require("../lib/utils");
var defaults_1 = require("../lib/defaults");
/**
* Enables Instant Bandit in your apps and websites.
*/
var InstantBanditComponent = function (props) {
var _a = (0, react_1.useState)(false), ready = _a[0], setReady = _a[1];
var _b = (0, react_1.useState)({
renders: 0,
recordedExposure: false,
state: types_1.LoadState.PRELOAD,
loadTimeStart: new Date().getTime(),
loadTimeEnd: 0,
loadDuration: 0,
loadTimedOut: false,
loadTimeoutTimer: null,
}), loadState = _b[0], setLoadState = _b[1];
var _c = (0, react_1.useState)(function () {
var _a = props || {}, config = _a.options, siteName = _a.siteName;
var ctx = (0, contexts_1.createBanditContext)(config);
var loader = ctx.loader;
// Hook to switch variants on the fly for debugging
ctx.select = function (variant) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var site, err_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, loader.load(ctx, siteName, typeof variant === "string" ? variant : variant === null || variant === void 0 ? void 0 : variant.name)];
case 1:
site = _a.sent();
setBanditState(tslib_1.__assign({}, ctx));
return [2 /*return*/, site];
case 2:
err_1 = _a.sent();
handleError(err_1, ctx);
console.warn("[IB] Error loading site: ".concat(err_1));
return [2 /*return*/, defaults_1.DEFAULT_SITE];
case 3: return [2 /*return*/];
}
});
}); };
return ctx;
}), ctx = _c[0], setBanditState = _c[1];
var defer = props.defer, select = props.select, siteProp = props.site, siteName = props.siteName, propsTimeout = props.timeout, onError = props.onError, onReady = props.onReady;
var loader = ctx.loader, metrics = ctx.metrics, session = ctx.session;
var timeout = (0, utils_1.exists)(propsTimeout) ? propsTimeout : constants_1.DEFAULT_TIMEOUT;
// Rather than setting state as soon as we are ready, we defer it to the layout effect.
var readyCallback = (0, react_1.useCallback)(function () {
if (loadState.state === types_1.LoadState.READY) {
return;
}
loadState.state = types_1.LoadState.READY;
// Give the layoutEffect the go ahead, that's where we'll set our main state
// and kick off any layout changes, render, and we'll repaint more effectively.
setReady(true);
if (onReady) {
try {
onReady(ctx);
}
catch (err) {
console.warn("[IB] An error occurred while handling a ready event: ".concat(err));
}
}
}, [loadState, onReady, ctx]);
// Flush any queued metrics
var flush = (0, react_1.useCallback)(function () {
try {
window.removeEventListener("beforeunload", flush);
document.removeEventListener("onvisibilitychange", flush);
}
finally {
metrics.flush(ctx, true).catch(function () { return void 0; });
}
}, [ctx, metrics]);
function broadcastReadyState() {
readyCallback();
}
function markVariantPresented(ctx) {
if (!utils_1.isBrowserEnvironment || loadState.recordedExposure === true) {
return;
}
var experiment = ctx.experiment, variant = ctx.variant;
try {
session.persistVariant(ctx, experiment.id, variant.name);
}
catch (err) {
console.warn("[IB] Session not saved");
}
// Track the exposure
metrics.sinkEvent(ctx, constants_1.DefaultMetrics.EXPOSURES);
loadState.recordedExposure = true;
}
function handleError(err, ib) {
if (err === void 0) { err = null; }
if (!err) {
return;
}
console.warn("[IB] Component received error: ".concat(err));
if (onError) {
try {
onError(err, ib);
}
catch (err) {
console.warn("[IB] Additional error received invoking error handler: ".concat(err));
}
}
}
var timeoutCallback = (0, react_1.useCallback)(function () {
if (loadState.state === types_1.LoadState.READY) {
return;
}
loadState.loadTimedOut = true;
loadState.loadTimeEnd = new Date().getTime();
loadState.loadDuration = loadState.loadTimeEnd - loadState.loadTimeStart;
return function () { return clearTimeout(loadState.loadTimeoutTimer); };
}, [loadState]);
// Kick off site loading ASAP
if (loader && loadState.state === types_1.LoadState.PRELOAD) {
// Note: Not calling setState for internal state. Prevents unwanted renders aka flicker
loadState.state = types_1.LoadState.WAIT;
// Note: In order to load in SSR without flicker, we must initialize *synchronously*.
if (siteProp) {
loader.init(ctx, siteProp, select);
setLoadState(tslib_1.__assign(tslib_1.__assign({}, loadState), { state: types_1.LoadState.READY }));
markVariantPresented(ctx);
broadcastReadyState();
if (loader.error) {
handleError(loader.error, ctx);
}
}
else {
if (!(0, utils_1.exists)(loadState.loadTimeoutTimer) && (0, utils_1.exists)(timeout)) {
loadState.loadTimeoutTimer = setTimeout(timeoutCallback, timeout);
}
loader.load(ctx, siteName, select)
.then(function () {
if (loadState.loadTimedOut) {
ctx.init(defaults_1.DEFAULT_SITE);
markVariantPresented(ctx);
setLoadState(tslib_1.__assign({}, loadState));
broadcastReadyState();
throw new Error("[IB] Timed out waiting for site @ ".concat(loadState.loadDuration, " ms."));
}
})
// This will invoke a layout effect, which will do our primary state update in
.then(function () { return markVariantPresented(ctx); })
.then(function () { return setLoadState(tslib_1.__assign(tslib_1.__assign({}, loadState), { state: types_1.LoadState.READY })); })
.then(broadcastReadyState)
.then(function () { return loader.error ? handleError(loader.error, ctx) : void 0; })
.catch(function (err) { return handleError(err, ctx); });
}
}
(0, react_1.useEffect)(function () {
window.addEventListener("beforeunload", flush);
document.addEventListener("onvisibilitychange", flush);
return flush;
}, [flush]);
// This state change happens synchronously before the next paint.
// Arranging our state changes in order to do our biggest one here reduces flicker immensely.
(0, utils_1.useIsomorphicLayoutEffect)(function () {
if (ready) {
setBanditState(ctx);
}
}, []);
// Skip the hydration render.
// This decouples browser selection from full SSR selection and de-risks against rehydration
// errors in general.
if (defer === true && loadState.renders === 0) {
++loadState.renders;
setTimeout(function () { return setLoadState(tslib_1.__assign({}, loadState)); });
return (react_1.default.createElement(react_1.default.Fragment, null));
}
return (react_1.default.createElement(contexts_1.InstantBanditContext.Provider, { value: ctx }, ready && props.children));
};
exports.InstantBanditComponent = InstantBanditComponent;
exports.InstantBandit = InstantBanditComponent;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.InstantBandit=exports.InstantBanditComponent=void 0;var b=require("tslib"),c=b.__importStar(require("react")),d=require("../lib/constants"),e=require("../lib/types"),f=require("../lib/contexts"),g=require("../lib/utils"),h=require("../lib/defaults"),a=function(j){var l=function(){H()},t=function(a){if(g.isBrowserEnvironment&& !0!==i.recordedExposure){var b=a.experiment,c=a.variant;try{G.persistVariant(a,b.id,c.name)}catch(e){console.warn("[IB] Session not saved")}A.sinkEvent(a,d.DefaultMetrics.EXPOSURES),i.recordedExposure=!0}},u=function(a,b){if(void 0===a&&(a=null),a&&(console.warn("[IB] Component received error: ".concat(a)),F))try{F(a,b)}catch(c){console.warn("[IB] Additional error received invoking error handler: ".concat(c))}},m=(0,c.useState)(!1),v=m[0],D=m[1],n=(0,c.useState)({renders:0,recordedExposure:!1,state:e.LoadState.PRELOAD,loadTimeStart:new Date().getTime(),loadTimeEnd:0,loadDuration:0,loadTimedOut:!1,loadTimeoutTimer:null}),i=n[0],w=n[1],o=(0,c.useState)(function(){var c=j||{},d=c.options,e=c.siteName,a=(0,f.createBanditContext)(d),g=a.loader;return a.select=function(c){return b.__awaiter(void 0,void 0,void 0,function(){var d,f;return b.__generator(this,function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),[4,g.load(a,e,"string"==typeof c?c:null==c?void 0:c.name)];case 1:return d=i.sent(),E(b.__assign({},a)),[2,d];case 2:return u(f=i.sent(),a),console.warn("[IB] Error loading site: ".concat(f)),[2,h.DEFAULT_SITE];case 3:return[2]}})})},a}),a=o[0],E=o[1],x=j.defer,p=j.select,q=j.site,y=j.siteName,r=j.timeout,F=j.onError,z=j.onReady,k=a.loader,A=a.metrics,G=a.session,s=(0,g.exists)(r)?r:d.DEFAULT_TIMEOUT,H=(0,c.useCallback)(function(){if(i.state!==e.LoadState.READY&&(i.state=e.LoadState.READY,D(!0),z))try{z(a)}catch(b){console.warn("[IB] An error occurred while handling a ready event: ".concat(b))}},[i,z,a]),B=(0,c.useCallback)(function(){try{window.removeEventListener("beforeunload",B),document.removeEventListener("onvisibilitychange",B)}finally{A.flush(a,!0).catch(function(){})}},[a,A]),C=(0,c.useCallback)(function(){if(i.state!==e.LoadState.READY)return i.loadTimedOut=!0,i.loadTimeEnd=new Date().getTime(),i.loadDuration=i.loadTimeEnd-i.loadTimeStart,function(){return clearTimeout(i.loadTimeoutTimer)}},[i]);return(k&&i.state===e.LoadState.PRELOAD&&(i.state=e.LoadState.WAIT,q?(k.init(a,q,p),w(b.__assign(b.__assign({},i),{state:e.LoadState.READY})),t(a),l(),k.error&&u(k.error,a)):(!(0,g.exists)(i.loadTimeoutTimer)&&(0,g.exists)(s)&&(i.loadTimeoutTimer=setTimeout(C,s)),k.load(a,y,p).then(function(){if(i.loadTimedOut)throw a.init(h.DEFAULT_SITE),t(a),w(b.__assign({},i)),l(),Error("[IB] Timed out waiting for site @ ".concat(i.loadDuration," ms."))}).then(function(){return t(a)}).then(function(){return w(b.__assign(b.__assign({},i),{state:e.LoadState.READY}))}).then(l).then(function(){return k.error?u(k.error,a):void 0}).catch(function(b){return u(b,a)}))),(0,c.useEffect)(function(){return window.addEventListener("beforeunload",B),document.addEventListener("onvisibilitychange",B),B},[B]),(0,g.useIsomorphicLayoutEffect)(function(){v&&E(a)},[]),!0===x&&0===i.renders)?(++i.renders,setTimeout(function(){return w(b.__assign({},i))}),c.default.createElement(c.default.Fragment,null)):c.default.createElement(f.InstantBanditContext.Provider,{value:a},v&&j.children)};exports.InstantBanditComponent=a,exports.InstantBandit=a

@@ -1,16 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Placeholder = void 0;
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importDefault(require("react"));
var types_1 = require("../lib/types");
var hooks_1 = require("../lib/hooks");
/**
* A Placeholder renders its children whenever Instant Bandit is in a loading state.
*/
function Placeholder(props) {
var loader = (0, hooks_1.useInstantBandit)().loader;
var siteReady = loader.state === types_1.LoadState.READY;
return (react_1.default.createElement(react_1.default.Fragment, null, !siteReady && props.children));
}
exports.Placeholder = Placeholder;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Placeholder=void 0;var a=require("tslib").__importDefault(require("react")),b=require("../lib/types"),c=require("../lib/hooks");exports.Placeholder=function(d){var e=(0,c.useInstantBandit)().loader.state===b.LoadState.READY;return a.default.createElement(a.default.Fragment,null,!e&&d.children)}

@@ -1,6 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
tslib_1.__exportStar(require("./Default"), exports);
tslib_1.__exportStar(require("./Placeholder"), exports);
tslib_1.__exportStar(require("./Variant"), exports);
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var a=require("tslib");a.__exportStar(require("./Default"),exports),a.__exportStar(require("./Placeholder"),exports),a.__exportStar(require("./Variant"),exports)

@@ -1,19 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Variant = void 0;
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importDefault(require("react"));
var types_1 = require("../lib/types");
var hooks_1 = require("../lib/hooks");
/**
* A Variant is rendered when its `name` prop matches the current variant
*/
var Variant = function (props) {
var name = props.name;
var _a = (0, hooks_1.useInstantBandit)(), loader = _a.loader, variant = _a.variant;
var matchesVariant = variant && variant.name === name;
var siteIsReady = loader.state === types_1.LoadState.READY;
var isPresent = matchesVariant && siteIsReady;
return (react_1.default.createElement(react_1.default.Fragment, null, isPresent && props.children));
};
exports.Variant = Variant;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Variant=void 0;var a=require("tslib").__importDefault(require("react")),b=require("../lib/types"),c=require("../lib/hooks");exports.Variant=function(d){var g=d.name,e=(0,c.useInstantBandit)(),h=e.loader,f=e.variant,i=f&&f.name===g,j=h.state===b.LoadState.READY;return a.default.createElement(a.default.Fragment,null,i&&j&&d.children)}

@@ -1,13 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Default = exports.Variant = exports.InstantBandit = void 0;
var tslib_1 = require("tslib");
var InstantBanditComponent_1 = require("./components/InstantBanditComponent");
Object.defineProperty(exports, "InstantBandit", { enumerable: true, get: function () { return InstantBanditComponent_1.InstantBandit; } });
var primitives_1 = require("./components/primitives");
Object.defineProperty(exports, "Variant", { enumerable: true, get: function () { return primitives_1.Variant; } });
Object.defineProperty(exports, "Default", { enumerable: true, get: function () { return primitives_1.Default; } });
tslib_1.__exportStar(require("./lib/defaults"), exports);
tslib_1.__exportStar(require("./lib/hooks"), exports);
tslib_1.__exportStar(require("./lib/models"), exports);
tslib_1.__exportStar(require("./lib/types"), exports);
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Default=exports.Variant=exports.InstantBandit=void 0;var a=require("tslib"),b=require("./components/InstantBanditComponent");Object.defineProperty(exports,"InstantBandit",{enumerable:!0,get:function(){return b.InstantBandit}});var c=require("./components/primitives");Object.defineProperty(exports,"Variant",{enumerable:!0,get:function(){return c.Variant}}),Object.defineProperty(exports,"Default",{enumerable:!0,get:function(){return c.Default}}),a.__exportStar(require("./lib/defaults"),exports),a.__exportStar(require("./lib/hooks"),exports),a.__exportStar(require("./lib/models"),exports),a.__exportStar(require("./lib/types"),exports)

@@ -1,41 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.conversionRates = exports.maxKey = exports.otherProbabilities = exports.bandit = void 0;
var tslib_1 = require("tslib");
/**
* This is an epsilon-greedy bandit algorithm.
* @see https://en.wikipedia.org/wiki/Multi-armed_bandit#Semi-uniform_strategies
* IDEA: equal probs until one clear winner
*/
function bandit(exposures, conversions, epsilon // taken from common values in literature
) {
var _a;
if (epsilon === void 0) { epsilon = 0.2; }
var rates = conversionRates(exposures, conversions);
var winningVariant = maxKey(rates);
return tslib_1.__assign((_a = {}, _a[winningVariant] = 1 - epsilon, _a), otherProbabilities(Object.keys(exposures), winningVariant, epsilon));
}
exports.bandit = bandit;
function otherProbabilities(variants, winningVariant, epsilon) {
var otherVariants = variants.filter(function (v) { return v !== winningVariant; });
return Object.fromEntries(otherVariants.map(function (v) { return [v, epsilon / otherVariants.length]; }));
}
exports.otherProbabilities = otherProbabilities;
function maxKey(rates) {
return Object.entries(rates).reduce(function (_a, _b) {
var v1 = _a[0], rate1 = _a[1];
var v2 = _b[0], rate2 = _b[1];
return rate1 > rate2 ? [v1, rate1] : [v2, rate2];
})[0];
}
exports.maxKey = maxKey;
function conversionRates(exposures, conversions) {
return Object.fromEntries(Object.entries(exposures).map(function (_a) {
var v = _a[0], count = _a[1];
return [
v,
(conversions[v] || 0) / count,
];
}));
}
exports.conversionRates = conversionRates;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.conversionRates=exports.maxKey=exports.otherProbabilities=exports.bandit=void 0;var d=require("tslib");function a(a,c,d){var b=a.filter(function(a){return a!==c});return Object.fromEntries(b.map(function(a){return[a,d/b.length]}))}function b(a){return Object.entries(a).reduce(function(a,b){var e=a[0],c=a[1],f=b[0],d=b[1];return c>d?[e,c]:[f,d]})[0]}function c(a,b){return Object.fromEntries(Object.entries(a).map(function(a){var c=a[0],d=a[1];return[c,(b[c]||0)/d,]}))}exports.bandit=function(f,i,e){void 0===e&&(e=.2);var g,j=c(f,i),h=b(j);return d.__assign(((g={})[h]=1-e,g),a(Object.keys(f),h,e))},exports.otherProbabilities=a,exports.maxKey=b,exports.conversionRates=c

8

dist/lib/constants.d.ts

@@ -26,2 +26,10 @@ export declare const DEFAULT_NAME = "default";

export declare const MAX_STORAGE_VALUE_LENGTH = 1024;
export declare const METRICS_MAX_LENGTH: number;
export declare const METRICS_MAX_ITEM_LENGTH = 1024;
export declare const METRICS_PAYLOAD_IGNORED: {
readonly error: "ignored";
};
export declare const METRICS_PAYLOAD_SIZE_ERR: {
readonly error: "size";
};
/**

@@ -28,0 +36,0 @@ * Metrics tracked by Instant Bandit by default

@@ -1,38 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultMetrics = exports.MAX_STORAGE_VALUE_LENGTH = exports.MAX_STORAGE_KEY_LENGTH = exports.UUID_LENGTH = exports.PROBABILITY_PRECISION = exports.DEFAULT_TIMEOUT = exports.HEADER_SESSION_ID = exports.PARAM_SELECT = exports.PARAM_TIMESTAMP = exports.VARNAME_METRICS_PATH = exports.VARNAME_SITE_PATH = exports.VARNAME_BASE_URL_PUBLIC = exports.VARNAME_BASE_URL = exports.NEXTJS_PUBLIC_PREFIX = exports.DEFAULT_METRICS_PATH = exports.DEFAULT_SITE_PATH = exports.DEFAULT_COOKIE_SETTINGS = exports.DEFAULT_BASE_URL = exports.DEFAULT_ORIGIN = exports.DEFAULT_VARIANT_NAME = exports.DEFAULT_VARIANT_ID = exports.DEFAULT_EXPERIMENT_NAME = exports.DEFAULT_EXPERIMENT_ID = exports.DEFAULT_SITE_NAME = exports.DEFAULT_SITE_ID = exports.DEFAULT_NAME = void 0;
exports.DEFAULT_NAME = "default";
exports.DEFAULT_SITE_ID = exports.DEFAULT_NAME;
exports.DEFAULT_SITE_NAME = exports.DEFAULT_NAME;
exports.DEFAULT_EXPERIMENT_ID = exports.DEFAULT_NAME;
exports.DEFAULT_EXPERIMENT_NAME = exports.DEFAULT_NAME;
exports.DEFAULT_VARIANT_ID = exports.DEFAULT_NAME;
exports.DEFAULT_VARIANT_NAME = exports.DEFAULT_NAME;
exports.DEFAULT_ORIGIN = "localhost";
exports.DEFAULT_BASE_URL = "http://localhost:3000";
exports.DEFAULT_COOKIE_SETTINGS = "Path=/; SameSite=Strict; Max-Age=2147483647; HttpOnly";
exports.DEFAULT_SITE_PATH = "api/sites";
exports.DEFAULT_METRICS_PATH = "api/metrics";
// Any env vars prefixed with this are exposed to the browser
// See: https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser
exports.NEXTJS_PUBLIC_PREFIX = "NEXT_PUBLIC_";
exports.VARNAME_BASE_URL = "IB_BASE_API_URL";
exports.VARNAME_BASE_URL_PUBLIC = exports.NEXTJS_PUBLIC_PREFIX + exports.VARNAME_BASE_URL;
exports.VARNAME_SITE_PATH = "DEFAULT_SITE_PATH";
exports.VARNAME_METRICS_PATH = "DEFAULT_METRICS_PATH";
exports.PARAM_TIMESTAMP = "ts";
exports.PARAM_SELECT = "select";
exports.HEADER_SESSION_ID = "ibsession";
exports.DEFAULT_TIMEOUT = 1000;
exports.PROBABILITY_PRECISION = 4;
exports.UUID_LENGTH = 36;
exports.MAX_STORAGE_KEY_LENGTH = 256;
exports.MAX_STORAGE_VALUE_LENGTH = 1024;
/**
* Metrics tracked by Instant Bandit by default
*/
var DefaultMetrics;
(function (DefaultMetrics) {
DefaultMetrics["EXPOSURES"] = "exposures";
DefaultMetrics["CONVERSIONS"] = "conversions";
})(DefaultMetrics = exports.DefaultMetrics || (exports.DefaultMetrics = {}));
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.DefaultMetrics=exports.METRICS_PAYLOAD_SIZE_ERR=exports.METRICS_PAYLOAD_IGNORED=exports.METRICS_MAX_ITEM_LENGTH=exports.METRICS_MAX_LENGTH=exports.MAX_STORAGE_VALUE_LENGTH=exports.MAX_STORAGE_KEY_LENGTH=exports.UUID_LENGTH=exports.PROBABILITY_PRECISION=exports.DEFAULT_TIMEOUT=exports.HEADER_SESSION_ID=exports.PARAM_SELECT=exports.PARAM_TIMESTAMP=exports.VARNAME_METRICS_PATH=exports.VARNAME_SITE_PATH=exports.VARNAME_BASE_URL_PUBLIC=exports.VARNAME_BASE_URL=exports.NEXTJS_PUBLIC_PREFIX=exports.DEFAULT_METRICS_PATH=exports.DEFAULT_SITE_PATH=exports.DEFAULT_COOKIE_SETTINGS=exports.DEFAULT_BASE_URL=exports.DEFAULT_ORIGIN=exports.DEFAULT_VARIANT_NAME=exports.DEFAULT_VARIANT_ID=exports.DEFAULT_EXPERIMENT_NAME=exports.DEFAULT_EXPERIMENT_ID=exports.DEFAULT_SITE_NAME=exports.DEFAULT_SITE_ID=exports.DEFAULT_NAME=void 0,exports.DEFAULT_NAME="default",exports.DEFAULT_SITE_ID=exports.DEFAULT_NAME,exports.DEFAULT_SITE_NAME=exports.DEFAULT_NAME,exports.DEFAULT_EXPERIMENT_ID=exports.DEFAULT_NAME,exports.DEFAULT_EXPERIMENT_NAME=exports.DEFAULT_NAME,exports.DEFAULT_VARIANT_ID=exports.DEFAULT_NAME,exports.DEFAULT_VARIANT_NAME=exports.DEFAULT_NAME,exports.DEFAULT_ORIGIN="localhost",exports.DEFAULT_BASE_URL="http://localhost:3000",exports.DEFAULT_COOKIE_SETTINGS="Path=/; SameSite=Strict; Max-Age=2147483647; HttpOnly",exports.DEFAULT_SITE_PATH="api/sites",exports.DEFAULT_METRICS_PATH="api/metrics",exports.NEXTJS_PUBLIC_PREFIX="NEXT_PUBLIC_",exports.VARNAME_BASE_URL="IB_BASE_API_URL",exports.VARNAME_BASE_URL_PUBLIC=exports.NEXTJS_PUBLIC_PREFIX+exports.VARNAME_BASE_URL,exports.VARNAME_SITE_PATH="DEFAULT_SITE_PATH",exports.VARNAME_METRICS_PATH="DEFAULT_METRICS_PATH",exports.PARAM_TIMESTAMP="ts",exports.PARAM_SELECT="select",exports.HEADER_SESSION_ID="ibsession",exports.DEFAULT_TIMEOUT=1e3,exports.PROBABILITY_PRECISION=4,exports.UUID_LENGTH=36,exports.MAX_STORAGE_KEY_LENGTH=256,exports.MAX_STORAGE_VALUE_LENGTH=1024,exports.METRICS_MAX_LENGTH=524288,exports.METRICS_MAX_ITEM_LENGTH=1024,exports.METRICS_PAYLOAD_IGNORED={error:"ignored"},exports.METRICS_PAYLOAD_SIZE_ERR={error:"size"},function(a){a.EXPOSURES="exposures",a.CONVERSIONS="conversions"}(exports.DefaultMetrics||(exports.DefaultMetrics={}))
import React from "react";
import { InstantBanditOptions, MetricsProvider, SessionProvider, SiteProvider } from "./types";
import * as constants from "./constants";
import { InstantBanditOptions, Metric, MetricsProvider, SessionProvider, SiteProvider } from "./types";
import { Experiment, Site, Variant } from "./models";

@@ -13,5 +14,7 @@ export interface InstantBanditContext {

session: SessionProvider;
init: (site: Site) => Promise<Site>;
load: (siteName?: string, variant?: string) => Promise<Site>;
select: (variant?: Variant | string) => Promise<Site>;
init(site: Site): Promise<Site>;
load(siteName?: string, variant?: string): Promise<Site>;
select(variant?: Variant | string): Promise<Site>;
incrementMetric(metric: constants.DefaultMetrics | string): any;
recordEvent(name: string, payload: Metric): any;
}

@@ -18,0 +21,0 @@ export declare function createBanditContext(options?: Partial<InstantBanditOptions>): InstantBanditContext;

@@ -1,68 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstantBanditContext = exports.DEFAULT_BANDIT_OPTIONS = exports.mergeBanditOptions = exports.createBanditContext = void 0;
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importDefault(require("react"));
var constants = tslib_1.__importStar(require("./constants"));
var session_1 = require("./providers/session");
var site_1 = require("./providers/site");
var site_2 = require("./providers/site");
var metrics_1 = require("./providers/metrics");
function createBanditContext(options) {
var _this = this;
var appliedOptions = mergeBanditOptions(exports.DEFAULT_BANDIT_OPTIONS, options !== null && options !== void 0 ? options : {});
var providers = appliedOptions.providers;
var loader = providers.loader(appliedOptions);
var metrics = providers.metrics(appliedOptions);
var session = providers.session(appliedOptions);
var ctx = {
origin: typeof location !== "undefined" ? location.origin : constants.DEFAULT_ORIGIN,
config: appliedOptions,
loader: loader,
metrics: metrics,
session: session,
get site() { return loader.model; },
get experiment() { return loader.experiment; },
get variant() { return loader.variant; },
load: function (variant) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, loader.load(ctx, variant)];
case 1: return [2 /*return*/, _a.sent()];
}
});
}); },
init: function (site, select) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, loader.init(ctx, site, select)];
case 1: return [2 /*return*/, _a.sent()];
}
});
}); },
select: function (variant) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
loader.select(ctx, variant);
session.persistVariant(ctx, loader.experiment.id, loader.variant.name);
return [2 /*return*/, loader.model];
});
}); },
};
return ctx;
}
exports.createBanditContext = createBanditContext;
function mergeBanditOptions(a, b) {
var providersA = a.providers;
var providersB = b.providers;
var providers = Object.assign({}, providersA, providersB);
var merged = Object.assign({}, a, b, { providers: providers });
return Object.freeze(merged);
}
exports.mergeBanditOptions = mergeBanditOptions;
exports.DEFAULT_BANDIT_OPTIONS = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, site_2.DEFAULT_SITE_PROVIDER_OPTIONS), metrics_1.DEFAULT_METRICS_SINK_OPTIONS), { providers: {
loader: function (options) { return (0, site_1.getSiteProvider)(options); },
session: function (options) { return (0, session_1.getLocalStorageSessionProvider)(options); },
metrics: function (options) { return (0, metrics_1.getHttpMetricsSink)(options); },
} });
Object.freeze(exports.DEFAULT_BANDIT_OPTIONS);
exports.InstantBanditContext = react_1.default.createContext(createBanditContext());
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.InstantBanditContext=exports.DEFAULT_BANDIT_OPTIONS=exports.mergeBanditOptions=exports.createBanditContext=void 0;var a=require("tslib"),c=a.__importDefault(require("react")),g=a.__importStar(require("./constants")),h=require("./providers/session"),i=require("./providers/site"),d=require("./providers/site"),e=require("./providers/metrics");function b(e){var b=f(exports.DEFAULT_BANDIT_OPTIONS,null!=e?e:{}),d=b.providers,c=d.loader(b),h=d.metrics(b),i=d.session(b),j={origin:"undefined"!=typeof location?location.origin:g.DEFAULT_ORIGIN,config:b,loader:c,metrics:h,session:i,get site(){return c.model},get experiment(){return c.experiment},get variant(){return c.variant},load:function(b){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){switch(a.label){case 0:return[4,c.load(j,b)];case 1:return[2,a.sent()]}})})},init:function(b,d){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){switch(a.label){case 0:return[4,c.init(j,b,d)];case 1:return[2,a.sent()]}})})},select:function(b){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return c.select(j,b),i.persistVariant(j,c.experiment.id,c.variant.name),[2,c.model]})})},incrementMetric:function(a){var b={ts:new Date().getTime(),name:a};h.sink(j,b)},recordEvent:function(a,b){var c={ts:new Date().getTime(),name:"evt.component.".concat(a),payload:b};h.sink(j,c)}};return j}function f(a,b){var e,c=Object.assign({},a.providers,b.providers),d=Object.assign({},a,b,{providers:c});return Object.freeze(d)}exports.createBanditContext=b,exports.mergeBanditOptions=f,exports.DEFAULT_BANDIT_OPTIONS=a.__assign(a.__assign(a.__assign({},d.DEFAULT_SITE_PROVIDER_OPTIONS),e.DEFAULT_METRICS_SINK_OPTIONS),{providers:{loader:function(a){return(0,i.getSiteProvider)(a)},session:function(a){return(0,h.getLocalStorageSessionProvider)(a)},metrics:function(a){return(0,e.getHttpMetricsSink)(a)}}}),Object.freeze(exports.DEFAULT_BANDIT_OPTIONS),exports.InstantBanditContext=c.default.createContext(b())

@@ -1,38 +0,1 @@

"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_METRICS = exports.DEFAULT_VARIANT = exports.DEFAULT_EXPERIMENT = exports.DEFAULT_SITE = exports.DEFAULT_OPTIONS = void 0;
var tslib_1 = require("tslib");
var constants = tslib_1.__importStar(require("./constants"));
var utils_1 = require("./utils");
exports.DEFAULT_OPTIONS = {
baseUrl: (_a = (0, utils_1.env)(constants.VARNAME_BASE_URL)) !== null && _a !== void 0 ? _a : constants.DEFAULT_BASE_URL,
};
/**
*
* This file defines the built-in default model and is used as a fallback,
* as well as the template for new sites.
*
* It represents the invariant, baseline version of a new or existing site.
*
*/
var DEFAULT_ORIGIN = constants.DEFAULT_ORIGIN, DEFAULT_SITE_NAME = constants.DEFAULT_SITE_NAME, DEFAULT_EXPERIMENT_ID = constants.DEFAULT_EXPERIMENT_ID, DEFAULT_EXPERIMENT_NAME = constants.DEFAULT_EXPERIMENT_NAME, DEFAULT_VARIANT_NAME = constants.DEFAULT_VARIANT_NAME;
exports.DEFAULT_SITE = (0, utils_1.deepFreeze)({
name: DEFAULT_SITE_NAME,
origin: DEFAULT_ORIGIN,
experiments: [{
id: DEFAULT_EXPERIMENT_ID,
name: DEFAULT_EXPERIMENT_NAME,
pValue: 1,
metrics: {},
variants: [{
name: DEFAULT_VARIANT_NAME,
prob: 1,
metrics: {},
props: {},
}],
}],
});
exports.DEFAULT_EXPERIMENT = exports.DEFAULT_SITE.experiments[0];
exports.DEFAULT_VARIANT = exports.DEFAULT_EXPERIMENT.variants[0];
exports.DEFAULT_METRICS = exports.DEFAULT_VARIANT.metrics;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEFAULT_METRICS=exports.DEFAULT_VARIANT=exports.DEFAULT_EXPERIMENT=exports.DEFAULT_SITE=exports.DEFAULT_OPTIONS=void 0;var b,a=require("tslib").__importStar(require("./constants")),c=require("./utils");exports.DEFAULT_OPTIONS={baseUrl:null!==(b=(0,c.env)(a.VARNAME_BASE_URL))&& void 0!==b?b:a.DEFAULT_BASE_URL};var d=a.DEFAULT_ORIGIN,e=a.DEFAULT_SITE_NAME,f=a.DEFAULT_EXPERIMENT_ID,g=a.DEFAULT_EXPERIMENT_NAME,h=a.DEFAULT_VARIANT_NAME;exports.DEFAULT_SITE=(0,c.deepFreeze)({name:e,origin:d,experiments:[{id:f,name:g,pValue:1,metrics:{},variants:[{name:h,prob:1,metrics:{},props:{}}]}]}),exports.DEFAULT_EXPERIMENT=exports.DEFAULT_SITE.experiments[0],exports.DEFAULT_VARIANT=exports.DEFAULT_EXPERIMENT.variants[0],exports.DEFAULT_METRICS=exports.DEFAULT_VARIANT.metrics

@@ -1,12 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useInstantBandit = void 0;
var react_1 = require("react");
var contexts_1 = require("./contexts");
/**
* Gets access to the Instant Bandit API and the current site/experiment/variant
*/
function useInstantBandit() {
return (0, react_1.useContext)(contexts_1.InstantBanditContext);
}
exports.useInstantBandit = useInstantBandit;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useInstantBandit=void 0;var a=require("react"),b=require("./contexts");exports.useInstantBandit=function(){return(0,a.useContext)(b.InstantBanditContext)}

@@ -1,9 +0,1 @@

"use strict";
/**
* Domain Models
*
* These models formalize a language that Instant Bandit clients and servers can speak.
* Types suffixed in `Meta` represent additional server-side info.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
"use strict";Object.defineProperty(exports,"__esModule",{value:!0})

@@ -1,154 +0,1 @@

"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendBatchViaFetch = exports.sendBatchViaBeacon = exports.getHttpMetricsSink = exports.DEFAULT_METRICS_SINK_OPTIONS = void 0;
var tslib_1 = require("tslib");
var constants = tslib_1.__importStar(require("../constants"));
var defaults_1 = require("../defaults");
var utils_1 = require("../utils");
exports.DEFAULT_METRICS_SINK_OPTIONS = tslib_1.__assign(tslib_1.__assign({}, defaults_1.DEFAULT_OPTIONS), { metricsPath: (_a = (0, utils_1.env)(constants.VARNAME_METRICS_PATH)) !== null && _a !== void 0 ? _a : constants.DEFAULT_METRICS_PATH, batchSize: 10, flushInterval: 100 });
function getHttpMetricsSink(initOptions) {
var options = Object.assign({}, exports.DEFAULT_METRICS_SINK_OPTIONS, initOptions);
var items = [];
var flushTimer = null;
var provider = {
get pending() { return items.length; },
sink: function (ctx, sample, forceFlush) {
if (forceFlush === void 0) { forceFlush = false; }
items.push(sample);
if (forceFlush) {
return provider.flush(ctx);
}
else {
provider.scheduleFlush(ctx);
}
},
sinkEvent: function (ctx, eventName, payload, forceFlush) {
if (forceFlush === void 0) { forceFlush = false; }
try {
var evt = {
ts: new Date().getTime(),
name: eventName,
payload: payload,
};
provider.sink(ctx, evt, forceFlush);
}
catch (err) {
console.warn("[IB] Error sinking event: ".concat(err));
}
},
scheduleFlush: function (ctx) {
if (flushTimer) {
return;
}
flushTimer = setTimeout(function () {
return provider
.flush(ctx)
.catch(function (err) { return console.warn(err); });
}, options.flushInterval);
},
flush: function (ctx, flushAll) {
var _a, _b;
if (flushAll === void 0) { flushAll = false; }
return tslib_1.__awaiter(this, void 0, void 0, function () {
var baseUrl, batchSize, metricsPath, session, site, experiment, variant, count, entries, sessionId, batch, url, session_1, err_1;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
baseUrl = options.baseUrl, batchSize = options.batchSize, metricsPath = options.metricsPath;
session = ctx.session, site = ctx.site, experiment = ctx.experiment, variant = ctx.variant;
// No where to send to? Discard.
if (!(0, utils_1.exists)(metricsPath) || metricsPath.trim() === "") {
console.debug("[IB] No metrics path configured");
items.splice(0, items.length);
return [2 /*return*/];
}
count = flushAll
? items.length
: Math.min(items.length, batchSize);
entries = items.slice(0, count);
sessionId = (_b = (_a = session.id) !== null && _a !== void 0 ? _a : (0, utils_1.getCookie)(constants.HEADER_SESSION_ID)) !== null && _b !== void 0 ? _b : "";
batch = {
site: site.name,
experiment: experiment.id,
variant: variant.name,
entries: entries,
};
url = new URL(metricsPath, baseUrl);
_c.label = 1;
case 1:
_c.trys.push([1, 5, 6, 7]);
if (!(flushAll &&
typeof navigator !== "undefined" &&
typeof navigator.sendBeacon !== "undefined")) return [3 /*break*/, 2];
sendBatchViaBeacon(url, batch);
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, sendBatchViaFetch(url, sessionId, batch)];
case 3:
session_1 = _c.sent();
if ((0, utils_1.exists)(session_1.sid)) {
ctx.session.save(ctx, session_1);
}
_c.label = 4;
case 4: return [3 /*break*/, 7];
case 5:
err_1 = _c.sent();
console.warn("Error occurred while flushing metrics: ".concat(err_1));
return [3 /*break*/, 7];
case 6:
items.splice(0, count);
if (flushTimer) {
clearTimeout(flushTimer);
flushTimer = null;
}
if (items.length > 0) {
provider.scheduleFlush(ctx);
}
return [7 /*endfinally*/];
case 7: return [2 /*return*/];
}
});
});
}
};
return provider;
}
exports.getHttpMetricsSink = getHttpMetricsSink;
// Note: sendBeacon is synchronous (fire and forget), and will return `true` even in the case server error
function sendBatchViaBeacon(url, batch) {
var blob = new Blob([JSON.stringify(batch)], {
type: "application/json; charset=UTF-8",
});
return navigator.sendBeacon(url + "", blob);
}
exports.sendBatchViaBeacon = sendBatchViaBeacon;
function sendBatchViaFetch(url, sessionId, batch) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var resp, status, statusText, session;
var _a;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, fetch(url.toString(), {
method: "POST",
headers: (_a = {
"Accept": "application/json",
"Content-Type": "application/json"
},
_a[constants.HEADER_SESSION_ID] = sessionId,
_a),
body: JSON.stringify(batch),
})];
case 1:
resp = _b.sent();
status = resp.status, statusText = resp.statusText;
if (!(status === 200)) return [3 /*break*/, 3];
return [4 /*yield*/, resp.json()];
case 2:
session = _b.sent();
return [2 /*return*/, session];
case 3: return [2 /*return*/, { status: status, statusText: statusText }];
}
});
});
}
exports.sendBatchViaFetch = sendBatchViaFetch;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.sendBatchViaFetch=exports.sendBatchViaBeacon=exports.getHttpMetricsSink=exports.DEFAULT_METRICS_SINK_OPTIONS=void 0;var a,b=require("tslib"),c=b.__importStar(require("../constants")),d=require("../defaults"),e=require("../utils");function f(a,b){var c=new Blob([(0,e.encodeMetricsBatch)(b)],{type:"application/text; charset=UTF-8"});return navigator.sendBeacon(a+"",c)}function g(a,d,f){return b.__awaiter(this,void 0,void 0,function(){var g,h,i,j,k;return b.__generator(this,function(b){switch(b.label){case 0:return[4,fetch(a.toString(),{method:"POST",headers:((k={Accept:"application/json","Content-Type":"text/plain; charset=UTF-8"})[c.HEADER_SESSION_ID]=d,k),body:(0,e.encodeMetricsBatch)(f)})];case 1:if(h=(g=b.sent()).status,i=g.statusText,200!==h)return[3,3];return[4,g.json()];case 2:return[2,j=b.sent()];case 3:return[2,{status:h,statusText:i}]}})})}exports.DEFAULT_METRICS_SINK_OPTIONS=b.__assign(b.__assign({},d.DEFAULT_OPTIONS),{metricsPath:null!==(a=(0,e.env)(c.VARNAME_METRICS_PATH))&& void 0!==a?a:c.DEFAULT_METRICS_PATH,batchSize:10,flushInterval:100}),exports.getHttpMetricsSink=function(a){var i=Object.assign({},exports.DEFAULT_METRICS_SINK_OPTIONS,a),d=[],j=null,h={get pending(){return d.length},sink:function(b,c,a){if(void 0===a&&(a=!1),d.push(c),a)return h.flush(b);h.scheduleFlush(b)},sinkEvent:function(b,c,d,a){void 0===a&&(a=!1);try{var e={ts:new Date().getTime(),name:c,payload:d};h.sink(b,e,a)}catch(f){console.warn("[IB] Error sinking event: ".concat(f))}},scheduleFlush:function(a){!j&&(j=setTimeout(function(){return h.flush(a).catch(function(a){return console.warn(a)})},i.flushInterval))},flush:function(k,a){var l,m;return void 0===a&&(a=!1),b.__awaiter(this,void 0,void 0,function(){var n,o,p,q,r,s,t,u,v,w,x,y,z,A;return b.__generator(this,function(b){switch(b.label){case 0:if(n=i.baseUrl,o=i.batchSize,p=i.metricsPath,q=k.session,r=k.site,s=k.experiment,t=k.variant,!(0,e.exists)(p)||""===p.trim())return console.debug("[IB] No metrics path configured"),d.splice(0,d.length),[2];u=a?d.length:Math.min(d.length,o),v=d.slice(0,u),w=null!==(m=null!==(l=q.id)&& void 0!==l?l:(0,e.getCookie)(c.HEADER_SESSION_ID))&& void 0!==m?m:"",x={site:r.name,experiment:s.id,variant:t.name,entries:v},y=new URL(p,n),b.label=1;case 1:if(b.trys.push([1,5,6,7]),!(a&&"undefined"!=typeof navigator&& void 0!==navigator.sendBeacon))return[3,2];return f(y,x),[3,4];case 2:return[4,g(y,w,x)];case 3:z=b.sent(),(0,e.exists)(z.sid)&&k.session.save(k,z),b.label=4;case 4:return[3,7];case 5:return A=b.sent(),console.warn("Error occurred while flushing metrics: ".concat(A)),[3,7];case 6:return d.splice(0,u),j&&(clearTimeout(j),j=null),d.length>0&&h.scheduleFlush(k),[7];case 7:return[2]}})})}};return h},exports.sendBatchViaBeacon=f,exports.sendBatchViaFetch=g

@@ -1,148 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLocalStorageSessionProvider = exports.getLocalStorageKey = void 0;
var utils_1 = require("../utils");
var defaults_1 = require("../defaults");
var constants_1 = require("../constants");
function getLocalStorageKey() {
return constants_1.HEADER_SESSION_ID;
}
exports.getLocalStorageKey = getLocalStorageKey;
function getLocalStorageSessionProvider(options) {
options;
var id = null;
return {
get id() { return id; },
/**
* Gets an existing session for the given site, creating one with default
* properties if it does not exist.
*/
getOrCreateSession: function (ctx, props) {
var session = getOrCreateSession(ctx, props);
if ((0, utils_1.exists)(session.sid)) {
id = session.sid;
}
return session;
},
/**
* Records a variant exposure in the session in order to show the same one next time
*/
persistVariant: function (ctx, experiment, variant) {
return persistVariant(ctx, experiment, variant);
},
/**
* Checks the session to see if a particular site/experiment/variant combo has been
* presented before
*/
hasSeen: function (ctx, experiment, variant) {
return hasSeen(ctx, experiment, variant);
},
/**
* Persists a new version of the session from the server
* @param ctx
* @param session
*/
save: function (ctx, session) {
id = session.sid;
var storageKey = getLocalStorageKey();
try {
localStorage.setItem(storageKey, JSON.stringify(session));
}
catch (err) {
handlePossibleQuotaError(err);
}
return session;
}
};
}
exports.getLocalStorageSessionProvider = getLocalStorageSessionProvider;
function getOrCreateSession(ctx, props) {
if (!utils_1.isBrowserEnvironment) {
return Object.assign((0, utils_1.makeNewSession)(), props);
}
var site = ctx.site;
if (!(0, utils_1.exists)(site)) {
site = defaults_1.DEFAULT_SITE;
}
var storageKey = getLocalStorageKey();
var sessionJson = localStorage.getItem(storageKey);
var session;
if ((0, utils_1.exists)(sessionJson)) {
session = JSON.parse(sessionJson);
}
else {
session = (0, utils_1.makeNewSession)();
}
if (props) {
Object.assign(session, props);
}
try {
localStorage.setItem(storageKey, JSON.stringify(session));
}
catch (err) {
handlePossibleQuotaError(err);
}
return session;
}
function persistVariant(ctx, experiment, variant) {
if (!utils_1.isBrowserEnvironment) {
return;
}
// Defaults are implicit, no need to have them persisted
if (experiment === defaults_1.DEFAULT_EXPERIMENT.id && variant === defaults_1.DEFAULT_VARIANT.name) {
return;
}
var site = ctx.site;
var storageKey = getLocalStorageKey();
var session = getOrCreateSession(ctx);
(0, utils_1.markVariantInSession)(session, site.name, experiment, variant);
try {
localStorage.setItem(storageKey, JSON.stringify(session));
}
catch (err) {
handlePossibleQuotaError(err);
}
}
function handlePossibleQuotaError(err) {
if (!isQuotaError(err)) {
console.warn("[IB] Error updating session: ".concat(err));
}
else {
// NOTE: This is almost certainly a quota issue, and the shape of which is not
// consistent across browsers. Safe to suppress here.
console.debug("[IB] Storage quota error: ".concat(err));
}
}
/**
* Examines an error to see if it's a quota error from local/session storage
*/
function isQuotaError(err) {
if (!err) {
return false;
}
if (!(0, utils_1.exists)(err.code)) {
return false;
}
else {
switch (err.code) {
// Proper DOM code in most modern browsers
case 22:
return true;
// Firefox
case 1014:
return err.name === "NS_ERROR_DOM_QUOTA_REACHED";
default:
return false;
}
}
}
function hasSeen(ctx, experiment, variant) {
var _a, _b;
if (!utils_1.isBrowserEnvironment) {
return false;
}
var site = ctx.site;
var session = getOrCreateSession(ctx);
var seenVariants = (_b = (_a = session.selections) === null || _a === void 0 ? void 0 : _a[site.name]) === null || _b === void 0 ? void 0 : _b[experiment];
return (0, utils_1.exists)(seenVariants) && (0, utils_1.exists)(seenVariants.find(function (v) { return v === variant; }));
}
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getLocalStorageSessionProvider=exports.getLocalStorageKey=void 0;var b=require("../utils"),c=require("../defaults"),d=require("../constants");function a(){return d.HEADER_SESSION_ID}function e(j,e){if(!b.isBrowserEnvironment)return Object.assign((0,b.makeNewSession)(),e);var d,g=j.site;(0,b.exists)(g)||(g=c.DEFAULT_SITE);var h=a(),i=localStorage.getItem(h);d=(0,b.exists)(i)?JSON.parse(i):(0,b.makeNewSession)(),e&&Object.assign(d,e);try{localStorage.setItem(h,JSON.stringify(d))}catch(k){f(k)}return d}exports.getLocalStorageKey=a;function f(a){g(a)?console.debug("[IB] Storage quota error: ".concat(a)):console.warn("[IB] Error updating session: ".concat(a))}function g(a){if(!a||!(0,b.exists)(a.code))return!1;switch(a.code){case 22:return!0;case 1014:return"NS_ERROR_DOM_QUOTA_REACHED"===a.name;default:return!1}}exports.getLocalStorageSessionProvider=function(g){var d=null;return{get id(){return d},getOrCreateSession:function(c,f){var a=e(c,f);return(0,b.exists)(a.sid)&&(d=a.sid),a},persistVariant:function(d,g,h){return function m(d,g,h){if(b.isBrowserEnvironment&&(g!==c.DEFAULT_EXPERIMENT.id||h!==c.DEFAULT_VARIANT.name)){var j=d.site,k=a(),i=e(d);(0,b.markVariantInSession)(i,j.name,g,h);try{localStorage.setItem(k,JSON.stringify(i))}catch(l){f(l)}}}(d,g,h)},hasSeen:function(a,c,d){return function j(d,g,k){if(!b.isBrowserEnvironment)return!1;var a,c,h=d.site,i=e(d),f=null===(c=null===(a=i.selections)|| void 0===a?void 0:a[h.name])|| void 0===c?void 0:c[g];return(0,b.exists)(f)&&(0,b.exists)(f.find(function(a){return a===k}))}(a,c,d)},save:function(g,b){d=b.sid;var c=a();try{localStorage.setItem(c,JSON.stringify(b))}catch(e){f(e)}return b}}}

@@ -1,273 +0,1 @@

"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDefaultExperiment = exports.getActiveExperiment = exports.balanceProbabilities = exports.selectWithProbabilities = exports.getSiteProvider = exports.DEFAULT_SITE_PROVIDER_OPTIONS = void 0;
var tslib_1 = require("tslib");
var constants = tslib_1.__importStar(require("../constants"));
var types_1 = require("../types");
var utils_1 = require("../utils");
var defaults_1 = require("../defaults");
exports.DEFAULT_SITE_PROVIDER_OPTIONS = tslib_1.__assign(tslib_1.__assign({}, defaults_1.DEFAULT_OPTIONS), { sitePath: (_a = (0, utils_1.env)(constants.VARNAME_SITE_PATH)) !== null && _a !== void 0 ? _a : constants.DEFAULT_SITE_PATH, appendTimestamp: false });
Object.freeze(exports.DEFAULT_SITE_PROVIDER_OPTIONS);
function getSiteProvider(initOptions) {
if (initOptions === void 0) { initOptions = {}; }
var options = Object.assign({}, exports.DEFAULT_SITE_PROVIDER_OPTIONS, initOptions);
Object.freeze(options);
var state = types_1.LoadState.PRELOAD;
var error = null;
var site = defaults_1.DEFAULT_SITE;
var experiment = defaults_1.DEFAULT_EXPERIMENT;
var variant = defaults_1.DEFAULT_VARIANT;
var provider = {
get error() { return error; },
get origin() {
if (utils_1.isBrowserEnvironment) {
return location.origin;
}
else {
return constants.DEFAULT_ORIGIN;
}
},
get model() { return site; },
get experiment() { return experiment; },
get variant() { return variant; },
get state() { return state; },
load: function (ctx, siteName, variant) {
if (siteName === void 0) { siteName = defaults_1.DEFAULT_SITE.name; }
return tslib_1.__awaiter(this, void 0, void 0, function () {
var siteUrl, baseUrl, sitePath, url, resp, siteJson, err_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
siteUrl = "";
_a.label = 1;
case 1:
_a.trys.push([1, 5, 7, 8]);
baseUrl = options.baseUrl, sitePath = options.sitePath;
url = new URL([sitePath, siteName].join("/"), baseUrl);
siteUrl = url.toString();
if (options.appendTimestamp === true) {
url.searchParams.append(constants.PARAM_TIMESTAMP, new Date().getTime() + "");
}
siteUrl = url.toString();
state = types_1.LoadState.WAIT;
return [4 /*yield*/, fetch(siteUrl)];
case 2:
resp = _a.sent();
return [4 /*yield*/, resp.json()];
case 3:
siteJson = _a.sent();
return [4 /*yield*/, provider.init(ctx, siteJson, variant)];
case 4:
site = _a.sent();
return [3 /*break*/, 8];
case 5:
err_1 = _a.sent();
error = err_1;
console.warn("[IB] An error occurred while loading from '".concat(siteUrl, "': ").concat(err_1, ". Default site will be used."));
// Re-init w/ builtins
return [4 /*yield*/, provider.init(ctx, defaults_1.DEFAULT_SITE, variant)];
case 6:
// Re-init w/ builtins
_a.sent();
return [3 /*break*/, 8];
case 7:
state = types_1.LoadState.READY;
return [7 /*endfinally*/];
case 8: return [2 /*return*/, site];
}
});
});
},
/**
* Initializes from a site object provided locally
* @param ctx
* @param site
* @param select
* @returns
*/
init: function (ctx, siteArg, select) {
try {
if (!siteArg || typeof siteArg !== "object") {
throw new Error("Invalid site configuration");
}
error = null;
state = types_1.LoadState.SELECTING;
site = Object.assign({}, siteArg);
var _a = provider.select(ctx, select), selectedExperiment = _a.experiment, selectedVariant = _a.variant;
experiment = Object.assign({}, selectedExperiment);
variant = Object.assign({}, selectedVariant);
state = types_1.LoadState.READY;
return site;
}
catch (err) {
error = err;
site = defaults_1.DEFAULT_SITE;
experiment = defaults_1.DEFAULT_EXPERIMENT;
variant = defaults_1.DEFAULT_VARIANT;
console.warn("[IB] Error initializing. Default site will be used. Error was: ".concat(err));
}
finally {
state = types_1.LoadState.READY;
}
// Just to be safe
if (!site) {
site = defaults_1.DEFAULT_SITE;
experiment = defaults_1.DEFAULT_EXPERIMENT;
variant = defaults_1.DEFAULT_VARIANT;
}
return site;
},
select: function (ctx, selectVariant) {
var _a, _b, _c, _d, _e;
try {
// Selection precedence:
// 1. Explicit (i.e. specified on props)
// 2. Specified in site object (via the "select" field)
// 3. Session (display previously presented variant)
// 4. Probabilistic (use probabilities computed on the server)
// 5. Fallback to default variant in configured experiment (should one exist)
// 6. Fallback to default variant in builtin experiment
var selection = (_a = selectVariant !== null && selectVariant !== void 0 ? selectVariant : site.select) !== null && _a !== void 0 ? _a : undefined;
var experiment_1 = (_c = (_b = getActiveExperiment(site, selection)) !== null && _b !== void 0 ? _b : getDefaultExperiment(site)) !== null && _c !== void 0 ? _c : defaults_1.DEFAULT_EXPERIMENT;
var variant_1 = null;
var session = ctx.session;
if ((0, utils_1.exists)(selection) && selection) {
var result = provider.selectSpecific(experiment_1, selection);
experiment_1 = result.experiment;
variant_1 = result.variant;
}
else if (session) {
var userSession = session.getOrCreateSession(ctx);
var selections = userSession.selections;
var selectedSite = selections[site.name];
var mostRecentSeenVariant_1 = (_d = selectedSite === null || selectedSite === void 0 ? void 0 : selectedSite[experiment_1.id]) === null || _d === void 0 ? void 0 : _d.slice().reverse()[0];
variant_1 = (_e = experiment_1.variants.find(function (v) { return v.name === mostRecentSeenVariant_1; })) !== null && _e !== void 0 ? _e : null;
}
if (!variant_1) {
variant_1 = selectWithProbabilities(experiment_1);
}
if (!variant_1) {
variant_1 = defaults_1.DEFAULT_VARIANT;
}
return { experiment: experiment_1, variant: variant_1 };
}
catch (err) {
error = err;
site = defaults_1.DEFAULT_SITE;
experiment = defaults_1.DEFAULT_EXPERIMENT;
variant = defaults_1.DEFAULT_VARIANT;
console.warn("[IB] Error encountered while selecting variant '".concat(selectVariant, "': ").concat(err));
return { experiment: defaults_1.DEFAULT_EXPERIMENT, variant: defaults_1.DEFAULT_VARIANT };
}
},
/**
* Selects a specific variant from the given experiment.
* If it does not exist, the variant will be searched for in a configured default experiment.
* If no variant match is found, the default variant in the default experiment is returned.
* @param experiment
* @param variant
* @returns
*/
selectSpecific: function (experiment, variant) {
var configuredDefaultExperiment;
// Check in the specified experiment
var selected = experiment.variants.find(function (v) { return v.name === variant; });
if (selected) {
return { experiment: experiment, variant: selected };
}
else {
// Check in an explicitly configured default experiment, should one exist
configuredDefaultExperiment = site.experiments.find(function (e) { return e.id === defaults_1.DEFAULT_EXPERIMENT.id; });
selected = configuredDefaultExperiment === null || configuredDefaultExperiment === void 0 ? void 0 : configuredDefaultExperiment.variants.find(function (v) { return v.name === defaults_1.DEFAULT_VARIANT.name; });
}
// If the variant was found in the configured default, use the default variant, configured default experiment
if (configuredDefaultExperiment && !selected) {
return { experiment: configuredDefaultExperiment, variant: defaults_1.DEFAULT_VARIANT };
}
else if (selected) {
return { experiment: experiment, variant: selected };
}
else {
// Otherwise, fall back to the inbuilt default experiment + variant
return { experiment: defaults_1.DEFAULT_EXPERIMENT, variant: defaults_1.DEFAULT_VARIANT };
}
},
};
return provider;
}
exports.getSiteProvider = getSiteProvider;
/**
* Selects a specific variant based on the probabilities expressed by the variants
* in the experiment
* @param experiment
*/
function selectWithProbabilities(experiment) {
var _a;
var variants = experiment.variants;
var probs = balanceProbabilities(variants);
var winner = null;
var rand = Math.random();
var cumulativeProb = 0.0;
var sorted = Object.entries(probs).sort(function (a, b) { return a[1] - b[1]; });
var _loop_1 = function (pair) {
var name_1 = pair[0], prob = pair[1];
cumulativeProb += prob;
if (rand <= cumulativeProb) {
winner = (_a = variants.find(function (v) { return v.name === name_1; })) !== null && _a !== void 0 ? _a : null;
return "break";
}
};
for (var _i = 0, sorted_1 = sorted; _i < sorted_1.length; _i++) {
var pair = sorted_1[_i];
var state_1 = _loop_1(pair);
if (state_1 === "break")
break;
}
return winner;
}
exports.selectWithProbabilities = selectWithProbabilities;
/**
* Balances variant probabilities, ensuring that don't exceed 1.0.
* If all probabilities are 0, gives them equal weight.
* If the sum is < 1, balances them so that the sum is 1.
* @param variants
* @returns
*/
function balanceProbabilities(variants) {
var sum = variants.reduce(function (p, v) { var _a; return p += (_a = v.prob) !== null && _a !== void 0 ? _a : 0; }, 0);
var results = {};
for (var _i = 0, variants_1 = variants; _i < variants_1.length; _i++) {
var v = variants_1[_i];
if (!(0, utils_1.exists)(v.prob) || isNaN(v.prob) || v.prob === 0) {
results[v.name] = 0;
}
if (sum === 0) {
results[v.name] = parseFloat((1 / variants.length).toPrecision(constants.PROBABILITY_PRECISION));
}
else {
var ratio = 1 / sum;
results[v.name] = !(0, utils_1.exists)(v.prob) ? 0 : parseFloat((v.prob * ratio).toPrecision(constants.PROBABILITY_PRECISION));
}
}
return results;
}
exports.balanceProbabilities = balanceProbabilities;
function getActiveExperiment(site, variant) {
var _a, _b;
var experiment = ((_a = site.experiments) !== null && _a !== void 0 ? _a : [])
.filter(function (exp) { return !(0, utils_1.exists)(variant) ? true : exp.variants.some(function (v) { return v.name === variant; }); })
.filter(function (exp) { return exp.inactive !== true; })[0];
// No active experiment? Look for an explicit default, falling back to the implicit default
// The implicit default is always considered active if we have no other alternative
if (!experiment) {
experiment = (_b = site.experiments.filter(function (exp) { return exp.id === constants.DEFAULT_EXPERIMENT_ID; })) === null || _b === void 0 ? void 0 : _b[0];
}
return experiment !== null && experiment !== void 0 ? experiment : null;
}
exports.getActiveExperiment = getActiveExperiment;
function getDefaultExperiment(site) {
var _a, _b;
return (_b = (_a = site.experiments.filter(function (e) { return e.id === defaults_1.DEFAULT_EXPERIMENT.id; })) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : defaults_1.DEFAULT_EXPERIMENT;
}
exports.getDefaultExperiment = getDefaultExperiment;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getDefaultExperiment=exports.getActiveExperiment=exports.balanceProbabilities=exports.selectWithProbabilities=exports.getSiteProvider=exports.DEFAULT_SITE_PROVIDER_OPTIONS=void 0;var a,b=require("tslib"),c=b.__importStar(require("../constants")),j=require("../types"),d=require("../utils"),e=require("../defaults");function f(c){for(var i,d=c.variants,e=g(d),f=null,j=Math.random(),k=0,h=Object.entries(e).sort(function(a,b){return a[1]-b[1]}),a=0,b=h;a<b.length&&"break"!==function(a){var b=a[0];if(j<=(k+=a[1]))return f=null!==(i=d.find(function(a){return a.name===b}))&& void 0!==i?i:null,"break"}(b[a]);a++);return f}function g(e){for(var g=e.reduce(function(b,c){var a;return b+(null!==(a=c.prob)&& void 0!==a?a:0)},0),b={},f=0,h=e;f<h.length;f++){var a=h[f];if((!(0,d.exists)(a.prob)||isNaN(a.prob)||0===a.prob)&&(b[a.name]=0),0===g)b[a.name]=parseFloat((1/e.length).toPrecision(c.PROBABILITY_PRECISION));else{var i=1/g;b[a.name]=(0,d.exists)(a.prob)?parseFloat((a.prob*i).toPrecision(c.PROBABILITY_PRECISION)):0}}return b}function h(f,g){var b,e,a=(null!==(b=f.experiments)&& void 0!==b?b:[]).filter(function(a){return!(0,d.exists)(g)||a.variants.some(function(a){return a.name===g})}).filter(function(a){return!0!==a.inactive})[0];return a||(a=null===(e=f.experiments.filter(function(a){return a.id===c.DEFAULT_EXPERIMENT_ID}))|| void 0===e?void 0:e[0]),null!=a?a:null}function i(c){var a,b;return null!==(b=null===(a=c.experiments.filter(function(a){return a.id===e.DEFAULT_EXPERIMENT.id}))|| void 0===a?void 0:a[0])&& void 0!==b?b:e.DEFAULT_EXPERIMENT}exports.DEFAULT_SITE_PROVIDER_OPTIONS=b.__assign(b.__assign({},e.DEFAULT_OPTIONS),{sitePath:null!==(a=(0,d.env)(c.VARNAME_SITE_PATH))&& void 0!==a?a:c.DEFAULT_SITE_PATH,appendTimestamp:!1}),Object.freeze(exports.DEFAULT_SITE_PROVIDER_OPTIONS),exports.getSiteProvider=function(a){void 0===a&&(a={});var g=Object.assign({},exports.DEFAULT_SITE_PROVIDER_OPTIONS,a);Object.freeze(g);var k=j.LoadState.PRELOAD,l=null,m=e.DEFAULT_SITE,n=e.DEFAULT_EXPERIMENT,o=e.DEFAULT_VARIANT,p={get error(){return l},get origin(){if(d.isBrowserEnvironment)return location.origin;return c.DEFAULT_ORIGIN},get model(){return m},get experiment(){return n},get variant(){return o},get state(){return k},load:function(d,a,f){return void 0===a&&(a=e.DEFAULT_SITE.name),b.__awaiter(this,void 0,void 0,function(){var h,i,n,o,q,r,s;return b.__generator(this,function(b){switch(b.label){case 0:h="",b.label=1;case 1:return b.trys.push([1,5,7,8]),i=g.baseUrl,n=g.sitePath,h=(o=new URL([n,a].join("/"),i)).toString(),!0===g.appendTimestamp&&o.searchParams.append(c.PARAM_TIMESTAMP,new Date().getTime()+""),h=o.toString(),k=j.LoadState.WAIT,[4,fetch(h)];case 2:return[4,(q=b.sent()).json()];case 3:return r=b.sent(),[4,p.init(d,r,f)];case 4:return m=b.sent(),[3,8];case 5:return l=s=b.sent(),console.warn("[IB] An error occurred while loading from '".concat(h,"': ").concat(s,". Default site will be used.")),[4,p.init(d,e.DEFAULT_SITE,f)];case 6:return b.sent(),[3,8];case 7:return k=j.LoadState.READY,[7];case 8:return[2,m]}})})},init:function(d,a,f){try{if(!a||"object"!=typeof a)throw Error("Invalid site configuration");l=null,k=j.LoadState.SELECTING,m=Object.assign({},a);var b=p.select(d,f),g=b.experiment,h=b.variant;return n=Object.assign({},g),o=Object.assign({},h),k=j.LoadState.READY,m}catch(c){l=c,m=e.DEFAULT_SITE,n=e.DEFAULT_EXPERIMENT,o=e.DEFAULT_VARIANT,console.warn("[IB] Error initializing. Default site will be used. Error was: ".concat(c))}finally{k=j.LoadState.READY}return m||(m=e.DEFAULT_SITE,n=e.DEFAULT_EXPERIMENT,o=e.DEFAULT_VARIANT),m},select:function(t,g){var j,k,q,r,s;try{var c=null!==(j=null!=g?g:m.select)&& void 0!==j?j:void 0,b=null!==(q=null!==(k=h(m,c))&& void 0!==k?k:i(m))&& void 0!==q?q:e.DEFAULT_EXPERIMENT,a=null,u=t.session;if((0,d.exists)(c)&&c){var v=p.selectSpecific(b,c);b=v.experiment,a=v.variant}else if(u){var w=u.getOrCreateSession(t).selections[m.name],y=null===(r=null==w?void 0:w[b.id])|| void 0===r?void 0:r.slice().reverse()[0];a=null!==(s=b.variants.find(function(a){return a.name===y}))&& void 0!==s?s:null}return a||(a=f(b)),a||(a=e.DEFAULT_VARIANT),{experiment:b,variant:a}}catch(x){return l=x,m=e.DEFAULT_SITE,n=e.DEFAULT_EXPERIMENT,o=e.DEFAULT_VARIANT,console.warn("[IB] Error encountered while selecting variant '".concat(g,"': ").concat(x)),{experiment:e.DEFAULT_EXPERIMENT,variant:e.DEFAULT_VARIANT}}},selectSpecific:function(c,d){var b,a=c.variants.find(function(a){return a.name===d});return a?{experiment:c,variant:a}:(a=null==(b=m.experiments.find(function(a){return a.id===e.DEFAULT_EXPERIMENT.id}))?void 0:b.variants.find(function(a){return a.name===e.DEFAULT_VARIANT.name}),b&&!a)?{experiment:b,variant:e.DEFAULT_VARIANT}:a?{experiment:c,variant:a}:{experiment:e.DEFAULT_EXPERIMENT,variant:e.DEFAULT_VARIANT}}};return p},exports.selectWithProbabilities=f,exports.balanceProbabilities=g,exports.getActiveExperiment=h,exports.getDefaultExperiment=i

@@ -1,142 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_STATIC_SITE_BACKEND_OPTIONS = exports.scanSites = exports.getJsonSiteBackend = void 0;
var tslib_1 = require("tslib");
var fs_1 = tslib_1.__importDefault(require("fs"));
var path_1 = tslib_1.__importDefault(require("path"));
var process_1 = require("process");
var environment_1 = tslib_1.__importDefault(require("../environment"));
var defaults_1 = require("../../defaults");
var asyncfs = fs_1.default.promises;
/**
* Serves sites from named JSON definitions on the server
*/
function getJsonSiteBackend(initOptions) {
if (initOptions === void 0) { initOptions = {}; }
var options = Object.assign({}, exports.DEFAULT_STATIC_SITE_BACKEND_OPTIONS, initOptions);
var sites = {};
var lastScan = 0;
function logError(err) {
console.warn("[IB] Error scanning sites: ".concat(err));
}
return {
connect: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var err_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, scanSites(environment_1.default.IB_STATIC_SITES_PATH, sites, lastScan)];
case 1:
sites = _a.sent();
return [3 /*break*/, 3];
case 2:
err_1 = _a.sent();
logError(err_1);
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
});
},
getSiteConfig: function (req) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var siteName, refreshInterval, now, useRefreshInterval, intervalElapsed, siteNameOrDefault, site;
return tslib_1.__generator(this, function (_a) {
siteName = req.siteName;
refreshInterval = options.refreshInterval;
now = new Date().getTime();
useRefreshInterval = refreshInterval > -1;
intervalElapsed = (now - lastScan > refreshInterval);
if (useRefreshInterval && intervalElapsed) {
// Don't hold up the show.
scanSites(environment_1.default.IB_STATIC_SITES_PATH, sites, lastScan)
.then(function (scanned) { return sites = scanned; })
.then(function () { return lastScan = now; })
.catch(logError);
}
siteNameOrDefault = siteName !== null && siteName !== void 0 ? siteName : defaults_1.DEFAULT_SITE.name;
site = sites[siteNameOrDefault];
if (!site) {
console.warn("[IB] Could not find site '".concat(siteNameOrDefault, "'"));
return [2 /*return*/, defaults_1.DEFAULT_SITE];
}
if (site.name !== siteNameOrDefault) {
console.warn("[IB] Site '".concat(siteNameOrDefault, "'.json doesn't match name '").concat(site.name, "' in contents."));
site.name = siteNameOrDefault;
}
return [2 /*return*/, site];
});
});
}
};
}
exports.getJsonSiteBackend = getJsonSiteBackend;
/**
* Scans the public sites folder, reloading any modified sites.
* Observes a configurable interval so as to not cause unnecessary IO.
*/
function scanSites(folder, sites, lastScan) {
if (sites === void 0) { sites = {}; }
if (lastScan === void 0) { lastScan = 0; }
return tslib_1.__awaiter(this, void 0, void 0, function () {
var err_2, files, jsonFiles, root, _i, jsonFiles_1, jsonFile, siteName, fullPath, stat, mtimeMs, jsonBuffer, jsonStr, json, err_3;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, asyncfs.stat(folder)];
case 1:
_a.sent();
return [3 /*break*/, 3];
case 2:
err_2 = _a.sent();
console.warn("[IB] Public sites folder does not exist");
return [2 /*return*/, sites];
case 3: return [4 /*yield*/, asyncfs.readdir(folder)];
case 4:
files = _a.sent();
jsonFiles = files.filter(function (name) { return name.endsWith(".json"); });
root = path_1.default.join((0, process_1.cwd)(), folder);
_i = 0, jsonFiles_1 = jsonFiles;
_a.label = 5;
case 5:
if (!(_i < jsonFiles_1.length)) return [3 /*break*/, 11];
jsonFile = jsonFiles_1[_i];
siteName = jsonFile.replace(".json", "");
fullPath = path_1.default.join(root, jsonFile);
return [4 /*yield*/, asyncfs.stat(fullPath)];
case 6:
stat = _a.sent();
mtimeMs = stat.mtimeMs;
if (mtimeMs < lastScan) {
return [3 /*break*/, 10];
}
_a.label = 7;
case 7:
_a.trys.push([7, 9, , 10]);
return [4 /*yield*/, asyncfs.readFile(fullPath)];
case 8:
jsonBuffer = _a.sent();
jsonStr = jsonBuffer.toString("utf-8");
json = JSON.parse(jsonStr);
sites[siteName] = json;
return [3 /*break*/, 10];
case 9:
err_3 = _a.sent();
console.warn("[IB] Error reading '".concat(folder, "/").concat(jsonFile, "': ").concat(err_3));
return [3 /*break*/, 10];
case 10:
_i++;
return [3 /*break*/, 5];
case 11: return [2 /*return*/, sites];
}
});
});
}
exports.scanSites = scanSites;
exports.DEFAULT_STATIC_SITE_BACKEND_OPTIONS = {
basePath: environment_1.default.IB_STATIC_SITES_PATH,
refreshInterval: 10 * 1000,
};
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEFAULT_STATIC_SITE_BACKEND_OPTIONS=exports.scanSites=exports.getJsonSiteBackend=void 0;var a=require("tslib"),b=a.__importDefault(require("fs")),e=a.__importDefault(require("path")),f=require("process"),c=a.__importDefault(require("../environment")),g=require("../../defaults"),h=b.default.promises;function d(d,b,c){return void 0===b&&(b={}),void 0===c&&(c=0),a.__awaiter(this,void 0,void 0,function(){var g,i,j,k,l,m,n,o,p,q,r,s,t,u,v;return a.__generator(this,function(a){switch(a.label){case 0:return a.trys.push([0,2,,3]),[4,h.stat(d)];case 1:return a.sent(),[3,3];case 2:return g=a.sent(),console.warn("[IB] Public sites folder does not exist"),[2,b];case 3:return[4,h.readdir(d)];case 4:j=(i=a.sent()).filter(function(a){return a.endsWith(".json")}),k=e.default.join((0,f.cwd)(),d),l=0,m=j,a.label=5;case 5:if(!(l<m.length))return[3,11];return o=(n=m[l]).replace(".json",""),p=e.default.join(k,n),[4,h.stat(p)];case 6:if((r=(q=a.sent()).mtimeMs)<c)return[3,10];a.label=7;case 7:return a.trys.push([7,9,,10]),[4,h.readFile(p)];case 8:return u=JSON.parse(t=(s=a.sent()).toString("utf-8")),b[o]=u,[3,10];case 9:return v=a.sent(),console.warn("[IB] Error reading '".concat(d,"/").concat(n,"': ").concat(v)),[3,10];case 10:return l++,[3,5];case 11:return[2,b]}})})}exports.getJsonSiteBackend=function(b){var e=function(a){console.warn("[IB] Error scanning sites: ".concat(a))};void 0===b&&(b={});var f=Object.assign({},exports.DEFAULT_STATIC_SITE_BACKEND_OPTIONS,b),h={},i=0;return{connect:function(){return a.__awaiter(this,void 0,void 0,function(){var b;return a.__generator(this,function(a){switch(a.label){case 0:return a.trys.push([0,2,,3]),[4,d(c.default.IB_STATIC_SITES_PATH,h,i)];case 1:return h=a.sent(),[3,3];case 2:return e(b=a.sent()),[3,3];case 3:return[2]}})})},getSiteConfig:function(b){return a.__awaiter(this,void 0,void 0,function(){var j,k,l,m,n,o,p;return a.__generator(this,function(a){return(j=b.siteName,k=f.refreshInterval,l=new Date().getTime(),m=k> -1,n=l-i>k,m&&n&&d(c.default.IB_STATIC_SITES_PATH,h,i).then(function(a){return h=a}).then(function(){return i=l}).catch(e),o=null!=j?j:g.DEFAULT_SITE.name,p=h[o])?(p.name!==o&&(console.warn("[IB] Site '".concat(o,"'.json doesn't match name '").concat(p.name,"' in contents.")),p.name=o),[2,p]):(console.warn("[IB] Could not find site '".concat(o,"'")),[2,g.DEFAULT_SITE])})})}}},exports.scanSites=d,exports.DEFAULT_STATIC_SITE_BACKEND_OPTIONS={basePath:c.default.IB_STATIC_SITES_PATH,refreshInterval:1e4}

@@ -1,31 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStubMetricsBackend = void 0;
var tslib_1 = require("tslib");
function getStubMetricsBackend() {
return {
getMetricsForSite: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
return [2 /*return*/, new Map()];
});
});
},
getMetricsBucket: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
return [2 /*return*/, {}];
});
});
},
ingestBatch: function (req, batch) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
console.log("Metrics stub ingests batch", batch);
return [2 /*return*/];
});
});
},
};
}
exports.getStubMetricsBackend = getStubMetricsBackend;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getStubMetricsBackend=void 0;var a=require("tslib");exports.getStubMetricsBackend=function(){return{getMetricsForSite:function(){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return[2,new Map]})})},getMetricsBucket:function(){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return[2,{}]})})},ingestBatch:function(b,c){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return console.log("Metrics stub ingests batch",c),[2]})})}}}

@@ -1,384 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMetricsBucket = exports.incrementMetricInPipeline = exports.isValidMetricsSample = exports.ingestBatch = exports.markVariantSeen = exports.getOrCreateSession = exports.getMetricsForSite = exports.getRedisBackend = exports.DEFAULT_REDIS_OPTIONS = void 0;
var tslib_1 = require("tslib");
var crypto_1 = require("crypto");
var ioredis_1 = tslib_1.__importDefault(require("ioredis"));
var environment_1 = tslib_1.__importDefault(require("../environment"));
var server_utils_1 = require("../server-utils");
var utils_1 = require("../../utils");
var constants_1 = require("../../constants");
exports.DEFAULT_REDIS_OPTIONS = {
lazyConnect: true,
disconnectWaitDuration: 50,
retryStrategy: function (count) {
var maxAttempts = parseInt(environment_1.default.IB_REDIS_RETRY_COUNT + "");
var interval = parseInt(environment_1.default.IB_REDIS_RETRY_INTERVAL + "");
console.info("[IB] Connecting to Redis attempt # ".concat(count, " out of ").concat(maxAttempts));
if (count >= maxAttempts) {
return null;
}
else {
return interval;
}
},
};
function getRedisBackend(initOptions) {
if (initOptions === void 0) { initOptions = {}; }
var options = Object.freeze(Object.assign({}, exports.DEFAULT_REDIS_OPTIONS, initOptions));
var redis = new ioredis_1.default(options);
var connected = false;
return {
get client() { return redis; },
get connected() { return connected; },
connect: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
return [2 /*return*/, new Promise(function (resolve) {
if (connected) {
resolve();
return;
}
switch (redis.status) {
case "connecting":
case "connect":
case "ready":
resolve();
return;
default:
console.info("[IB] Connecting to redis...");
redis
.on("error", function (err) {
console.error("[IB] Error connecting to Redis: ".concat(err));
connected = false;
return;
})
.on("close", function () {
connected = false;
console.log("[IB] Redis connection closed");
return;
})
.on("connect", function () {
connected = true;
console.log("[IB] Redis connection opened");
return;
})
.connect()
// Despite the error handler above, ioredis will still throw.
// Suppress, because it's handled above.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.catch(function (err) { return void 0; });
resolve();
return;
}
})];
});
});
},
disconnect: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, , 3, 6]);
console.debug("[IB] Disconnecting from redis...");
if (!(redis.status === "ready")) return [3 /*break*/, 2];
return [4 /*yield*/, redis.quit()];
case 1:
_a.sent();
_a.label = 2;
case 2:
connected = false;
redis.disconnect();
return [3 /*break*/, 6];
case 3:
if (!(redis.status === "ready")) return [3 /*break*/, 5];
return [4 /*yield*/, new Promise(function (r) { return setTimeout(r, options.disconnectWaitDuration); })];
case 4:
_a.sent();
return [3 /*break*/, 3];
case 5: return [7 /*endfinally*/];
case 6: return [2 /*return*/];
}
});
});
},
getMetricsForSite: function (site, experiments) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!connected) {
return [2 /*return*/, new Map()];
}
return [2 /*return*/, getMetricsForSite(redis, site, experiments)];
});
});
},
getMetricsBucket: function (siteId, experiment, variant) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!connected) {
return [2 /*return*/, {}];
}
return [2 /*return*/, getMetricsBucket(redis, siteId, experiment, variant)];
});
});
},
ingestBatch: function (req, batch) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!connected) {
return [2 /*return*/];
}
return [2 /*return*/, ingestBatch(redis, req, batch)];
});
});
},
getOrCreateSession: function (req) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!connected) {
throw new Error("Not connected to Redis session store");
}
return [2 /*return*/, getOrCreateSession(redis, req)];
});
});
},
markVariantSeen: function (session, site, experimentId, variantName) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!connected) {
(0, utils_1.markVariantInSession)(session, site, experimentId, variantName);
return [2 /*return*/, session];
}
return [2 /*return*/, markVariantSeen(redis, session, site, experimentId, variantName)];
});
});
},
};
}
exports.getRedisBackend = getRedisBackend;
function getMetricsForSite(redis, site, experiments) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var variantBuckets, _i, experiments_1, exp, _a, _b, variant, bucket;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
variantBuckets = new Map();
_i = 0, experiments_1 = experiments;
_c.label = 1;
case 1:
if (!(_i < experiments_1.length)) return [3 /*break*/, 6];
exp = experiments_1[_i];
_a = 0, _b = exp.variants;
_c.label = 2;
case 2:
if (!(_a < _b.length)) return [3 /*break*/, 5];
variant = _b[_a];
return [4 /*yield*/, getMetricsBucket(redis, site.name, exp.id, variant.name)];
case 3:
bucket = _c.sent();
variantBuckets.set(variant, bucket);
_c.label = 4;
case 4:
_a++;
return [3 /*break*/, 2];
case 5:
_i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/, variantBuckets];
}
});
});
}
exports.getMetricsForSite = getMetricsForSite;
function getOrCreateSession(redis, req) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var siteName, sid, sessionsSetKey, session, sessionKey, sessionRaw, serializedSession, pipe, sessionKey, err_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
siteName = req.siteName;
sid = req.sid;
sessionsSetKey = (0, server_utils_1.makeKey)(["sessions"]);
session = null;
if (!((0, utils_1.exists)(sid) && sid.length === constants_1.UUID_LENGTH)) return [3 /*break*/, 2];
sessionKey = (0, server_utils_1.makeKey)(["session", sid]);
return [4 /*yield*/, redis.get(sessionKey)];
case 1:
sessionRaw = _a.sent();
if (!(0, utils_1.exists)(sessionRaw)) {
console.warn("[IB] Invalid or unknown session '".concat(sessionKey, "'"));
}
else {
session = JSON.parse(sessionRaw);
return [2 /*return*/, session];
}
_a.label = 2;
case 2:
if (!!(0, utils_1.exists)(session)) return [3 /*break*/, 6];
if (!(0, utils_1.exists)(siteName)) {
throw new Error("Invalid session scope");
}
sid = (0, crypto_1.randomUUID)();
session = {
sid: sid,
selections: {},
};
serializedSession = JSON.stringify(session);
pipe = redis.multi();
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
sessionKey = (0, server_utils_1.makeKey)(["session", session.sid]);
pipe.sadd(sessionsSetKey, session.sid);
pipe.set(sessionKey, serializedSession);
return [4 /*yield*/, pipe.exec()];
case 4:
_a.sent();
return [3 /*break*/, 6];
case 5:
err_1 = _a.sent();
console.warn("[IB] Error saving session '".concat(sid, "': ").concat(err_1));
return [3 /*break*/, 6];
case 6: return [2 /*return*/, session];
}
});
});
}
exports.getOrCreateSession = getOrCreateSession;
function markVariantSeen(redis, session, site, experimentId, variantName) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var serializedSession, sessionKey, err_2;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
(0, utils_1.markVariantInSession)(session, site, experimentId, variantName);
serializedSession = JSON.stringify(session);
sessionKey = (0, server_utils_1.makeKey)(["session", session.sid]);
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, redis.set(sessionKey, serializedSession)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
err_2 = _a.sent();
console.warn("[IB] Error saving session '".concat(session.sid, "': ").concat(err_2));
return [3 /*break*/, 4];
case 4: return [2 /*return*/, session];
}
});
});
}
exports.markVariantSeen = markVariantSeen;
/**
* Ingests a batch of metrics, pipelining it into Redis
* @param redis
*/
function ingestBatch(redis, req, batch) {
var _a;
return tslib_1.__awaiter(this, void 0, void 0, function () {
var sid, site, experiment, variant, samples, session, _b, pipe, prevTs, err_3;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
sid = req.sid;
if (!(0, utils_1.exists)(sid)) {
throw new Error("Missing session");
}
site = batch.site, experiment = batch.experiment, variant = batch.variant, samples = batch.entries;
if (!((_a = req.session) !== null && _a !== void 0)) return [3 /*break*/, 1];
_b = _a;
return [3 /*break*/, 3];
case 1: return [4 /*yield*/, getOrCreateSession(redis, req)];
case 2:
_b = _c.sent();
_c.label = 3;
case 3:
session = _b;
return [4 /*yield*/, markVariantSeen(redis, session, site, experiment, variant)];
case 4:
_c.sent();
pipe = redis.pipeline();
prevTs = 0;
samples
.filter(isValidMetricsSample)
.forEach(function (sample) {
var ts = sample.ts, name = sample.name;
// Ignore out of order timestamps
if (ts < prevTs) {
return;
}
incrementMetricInPipeline(pipe, site, experiment, variant, name, 1);
prevTs = ts;
});
_c.label = 5;
case 5:
_c.trys.push([5, 7, , 8]);
return [4 /*yield*/, pipe.exec()];
case 6:
_c.sent();
return [3 /*break*/, 8];
case 7:
err_3 = _c.sent();
console.error("[IB] Error while pipelining ".concat(samples.length, " for '").concat(sid, "'"));
return [3 /*break*/, 8];
case 8: return [2 /*return*/];
}
});
});
}
exports.ingestBatch = ingestBatch;
function isValidMetricsSample(sample) {
var ts = sample.ts, payload = sample.payload;
if (typeof ts !== "number") {
return false;
}
else if (typeof payload === "string" &&
payload.length > parseInt(environment_1.default.IB_MAX_METRICS_PAYLOAD_LEN + "")) {
return false;
}
else {
return true;
}
}
exports.isValidMetricsSample = isValidMetricsSample;
function incrementMetricInPipeline(pipe, site, eid, variant, metric, incBy) {
if (incBy === void 0) { incBy = 1; }
if (site === null || site === void 0) {
throw new Error("Missing site ID in incrementExposure");
}
var key = (0, server_utils_1.makeKey)([site, eid, variant, "metrics"]);
// TODO: Note the loss of bits, TEST
// NOTE: Conversion from an integer to a float can happen here.
if (incBy % 1 === 0) {
pipe.hincrby(key, metric, incBy);
}
else {
pipe.hincrbyfloat(key, metric, incBy);
}
return pipe;
}
exports.incrementMetricInPipeline = incrementMetricInPipeline;
function getMetricsBucket(redis, siteId, eid, variant) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var key, hmap, bucket;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
key = (0, server_utils_1.makeKey)([siteId, eid, variant, "metrics"]);
return [4 /*yield*/, redis.hgetall(key)];
case 1:
hmap = _a.sent();
if (!(0, utils_1.exists)(hmap)) {
return [2 /*return*/, {}];
}
bucket = {};
Object.keys(hmap).forEach(function (k) { return bucket[k] = (0, server_utils_1.toNumber)(hmap[k]); });
return [2 /*return*/, bucket];
}
});
});
}
exports.getMetricsBucket = getMetricsBucket;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getMetricsBucket=exports.incrementMetricInPipeline=exports.isValidMetricsSample=exports.ingestBatch=exports.markVariantSeen=exports.getOrCreateSession=exports.getMetricsForSite=exports.getRedisBackend=exports.DEFAULT_REDIS_OPTIONS=void 0;var a=require("tslib"),i=require("crypto"),j=a.__importDefault(require("ioredis")),k=a.__importDefault(require("../environment")),l=require("../server-utils"),m=require("../../utils"),n=require("../../constants");function b(b,c,d){return a.__awaiter(this,void 0,void 0,function(){var e,f,g,i,j,k,l,m;return a.__generator(this,function(a){switch(a.label){case 0:e=new Map,f=0,g=d,a.label=1;case 1:if(!(f<g.length))return[3,6];j=0,k=(i=g[f]).variants,a.label=2;case 2:if(!(j<k.length))return[3,5];return l=k[j],[4,h(b,c.name,i.id,l.name)];case 3:m=a.sent(),e.set(l,m),a.label=4;case 4:return j++,[3,2];case 5:return f++,[3,1];case 6:return[2,e]}})})}function c(b,c){return a.__awaiter(this,void 0,void 0,function(){var d,e,f,g,h,j,k,o,p;return a.__generator(this,function(a){switch(a.label){case 0:if(d=c.siteName,e=c.sid,f=(0,l.makeKey)(["sessions"]),g=null,!((0,m.exists)(e)&&e.length===n.UUID_LENGTH))return[3,2];return o=(0,l.makeKey)(["session",e]),[4,b.get(o)];case 1:if(h=a.sent(),(0,m.exists)(h))return[2,g=JSON.parse(h)];console.warn("[IB] Invalid or unknown session '".concat(o,"'")),a.label=2;case 2:if((0,m.exists)(g))return[3,6];if(!(0,m.exists)(d))throw Error("Invalid session scope");j=JSON.stringify(g={sid:e=(0,i.randomUUID)(),selections:{}}),k=b.multi(),a.label=3;case 3:return a.trys.push([3,5,,6]),o=(0,l.makeKey)(["session",g.sid]),k.sadd(f,g.sid),k.set(o,j),[4,k.exec()];case 4:return a.sent(),[3,6];case 5:return p=a.sent(),console.warn("[IB] Error saving session '".concat(e,"': ").concat(p)),[3,6];case 6:return[2,g]}})})}function d(b,c,d,e,f){return a.__awaiter(this,void 0,void 0,function(){var g,h,i;return a.__generator(this,function(a){switch(a.label){case 0:(0,m.markVariantInSession)(c,d,e,f),g=JSON.stringify(c),h=(0,l.makeKey)(["session",c.sid]),a.label=1;case 1:return a.trys.push([1,3,,4]),[4,b.set(h,g)];case 2:return a.sent(),[3,4];case 3:return i=a.sent(),console.warn("[IB] Error saving session '".concat(c.sid,"': ").concat(i)),[3,4];case 4:return[2,c]}})})}function e(b,e,h){var i;return a.__awaiter(this,void 0,void 0,function(){var j,k,l,n,o,p,q,r,s,t;return a.__generator(this,function(a){switch(a.label){case 0:if(j=e.sid,!(0,m.exists)(j))throw Error("Missing session");if(k=h.site,l=h.experiment,n=h.variant,o=h.entries,!(null!==(i=e.session)&& void 0!==i))return[3,1];return q=i,[3,3];case 1:return[4,c(b,e)];case 2:q=a.sent(),a.label=3;case 3:return[4,d(b,p=q,k,l,n)];case 4:a.sent(),r=b.pipeline(),s=0,o.filter(f).forEach(function(a){var b=a.ts,c=a.name;!(b<s)&&(g(r,k,l,n,c,1),s=b)}),a.label=5;case 5:return a.trys.push([5,7,,8]),[4,r.exec()];case 6:return a.sent(),[3,8];case 7:return t=a.sent(),console.error("[IB] Error while pipelining ".concat(o.length," for '").concat(j,"'")),[3,8];case 8:return[2]}})})}function f(a){var c=a.ts,b=a.payload;return"number"==typeof c&&!("string"==typeof b&&b.length>parseInt(k.default.IB_MAX_METRICS_PAYLOAD_LEN+""))}function g(b,c,f,g,d,a){if(void 0===a&&(a=1),null==c)throw Error("Missing site ID in incrementExposure");var e=(0,l.makeKey)([c,f,g,"metrics"]);return a%1==0?b.hincrby(e,d,a):b.hincrbyfloat(e,d,a),b}function h(b,c,d,e){return a.__awaiter(this,void 0,void 0,function(){var f,g,h;return a.__generator(this,function(a){switch(a.label){case 0:return f=(0,l.makeKey)([c,d,e,"metrics"]),[4,b.hgetall(f)];case 1:if(g=a.sent(),!(0,m.exists)(g))return[2,{}];return h={},Object.keys(g).forEach(function(a){return h[a]=(0,l.toNumber)(g[a])}),[2,h]}})})}exports.DEFAULT_REDIS_OPTIONS={lazyConnect:!0,disconnectWaitDuration:50,retryStrategy:function(a){var b=parseInt(k.default.IB_REDIS_RETRY_COUNT+""),c=parseInt(k.default.IB_REDIS_RETRY_INTERVAL+"");return(console.info("[IB] Connecting to Redis attempt # ".concat(a," out of ").concat(b)),a>=b)?null:c}},exports.getRedisBackend=function(f){void 0===f&&(f={});var g=Object.freeze(Object.assign({},exports.DEFAULT_REDIS_OPTIONS,f)),i=new j.default(g),k=!1;return{get client(){return i},get connected(){return k},connect:function(){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return[2,new Promise(function(a){if(k){a();return}switch(i.status){case"connecting":case"connect":case"ready":a();return;default:console.info("[IB] Connecting to redis..."),i.on("error",function(a){console.error("[IB] Error connecting to Redis: ".concat(a)),k=!1}).on("close",function(){k=!1,console.log("[IB] Redis connection closed")}).on("connect",function(){k=!0,console.log("[IB] Redis connection opened")}).connect().catch(function(a){}),a();return}})]})})},disconnect:function(){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){switch(a.label){case 0:if(a.trys.push([0,,3,6]),console.debug("[IB] Disconnecting from redis..."),"ready"!==i.status)return[3,2];return[4,i.quit()];case 1:a.sent(),a.label=2;case 2:return k=!1,i.disconnect(),[3,6];case 3:if("ready"!==i.status)return[3,5];return[4,new Promise(function(a){return setTimeout(a,g.disconnectWaitDuration)})];case 4:return a.sent(),[3,3];case 5:return[7];case 6:return[2]}})})},getMetricsForSite:function(c,d){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return k?[2,b(i,c,d)]:[2,new Map]})})},getMetricsBucket:function(b,c,d){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return k?[2,h(i,b,c,d)]:[2,{}]})})},ingestBatch:function(b,c){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return k?[2,e(i,b,c)]:[2]})})},getOrCreateSession:function(b){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){if(!k)throw Error("Not connected to Redis session store");return[2,c(i,b)]})})},markVariantSeen:function(b,c,e,f){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return k?[2,d(i,b,c,e,f)]:((0,m.markVariantInSession)(b,c,e,f),[2,b])})})}}},exports.getMetricsForSite=b,exports.getOrCreateSession=c,exports.markVariantSeen=d,exports.ingestBatch=e,exports.isValidMetricsSample=f,exports.incrementMetricInPipeline=g,exports.getMetricsBucket=h

@@ -1,44 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStubSessionsBackend = void 0;
var tslib_1 = require("tslib");
var crypto_1 = require("crypto");
var utils_1 = require("../../utils");
function getStubSessionsBackend() {
var sessions = {};
return {
getOrCreateSession: function (req) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var siteName, sid, session;
return tslib_1.__generator(this, function (_a) {
siteName = req.siteName;
sid = req.sid;
if (!(0, utils_1.exists)(siteName)) {
throw new Error("Invalid session scope");
}
session = null;
if ((0, utils_1.exists)(sid) && sid.trim() !== "") {
session = sessions[sid];
if (!(0, utils_1.exists)(session)) {
console.warn("[IB] Missing or expired session '".concat(sid, "'"));
session = null;
}
}
if (!session) {
return [2 /*return*/, (0, utils_1.makeNewSession)((0, crypto_1.randomUUID)())];
}
return [2 /*return*/, session];
});
});
},
markVariantSeen: function (session, site, experiment, variant) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
(0, utils_1.markVariantInSession)(session, site, experiment, variant);
return [2 /*return*/, session];
});
});
}
};
}
exports.getStubSessionsBackend = getStubSessionsBackend;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getStubSessionsBackend=void 0;var a=require("tslib"),b=require("crypto"),c=require("../../utils");exports.getStubSessionsBackend=function(){var d={};return{getOrCreateSession:function(e){return a.__awaiter(this,void 0,void 0,function(){var f,g,h;return a.__generator(this,function(a){if(f=e.siteName,g=e.sid,!(0,c.exists)(f))throw Error("Invalid session scope");return(h=null,(0,c.exists)(g)&&""!==g.trim()&&(h=d[g],(0,c.exists)(h)||(console.warn("[IB] Missing or expired session '".concat(g,"'")),h=null)),h)?[2,h]:[2,(0,c.makeNewSession)((0,b.randomUUID)())]})})},markVariantSeen:function(b,d,e,f){return a.__awaiter(this,void 0,void 0,function(){return a.__generator(this,function(a){return(0,c.markVariantInSession)(b,d,e,f),[2,b]})})}}}

@@ -5,2 +5,3 @@ declare const defaults: {

IB_COOKIE_SETTINGS: string;
IB_ENFORCE_ORIGIN_CHECK: string;
IB_ORIGINS_ALLOWLIST: string;

@@ -7,0 +8,0 @@ IB_REDIS_RETRY_INTERVAL: number;

@@ -1,26 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var constants_1 = require("../constants");
// These are overlaid with process variables, if set
var defaults = {
IB_MODE: process.env.NODE_ENV,
IB_BASE_API_URL: constants_1.DEFAULT_BASE_URL,
IB_COOKIE_SETTINGS: constants_1.DEFAULT_COOKIE_SETTINGS,
IB_ORIGINS_ALLOWLIST: process.env.NODE_ENV === "production" ? "" : constants_1.DEFAULT_BASE_URL,
IB_REDIS_RETRY_INTERVAL: 1000,
IB_REDIS_RETRY_COUNT: 10,
IB_STATIC_SITES_PATH: "./public/sites",
IB_MAX_METRICS_PAYLOAD_LEN: 1024,
};
var funcs = {
isDev: function () { return env.IB_MODE === "development"; },
isTest: function () { return env.IB_MODE === "test"; },
isProduction: function () { return env.IB_MODE === "production"; },
};
var env = Object.assign({}, defaults, funcs);
// Pull from actual environment vars, or use the defaults above
Object
.keys(defaults)
.filter(function (key) { return typeof process.env[key] !== "undefined"; })
.forEach(function (key) { return env[key] = process.env[key]; });
exports.default = env;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var a=require("../constants"),b={IB_MODE:process.env.NODE_ENV,IB_BASE_API_URL:a.DEFAULT_BASE_URL,IB_COOKIE_SETTINGS:a.DEFAULT_COOKIE_SETTINGS,IB_ENFORCE_ORIGIN_CHECK:"false",IB_ORIGINS_ALLOWLIST:"production"===process.env.NODE_ENV?"":a.DEFAULT_BASE_URL,IB_REDIS_RETRY_INTERVAL:1e3,IB_REDIS_RETRY_COUNT:10,IB_STATIC_SITES_PATH:"./public/sites",IB_MAX_METRICS_PAYLOAD_LEN:1024},c=Object.assign({},b,{isDev:function(){return"development"===c.IB_MODE},isTest:function(){return"test"===c.IB_MODE},isProduction:function(){return"production"===c.IB_MODE}});Object.keys(b).filter(function(a){return void 0!==process.env[a]}).forEach(function(a){return c[a]=process.env[a]}),exports.default=c
import { InstantBanditServerOptions, InstantBanditServer, ValidatedRequest, MetricsBackend } from "./server-types";
import { Site, SiteMeta } from "../models";
export declare const DEFAULT_SERVER_OPTIONS: Partial<InstantBanditServerOptions>;
export declare const DEFAULT_SERVER_OPTIONS: InstantBanditServerOptions;
/**

@@ -5,0 +5,0 @@ * Provides framework-agnostic helper methods that expose configuration and handle

@@ -1,217 +0,1 @@

"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBanditServer = exports.log = exports.TAG = exports.embedProbabilities = exports.buildInstantBanditServer = exports.DEFAULT_SERVER_OPTIONS = void 0;
var tslib_1 = require("tslib");
var environment_1 = tslib_1.__importDefault(require("./environment"));
var utils_1 = require("../utils");
var json_sites_1 = require("./backends/json-sites");
var server_utils_1 = require("./server-utils");
var bandit_1 = require("../bandit");
var redis_1 = require("./backends/redis");
exports.DEFAULT_SERVER_OPTIONS = {
clientOrigins: ((_a = environment_1.default.IB_ORIGINS_ALLOWLIST) !== null && _a !== void 0 ? _a : ""),
};
/**
* Provides framework-agnostic helper methods that expose configuration and handle
* initialization and shutdown of backend services for metrics, models, and sessions.
*
* Note: Implementers should call `getBanditServer` with their own config instead.
*
* @private
*/
function buildInstantBanditServer(initOptions) {
var _a;
var options = Object.assign({}, exports.DEFAULT_SERVER_OPTIONS, initOptions);
// Only instantiate the backends if needed
var defaultRedisBackend = null;
if (!options.models) {
options.models = (0, json_sites_1.getJsonSiteBackend)();
}
if (!options.metrics) {
options.metrics = defaultRedisBackend = (0, redis_1.getRedisBackend)();
}
if (!options.sessions) {
options.sessions = (0, utils_1.exists)(defaultRedisBackend) ? defaultRedisBackend
: (defaultRedisBackend = (0, redis_1.getRedisBackend)());
}
Object.freeze(options);
var metrics = options.metrics, models = options.models, sessions = options.sessions;
var devOrigins = environment_1.default.isDev() ? [(0, utils_1.getBaseUrl)()] : [];
var allowedOrigins = (0, server_utils_1.normalizeOrigins)((_a = options.clientOrigins) !== null && _a !== void 0 ? _a : [], devOrigins);
var backends = [metrics, models, sessions];
var initPromise;
var shutdownPromise;
return {
get metrics() { return metrics; },
get models() { return models; },
get sessions() { return sessions; },
get origins() { return allowedOrigins; },
isBackendConnected: function (backend) {
// Backends that don't implement connection logic are considered "connected"
if (!(0, utils_1.exists)(backend.connected)) {
return true;
}
else {
return backend.connected;
}
},
init: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (initPromise) {
return [2 /*return*/, initPromise];
}
initPromise = Promise.all(backends.filter(utils_1.exists).map(function (be) { var _a; return (_a = be.connect) === null || _a === void 0 ? void 0 : _a.call(be); }))
.catch(function (err) { return console.warn("[IB]: Error initializing: ".concat(err)); })
.then(function () { return void 0; });
(0, exports.log)("Server initializing....");
return [4 /*yield*/, initPromise];
case 1:
_a.sent();
(0, exports.log)("Server initialized");
return [2 /*return*/];
}
});
});
},
shutdown: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (shutdownPromise) {
return [2 /*return*/, shutdownPromise];
}
shutdownPromise = Promise.all(backends.filter(utils_1.exists).map(function (be) { var _a; return (_a = be.disconnect) === null || _a === void 0 ? void 0 : _a.call(be); }))
.catch(function (err) { return console.warn("[IB]: Error shutting down: ".concat(err)); })
.then(function () { return void 0; });
(0, exports.log)("Server shutting down....");
return [4 /*yield*/, shutdownPromise];
case 1:
_a.sent();
(0, exports.log)("Server shut down");
return [2 /*return*/];
}
});
});
},
/**
* Produces a site object bearing probabilities and ready for consumer selection
*/
getSite: function (req) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var getSiteConfig, siteConfig, siteWithProbs;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
getSiteConfig = models.getSiteConfig;
return [4 /*yield*/, getSiteConfig(req)];
case 1:
siteConfig = _a.sent();
return [4 /*yield*/, embedProbabilities(req, siteConfig, metrics)];
case 2:
siteWithProbs = _a.sent();
return [2 /*return*/, {
responseHeaders: {},
site: siteWithProbs,
}];
}
});
});
}
};
}
exports.buildInstantBanditServer = buildInstantBanditServer;
/**
* Computes probabilities required for variant selection and inlines them into a site configuration
*/
function embedProbabilities(req, origSite, metrics) {
var _a, _b;
return tslib_1.__awaiter(this, void 0, void 0, function () {
var site, experiments, variantMetrics, _i, _c, experiment, variants, exposures, conversions, _d, variants_1, variant, bucket, probs, _loop_1, key;
return tslib_1.__generator(this, function (_e) {
switch (_e.label) {
case 0:
site = JSON.parse(JSON.stringify(origSite));
experiments = site.experiments;
return [4 /*yield*/, metrics.getMetricsForSite(site, experiments)];
case 1:
variantMetrics = _e.sent();
for (_i = 0, _c = experiments; _i < _c.length; _i++) {
experiment = _c[_i];
variants = experiment.variants;
exposures = {};
conversions = {};
for (_d = 0, variants_1 = variants; _d < variants_1.length; _d++) {
variant = variants_1[_d];
if (!variantMetrics.has(variant)) {
continue;
}
bucket = variantMetrics.get(variant);
// Show raw metrics in dev mode
if (environment_1.default.isDev()) {
variant.metrics = bucket;
}
exposures[variant.name] = (_a = bucket.exposures) !== null && _a !== void 0 ? _a : 0;
conversions[variant.name] = (_b = bucket.conversions) !== null && _b !== void 0 ? _b : 0;
}
probs = void 0;
if (Object.keys(exposures).length > 0 && Object.keys(conversions).length > 0) {
probs = (0, bandit_1.bandit)(exposures, conversions || {});
}
else {
probs = {};
}
experiment.metrics = {};
_loop_1 = function (key) {
var variant = variants.find(function (v) { return v.name === key; });
if (variant) {
variant.prob = probs[key];
}
};
for (key in probs) {
_loop_1(key);
}
}
return [2 /*return*/, site];
}
});
});
}
exports.embedProbabilities = embedProbabilities;
exports.TAG = "[IB][server]";
var log = function () {
var items = [];
for (var _i = 0; _i < arguments.length; _i++) {
items[_i] = arguments[_i];
}
console.info.apply(console, tslib_1.__spreadArray([exports.TAG], items, true));
};
exports.log = log;
/**
* Creates an `InstantBanditServer` you can use in your backend app.
*/
var banditServer;
function getBanditServer(options) {
// NOTE: Next.js will re-import modules during HMR.
// This ends up creating multiple spurious instances of module functions, state,
// network resources, database connections, etc.
// A workaround is to attach an object to the global scope during dev.
if (environment_1.default.isDev()) {
if (global["defaultBanditServer"]) {
banditServer = global["defaultBanditServer"];
}
}
if (!banditServer) {
console.debug("[IB] Creating default InstantBanditServer...");
banditServer = buildInstantBanditServer(options);
}
if (environment_1.default.isDev()) {
global["defaultBanditServer"] = banditServer;
}
return banditServer;
}
exports.getBanditServer = getBanditServer;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getBanditServer=exports.log=exports.TAG=exports.embedProbabilities=exports.buildInstantBanditServer=exports.DEFAULT_SERVER_OPTIONS=void 0;var a,j,b=require("tslib"),c=b.__importStar(require("../constants")),d=b.__importDefault(require("./environment")),k=require("./backends/redis"),l=require("../bandit"),m=require("../utils"),e=require("./backends/json-sites"),f=require("./backends/metrics"),g=require("./backends/sessions"),n=require("./server-utils");function h(l){var f,q,r,a=Object.assign({},exports.DEFAULT_SERVER_OPTIONS,l),c=null;a.models||(a.models=(0,e.getJsonSiteBackend)()),a.metrics||(a.metrics=c=(0,k.getRedisBackend)()),a.sessions||(a.sessions=(0,m.exists)(c)?c:c=(0,k.getRedisBackend)()),Object.freeze(a);var g=a.metrics,h=a.models,j=a.sessions,o=d.default.isDev()?[(0,m.getBaseUrl)()]:[],p=(0,n.normalizeOrigins)(null!==(f=a.clientOrigins)&& void 0!==f?f:[],o),s=[g,h,j];return{get options(){return a},get metrics(){return g},get models(){return h},get sessions(){return j},get origins(){return p},isBackendConnected:function(a){return!(0,m.exists)(a.connected)||a.connected},init:function(){return b.__awaiter(this,void 0,void 0,function(){return b.__generator(this,function(a){switch(a.label){case 0:if(q)return[2,q];return q=Promise.all(s.filter(m.exists).map(function(b){var a;return null===(a=b.connect)|| void 0===a?void 0:a.call(b)})).catch(function(a){return console.warn("[IB]: Error initializing: ".concat(a))}).then(function(){}),(0,exports.log)("Server initializing...."),[4,q];case 1:return a.sent(),(0,exports.log)("Server initialized"),[2]}})})},shutdown:function(){return b.__awaiter(this,void 0,void 0,function(){return b.__generator(this,function(a){switch(a.label){case 0:if(r)return[2,r];return r=Promise.all(s.filter(m.exists).map(function(b){var a;return null===(a=b.disconnect)|| void 0===a?void 0:a.call(b)})).catch(function(a){return console.warn("[IB]: Error shutting down: ".concat(a))}).then(function(){}),(0,exports.log)("Server shutting down...."),[4,r];case 1:return a.sent(),(0,exports.log)("Server shut down"),[2]}})})},getSite:function(a){return b.__awaiter(this,void 0,void 0,function(){var c,d,e;return b.__generator(this,function(b){switch(b.label){case 0:return[4,(c=h.getSiteConfig)(a)];case 1:return[4,i(a,d=b.sent(),g)];case 2:return[2,{responseHeaders:{},site:e=b.sent()}]}})})}}}function i(a,c,e){var f,g;return b.__awaiter(this,void 0,void 0,function(){var a,h,i,j,k,m,n,o,p,q,r,s,t,u,v,w;return b.__generator(this,function(b){switch(b.label){case 0:return h=(a=JSON.parse(JSON.stringify(c))).experiments,[4,e.getMetricsForSite(a,h)];case 1:for(j=0,i=b.sent(),k=h;j<k.length;j++){for(q=0,n=(m=k[j]).variants,o={},p={},r=n;q<r.length;q++)s=r[q],i.has(s)&&(t=i.get(s),d.default.isDev()&&(s.metrics=t),o[s.name]=null!==(f=t.exposures)&& void 0!==f?f:0,p[s.name]=null!==(g=t.conversions)&& void 0!==g?g:0);for(w in u=void 0,u=Object.keys(o).length>0&&Object.keys(p).length>0?(0,l.bandit)(o,p||{}):{},m.metrics={},v=function(b){var a=n.find(function(a){return a.name===b});a&&(a.prob=u[b])},u)v(w)}return[2,a]}})})}exports.DEFAULT_SERVER_OPTIONS={clientOrigins:null!==(a=d.default.IB_ORIGINS_ALLOWLIST)&& void 0!==a?a:"",allowMetricsPayloads:!1,maxBatchItemLength:c.METRICS_MAX_ITEM_LENGTH,maxBatchLength:c.METRICS_MAX_LENGTH,metrics:(0,f.getStubMetricsBackend)(),sessions:(0,g.getStubSessionsBackend)(),models:(0,e.getJsonSiteBackend)()},Object.freeze(exports.DEFAULT_SERVER_OPTIONS),exports.buildInstantBanditServer=h,exports.embedProbabilities=i,exports.TAG="[IB][server]",exports.log=function(){for(var c=[],a=0;a<arguments.length;a++)c[a]=arguments[a];console.info.apply(console,b.__spreadArray([exports.TAG],c,!0))},exports.getBanditServer=function(a){return d.default.isDev()&&global.defaultBanditServer&&(j=global.defaultBanditServer),j||(console.debug("[IB] Creating default InstantBanditServer..."),j=h(a)),d.default.isDev()&&(global.defaultBanditServer=j),j}

@@ -1,44 +0,1 @@

"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getInternalDevServer = void 0;
var tslib_1 = require("tslib");
var environment_1 = tslib_1.__importDefault(require("./environment"));
var server_core_1 = require("./server-core");
var json_sites_1 = require("./backends/json-sites");
var redis_1 = require("./backends/redis");
var redis = (0, redis_1.getRedisBackend)();
var json = (0, json_sites_1.getJsonSiteBackend)();
var DEFAULT_DEV_SERVER_OPTIONS = {
clientOrigins: ((_a = environment_1.default.IB_ORIGINS_ALLOWLIST) !== null && _a !== void 0 ? _a : environment_1.default.IB_BASE_API_URL),
sessions: redis,
metrics: redis,
models: json,
};
/**
* This server helper intended is intended for use by the Instant Bandit repo.
*
* Implementers who import the Instant Bandit package should create their own with
* their desired configuration.
*
* See the note in `server-helpers` as to why we tack onto `global` here.
* @private
*/
var internalBanditServer;
function getInternalDevServer(options) {
if (options === void 0) { options = DEFAULT_DEV_SERVER_OPTIONS; }
if (environment_1.default.isDev()) {
if (global["internalBanditServer"]) {
internalBanditServer = global["internalBanditServer"];
}
}
if (!internalBanditServer) {
console.debug("[IB] Creating internal InstantBanditServer helper...");
internalBanditServer = (0, server_core_1.buildInstantBanditServer)(options);
}
if (environment_1.default.isDev()) {
global["internalBanditServer"] = internalBanditServer;
}
return internalBanditServer;
}
exports.getInternalDevServer = getInternalDevServer;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getInternalDevServer=void 0;var a,j,b=require("tslib"),c=b.__importDefault(require("./environment")),e=require("./server-core"),f=require("./backends/json-sites"),g=require("./backends/redis"),d=(0,g.getRedisBackend)(),h=(0,f.getJsonSiteBackend)(),i=b.__assign(b.__assign({},e.DEFAULT_SERVER_OPTIONS),{clientOrigins:null!==(a=c.default.IB_ORIGINS_ALLOWLIST)&& void 0!==a?a:c.default.IB_BASE_API_URL,sessions:d,metrics:d,models:h});Object.freeze(i),exports.getInternalDevServer=function(a){return void 0===a&&(a=i),c.default.isDev()&&global.internalBanditServer&&(j=global.internalBanditServer),j||(console.debug("[IB] Creating internal InstantBanditServer helper..."),j=(0,e.buildInstantBanditServer)(a)),c.default.isDev()&&(global.internalBanditServer=j),j}

@@ -10,5 +10,5 @@ /// <reference types="node" />

export declare function serverSideRenderedSite(server: InstantBanditServer, siteName: string, req: IncomingMessage & {
cookies: {
cookies: Partial<{
[key: string]: string;
};
}>;
}): Promise<{

@@ -15,0 +15,0 @@ site: import("../models").Site;

@@ -1,108 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.serverSideRenderedSite = void 0;
var tslib_1 = require("tslib");
var constants_1 = require("../constants");
var contexts_1 = require("../contexts");
var utils_1 = require("../utils");
var server_utils_1 = require("./server-utils");
/**
* Handles details around serving a site in a manner suitable for a full SSR render.
* The selection is persisted in the server's session store, and a session ID is
* returned to the user via cookie.
*/
function serverSideRenderedSite(server, siteName, req) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var validatedRequest, sid, sessions, session, err_1, _a, loader, metrics, ctx, siteConfig, site, experiment, variant, defer;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, server.init()];
case 1:
_b.sent();
return [4 /*yield*/, (0, server_utils_1.validateUserRequest)({
allowNoSession: true,
allowedOrigins: server.origins,
headers: req.headers,
siteName: siteName,
url: req.url,
})];
case 2:
validatedRequest = _b.sent();
if ((0, utils_1.exists)(req.cookies[constants_1.HEADER_SESSION_ID])) {
validatedRequest.sid = req.cookies[constants_1.HEADER_SESSION_ID];
}
sid = validatedRequest.sid;
sessions = server.sessions;
_b.label = 3;
case 3:
_b.trys.push([3, 7, , 8]);
if (!((0, utils_1.exists)(sid) && server.isBackendConnected(server.sessions))) return [3 /*break*/, 5];
return [4 /*yield*/, sessions.getOrCreateSession(validatedRequest)];
case 4:
session = _b.sent();
return [3 /*break*/, 6];
case 5:
session = (0, utils_1.makeNewSession)();
_b.label = 6;
case 6: return [3 /*break*/, 8];
case 7:
err_1 = _b.sent();
console.log("[IB] Error fetching session for '".concat(sid, "': ").concat(err_1));
session = (0, utils_1.makeNewSession)();
return [3 /*break*/, 8];
case 8:
_a = contexts_1.DEFAULT_BANDIT_OPTIONS.providers, loader = _a.loader, metrics = _a.metrics;
if (!server.isBackendConnected(server.sessions)) {
ctx = (0, contexts_1.createBanditContext)();
}
else {
ctx = (0, contexts_1.createBanditContext)({
providers: {
loader: loader,
metrics: metrics,
//
// Here we inject the session from the server's asynchronous session store into
// the InstantBandit component's *synchronous* store. This is done for SSR.
//
// In order to complete the render entirely on the server and avoid client-side
// hydration, the component needs to render synchronously in one pass.
//
session: function () {
return {
id: session.sid,
getOrCreateSession: function () { return session; },
hasSeen: function () {
return false;
},
persistVariant: function () {
// We do this server side below
return;
},
save: function () { return session; },
};
},
},
});
}
return [4 /*yield*/, server.getSite(validatedRequest)];
case 9:
siteConfig = (_b.sent()).site;
return [4 /*yield*/, ctx.init(siteConfig)];
case 10:
site = _b.sent();
experiment = ctx.experiment, variant = ctx.variant;
return [4 /*yield*/, server.sessions.markVariantSeen(session, site.name, experiment.id, variant.name)
.catch(function (err) { return console.warn("[IB]: Error marking variant '".concat(variant.name, "' seen: ").concat(err)); })];
case 11:
_b.sent();
defer = server.isBackendConnected(sessions) === false;
return [2 /*return*/, {
site: site,
select: defer ? null : variant.name,
defer: defer,
}];
}
});
});
}
exports.serverSideRenderedSite = serverSideRenderedSite;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.serverSideRenderedSite=void 0;var a=require("tslib"),b=require("../constants"),c=require("../contexts"),d=require("../utils"),e=require("./server-utils");exports.serverSideRenderedSite=function(f,g,h){return a.__awaiter(this,void 0,void 0,function(){var i,j,k,l,m,n,o,p,q,r,s,t,u,v;return a.__generator(this,function(a){switch(a.label){case 0:return[4,f.init()];case 1:return a.sent(),[4,(0,e.validateUserRequest)({allowNoSession:!0,allowedOrigins:f.origins,headers:h.headers,siteName:g,url:h.url})];case 2:i=a.sent(),(0,d.exists)(h.cookies[b.HEADER_SESSION_ID])&&(i.sid=h.cookies[b.HEADER_SESSION_ID]),j=i.sid,k=f.sessions,a.label=3;case 3:if(a.trys.push([3,7,,8]),!((0,d.exists)(j)&&f.isBackendConnected(f.sessions)))return[3,5];return[4,k.getOrCreateSession(i)];case 4:return l=a.sent(),[3,6];case 5:l=(0,d.makeNewSession)(),a.label=6;case 6:return[3,8];case 7:return m=a.sent(),console.log("[IB] Error fetching session for '".concat(j,"': ").concat(m)),l=(0,d.makeNewSession)(),[3,8];case 8:return o=(n=c.DEFAULT_BANDIT_OPTIONS.providers).loader,p=n.metrics,q=f.isBackendConnected(f.sessions)?(0,c.createBanditContext)({providers:{loader:o,metrics:p,session:function(){return{id:l.sid,getOrCreateSession:function(){return l},hasSeen:function(){return!1},persistVariant:function(){},save:function(){return l}}}}}):(0,c.createBanditContext)(),[4,f.getSite(i)];case 9:return r=a.sent().site,[4,q.init(r)];case 10:return s=a.sent(),t=q.experiment,u=q.variant,[4,f.sessions.markVariantSeen(l,s.name,t.id,u.name).catch(function(a){return console.warn("[IB]: Error marking variant '".concat(u.name,"' seen: ").concat(a))})];case 11:return a.sent(),[2,{site:s,select:(v=!1===f.isBackendConnected(k))?null:u.name,defer:v}]}})})}

@@ -14,2 +14,5 @@ /// <reference types="node" />

clientOrigins: string | string[];
allowMetricsPayloads: boolean;
maxBatchItemLength: number;
maxBatchLength: number;
};

@@ -21,2 +24,3 @@ /**

export declare type InstantBanditServer = {
readonly options: InstantBanditServerOptions;
readonly origins: Origins;

@@ -124,1 +128,9 @@ readonly metrics: MetricsBackend;

};
/**
* Options for decoding metrics batches
*/
export declare type MetricsDecodeOptions = {
maxBatchLength: number;
maxBatchItemLength: number;
allowMetricsPayloads: boolean;
};

@@ -1,3 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var constants_1 = require("../constants");
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),require("../constants")

@@ -1,5 +0,16 @@

import { InstantBanditHeaders, Origins, RequestValidationArgs, ValidatedRequest } from "./server-types";
import { InstantBanditHeaders, MetricsDecodeOptions, Origins, RequestValidationArgs, ValidatedRequest } from "./server-types";
import { MetricsBatch } from "../models";
import { SessionDescriptor } from "../types";
/**
* Decodes an encoded `MetricsBatch` from text/plain ND-JSON
*/
export declare function decodeMetricsBatch(text: string, options: MetricsDecodeOptions): MetricsBatch;
/**
* Ensures that an object contains only the given keys
* @param obj
* @param allowed
* @returns
*/
export declare function allowKeys<T>(obj: T, allowed: (keyof T)[], maxLength?: number): void;
/**
* Emits a cookie for the session including any configured settings for cookies

@@ -6,0 +17,0 @@ * @param session

@@ -1,188 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toNumber = exports.makeKey = exports.randomId = exports.validateMetricsBatch = exports.validateClientReportedOrigin = exports.getSessionIdFromHeaders = exports.createNewClientSession = exports.validateUserRequest = exports.normalizeOrigins = exports.emitCookie = void 0;
var tslib_1 = require("tslib");
var crypto_1 = require("crypto");
var environment_1 = tslib_1.__importDefault(require("./environment"));
var constants = tslib_1.__importStar(require("../constants"));
var utils_1 = require("../utils");
var defaults_1 = require("../defaults");
/**
* Emits a cookie for the session including any configured settings for cookies
* @param session
* @returns
*/
function emitCookie(req, session) {
var settings = environment_1.default.IB_COOKIE_SETTINGS;
return "".concat(constants.HEADER_SESSION_ID, "=").concat(session.sid, "; ").concat(settings);
}
exports.emitCookie = emitCookie;
/**
* Normalizes an array of origins and produces a map for quick lookup.
* Origin lookups are to prevent x-domain POST attacks and to enable proper CORS access.
* @returns
*/
function normalizeOrigins(originsArg, injected) {
if (injected === void 0) { injected = []; }
if (!(0, utils_1.exists)(originsArg)) {
return new Map();
}
var originsListRaw = Array.isArray(originsArg) ? originsArg : [originsArg];
var originsList = originsListRaw
.concat(injected)
.join(",")
.split(",")
.map(function (o) { return o.trim(); })
.map(function (o) { return o.toLowerCase(); })
.map(function (o) { return o; });
var origins = new Map();
originsList
.reduce(function (p, entry) { return origins.set(entry, { name: entry }); }, origins);
return origins;
}
exports.normalizeOrigins = normalizeOrigins;
/**
* Ensures that an incoming user request is well-formed and bears the appropriate information.
* Note: Does not validate the session itself.
*/
function validateUserRequest(args) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var headers, allowedOrigins, allowNoSession, requireOrigin, siteName, origin, allowed, sid;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
headers = args.headers, allowedOrigins = args.allowedOrigins, allowNoSession = args.allowNoSession, requireOrigin = args.requireOrigin, siteName = args.siteName;
origin = (0, utils_1.exists)(headers["origin"]) ? headers["origin"] : null;
// Null origins allowed by default
if (origin !== null || requireOrigin === true) {
allowed = validateClientReportedOrigin(allowedOrigins, origin);
if (!allowed) {
console.warn("[IB] Invalid request for '".concat(args.url, "' from origin '").concat(origin, "'"));
// Intentionally vague on error response
throw new Error("Invalid origin");
}
}
return [4 /*yield*/, getSessionIdFromHeaders(headers)];
case 1:
sid = _a.sent();
if (!allowNoSession && !(0, utils_1.exists)(sid)) {
throw new Error("Missing session");
}
return [2 /*return*/, {
sid: sid || "",
origin: origin !== null && origin !== void 0 ? origin : "null",
headers: headers,
siteName: siteName || defaults_1.DEFAULT_SITE.name,
session: null,
}];
}
});
});
}
exports.validateUserRequest = validateUserRequest;
function createNewClientSession() {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var sid, session;
return tslib_1.__generator(this, function (_a) {
sid = (0, crypto_1.randomUUID)();
session = (0, utils_1.makeNewSession)(sid);
return [2 /*return*/, session];
});
});
}
exports.createNewClientSession = createNewClientSession;
function getSessionIdFromHeaders(headers) {
var _a;
return tslib_1.__awaiter(this, void 0, void 0, function () {
var id;
return tslib_1.__generator(this, function (_b) {
id = (0, utils_1.getCookie)(constants.HEADER_SESSION_ID, headers["cookie"]);
if (!(0, utils_1.exists)(id)) {
id = (_a = headers[constants.HEADER_SESSION_ID]) !== null && _a !== void 0 ? _a : null;
}
return [2 /*return*/, id];
});
});
}
exports.getSessionIdFromHeaders = getSessionIdFromHeaders;
/**
* Checks a client's reported "origin" header against an allowlist
* @param allowedOrigins
* @param origin
* @returns
*/
function validateClientReportedOrigin(allowedOrigins, origin) {
if (!(0, utils_1.exists)(origin)) {
origin = "null";
}
return allowedOrigins.has(origin);
}
exports.validateClientReportedOrigin = validateClientReportedOrigin;
/**
* Validates a batch of metrics by ensuring they match their request
* @param req
* @param batch
* @returns
*/
function validateMetricsBatch(req, batch) {
var sid = req.sid;
if (!(0, utils_1.exists)(sid)) {
throw new Error("Missing or invalid session for metrics");
}
return batch;
}
exports.validateMetricsBatch = validateMetricsBatch;
/**
* Generates a cryptographic quality random identifier
* @param len
* @returns
*/
function randomId(len) {
if (len === void 0) { len = 16; }
return (0, crypto_1.randomBytes)(len).toString("base64url");
}
exports.randomId = randomId;
/**
* Prefixes a storage key with top-level key space identifiers, e.g. the site and experiment name
* @param pieces
* @returns
*/
function makeKey(pieces) {
if (pieces.length < 1) {
throw new Error("Expected key fragments");
}
// Enforce a max key length
pieces.reduce(function (length, piece, ix) {
if (!(0, utils_1.exists)(piece)) {
return length;
}
length += piece.length + ix;
if (length > constants.MAX_STORAGE_KEY_LENGTH) {
throw new Error("Maximum storage key size exceeded at length ".concat(length));
}
return length;
}, 0);
return pieces.map(function (p) { return p.replaceAll(":", "_"); }).join(":");
}
exports.makeKey = makeKey;
/**
* Converts a string to a number.
* If the string has a decimal point, `parseFloat` is used.
* If a number is supplied, returns the number.
* If `null` or `undefined` is passed, returns 0
*/
function toNumber(val) {
if (typeof val === "string") {
return (val.indexOf(".") > -1) ? parseFloat(val) : parseInt(val);
}
else if (typeof val === "number") {
return val;
}
else if (val === null || val === undefined) {
return 0;
}
else {
throw new Error("Invalid value '".concat(val, "' interpreted as number"));
}
}
exports.toNumber = toNumber;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.toNumber=exports.makeKey=exports.randomId=exports.validateMetricsBatch=exports.validateClientReportedOrigin=exports.getSessionIdFromHeaders=exports.createNewClientSession=exports.validateUserRequest=exports.normalizeOrigins=exports.emitCookie=exports.allowKeys=exports.decodeMetricsBatch=void 0;var a=require("tslib"),e=require("crypto"),f=a.__importDefault(require("./environment")),g=a.__importStar(require("../constants")),h=require("../utils"),i=require("../defaults");function b(b,c,a){void 0===a&&(a=g.MAX_STORAGE_VALUE_LENGTH),Object.keys(b).forEach(function(d){if(c.includes(d)&&b[d].toString().length<=a)return!0;throw Error("Invalid key '".concat(d.toString(),"'"))})}function c(b){var c;return a.__awaiter(this,void 0,void 0,function(){var d;return a.__generator(this,function(a){return d=(0,h.getCookie)(g.HEADER_SESSION_ID,b.cookie),(0,h.exists)(d)||(d=null!==(c=b[g.HEADER_SESSION_ID])&& void 0!==c?c:null),[2,d]})})}function d(b,a){return"true"!==f.default.IB_ENFORCE_ORIGIN_CHECK||((0,h.exists)(a)||(a="null"),b.has(a))}exports.decodeMetricsBatch=function(j,f){var o=f.allowMetricsPayloads,h=f.maxBatchItemLength,p=f.maxBatchLength;if(j.length>p)throw Error("Invalid metrics batch length");var k=j.split("\n"),l=k[0],i=k.slice(1),m=[];if(l.length>h)throw Error("Invalid metrics batch header");var c=JSON.parse(l);b(c,["site","experiment","variant"]);for(var d=0;d<i.length;++d){var e=i[d];if(e.length>h)throw Error("Batch item exceeds max length");var a=JSON.parse(e);if(b(a,["ts","name","payload"]),1===a.payload){var n=i[++d];if(n.length>h)console.warn("Batch item payload too large. Item: '".concat(e,"'")),a.payload=g.METRICS_PAYLOAD_SIZE_ERR;else if(o){var q=JSON.parse(n);a.payload=q}else console.warn("[IB] Received metrics payload for item '".concat(e,"'...ignoring")),a.payload=g.METRICS_PAYLOAD_IGNORED}m.push(a)}var r=c.site,s=c.experiment,t=c.variant;return{site:r,experiment:s,variant:t,entries:m}},exports.allowKeys=b,exports.emitCookie=function(c,a){var b=f.default.IB_COOKIE_SETTINGS;return"".concat(g.HEADER_SESSION_ID,"=").concat(a.sid,"; ").concat(b)},exports.normalizeOrigins=function(a,b){if(void 0===b&&(b=[]),!(0,h.exists)(a))return new Map;var d=(Array.isArray(a)?a:[a]).concat(b).join(",").split(",").map(function(a){return a.trim()}).map(function(a){return a.toLowerCase()}).map(function(a){return a}),c=new Map;return d.reduce(function(b,a){return c.set(a,{name:a})},c),c},exports.validateUserRequest=function(b){return a.__awaiter(this,void 0,void 0,function(){var e,f,g,j,k,l,m,n;return a.__generator(this,function(a){switch(a.label){case 0:if(e=b.headers,f=b.allowedOrigins,g=b.allowNoSession,j=b.requireOrigin,k=b.siteName,(null!==(l=(0,h.exists)(e.origin)?e.origin:null)|| !0===j)&&!(m=d(f,l)))throw console.warn("[IB] Invalid request for '".concat(b.url,"' from origin '").concat(l,"'")),Error("Invalid origin");return[4,c(e)];case 1:if(n=a.sent(),!g&&!(0,h.exists)(n))throw Error("Missing session");return[2,{sid:n||"",origin:null!=l?l:"null",headers:e,siteName:k||i.DEFAULT_SITE.name,session:null}]}})})},exports.createNewClientSession=function(){return a.__awaiter(this,void 0,void 0,function(){var b,c;return a.__generator(this,function(a){return b=(0,e.randomUUID)(),[2,c=(0,h.makeNewSession)(b)]})})},exports.getSessionIdFromHeaders=c,exports.validateClientReportedOrigin=d,exports.validateMetricsBatch=function(a,b){var c=a.sid;if(!(0,h.exists)(c))throw Error("Missing or invalid session for metrics");return b},exports.randomId=function(a){return void 0===a&&(a=16),(0,e.randomBytes)(a).toString("base64url")},exports.makeKey=function(a){if(a.length<1)throw Error("Expected key fragments");return a.reduce(function(a,b,c){if(!(0,h.exists)(b))return a;if((a+=b.length+c)>g.MAX_STORAGE_KEY_LENGTH)throw Error("Maximum storage key size exceeded at length ".concat(a));return a},0),a.map(function(a){return a.replaceAll(":","_")}).join(":")},exports.toNumber=function(a){if("string"==typeof a)return a.indexOf(".")> -1?parseFloat(a):parseInt(a);if("number"==typeof a)return a;if(null==a)return 0;throw Error("Invalid value '".concat(a,"' interpreted as number"))}

@@ -7,4 +7,2 @@ /// <reference types="node" />

export declare type InstantBanditProps = {
preserveSession?: boolean;
probabilities?: ProbabilityDistribution | null;
siteName?: string;

@@ -14,3 +12,2 @@ select?: string;

defer?: boolean;
debug?: boolean;
options?: InstantBanditOptions;

@@ -17,0 +14,0 @@ timeout?: number;

@@ -1,19 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Algorithm = exports.LoadState = void 0;
var LoadState;
(function (LoadState) {
LoadState["PRELOAD"] = "pre";
LoadState["WAIT"] = "wait-for-data";
LoadState["SELECTING"] = "selecting";
LoadState["READY"] = "ready";
})(LoadState = exports.LoadState || (exports.LoadState = {}));
/**
* Algorithms to use when selecting a variant.
*/
var Algorithm;
(function (Algorithm) {
Algorithm["DEFAULT"] = "default";
Algorithm["RANDOM"] = "random";
Algorithm["MAB_EPSILON_GREEDY"] = "mab-epsilon-greedy";
})(Algorithm = exports.Algorithm || (exports.Algorithm = {}));
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Algorithm=exports.LoadState=void 0,function(a){a.PRELOAD="pre",a.WAIT="wait-for-data",a.SELECTING="selecting",a.READY="ready"}(exports.LoadState||(exports.LoadState={})),function(a){a.DEFAULT="default",a.RANDOM="random",a.MAB_EPSILON_GREEDY="mab-epsilon-greedy"}(exports.Algorithm||(exports.Algorithm={}))
import { useLayoutEffect } from "react";
import { MetricsBatch } from "./models";
import { SessionDescriptor } from "./types";
/**
* Encodes a batch of metrics entries as ND-JSON lines.
* The first line is the "header" and represents the batch metadata.
* Subsequent lines represents the batch entries.
* The field `payload` is set to 1` based on the existence of a payload object.
* If a payload is present, it is represented by the next line.
* @param batch
*/
export declare function encodeMetricsBatch(batch: MetricsBatch): string;
export declare function makeNewSession(sid?: string): SessionDescriptor;

@@ -4,0 +14,0 @@ export declare function markVariantInSession(session: SessionDescriptor, site: string, experiment: string, variant: string): void;

@@ -1,127 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.flushPromises = exports.useIsomorphicLayoutEffect = exports.isBrowserEnvironment = exports.getBaseUrl = exports.getCookie = exports.env = exports.exists = exports.deepFreeze = exports.markVariantInSession = exports.makeNewSession = void 0;
var tslib_1 = require("tslib");
var react_1 = require("react");
var constants = tslib_1.__importStar(require("./constants"));
function makeNewSession(sid) {
if (sid === void 0) { sid = ""; }
var session = {
sid: sid,
selections: {},
};
return session;
}
exports.makeNewSession = makeNewSession;
function markVariantInSession(session, site, experiment, variant) {
var _a;
var sites = session.selections;
if (!sites) {
sites = session.selections = {};
}
var experiments = sites[site];
if (!experiments) {
experiments = sites[site] = (_a = {},
_a[experiment] = [],
_a);
}
var variants = experiments[experiment];
if (!exists(variants)) {
variants = experiments[experiment] = [];
}
// Put the most recently presented variant at the end
var ix = variants.indexOf(variant);
if (ix > -1) {
variants.splice(ix, 1);
}
variants.push(variant);
}
exports.markVariantInSession = markVariantInSession;
/**
* Freezes an entire object tree
* @param obj
* @returns
*/
function deepFreeze(obj, seen) {
if (seen === void 0) { seen = new WeakMap(); }
if (!exists(obj)) {
return obj;
}
seen.set(obj, true);
Object.getOwnPropertyNames(obj)
.filter(function (prop) { return !seen.has(obj[prop]); })
.filter(function (prop) { return obj[prop] && typeof obj[prop] === "object"; })
.forEach(function (prop) { return deepFreeze(obj[prop], seen); });
return Object.freeze(obj);
}
exports.deepFreeze = deepFreeze;
/**
* Returns `true` if something is non-null and non-undefined
* @param thing
* @returns
*/
function exists(thing) {
return (thing !== null && thing !== undefined);
}
exports.exists = exists;
/**
* Pulls an environment variable either from a server environment
* or from Next.js' public env vars exposed to the client.
* @param name
* @returns
*/
function env(name) {
if (typeof process === "undefined") {
return undefined;
}
else if (exports.isBrowserEnvironment) {
return process.env[constants.NEXTJS_PUBLIC_PREFIX + name];
}
else {
return process.env[name];
}
}
exports.env = env;
/**
* Extracts a cookie by name from a request header value
* @param cookieName
* @param str
* @returns
*/
function getCookie(cookieName, str) {
if (str === void 0) { str = ""; }
var fullStr = "";
if (typeof document === "undefined" && str === "") {
return null;
}
else if (str !== "") {
fullStr = str;
}
else {
fullStr = document.cookie;
}
var cookie = fullStr
.split(";")
.map(function (cookie) { return cookie.trim(); })
.filter(function (cookie) { return cookie.indexOf(cookieName) === 0; })
.map(function (cookie) { return cookie.substring(cookieName.length + 1, cookie.length); })
.pop();
return cookie !== null && cookie !== void 0 ? cookie : null;
}
exports.getCookie = getCookie;
/**
* Gets the base URL, observing environment variables if in a Node environment
* @returns
*/
function getBaseUrl() {
var _a;
return (_a = env(constants.VARNAME_BASE_URL)) !== null && _a !== void 0 ? _a : constants.DEFAULT_BASE_URL;
}
exports.getBaseUrl = getBaseUrl;
exports.isBrowserEnvironment = typeof window !== "undefined";
exports.useIsomorphicLayoutEffect = exports.isBrowserEnvironment ? react_1.useLayoutEffect : react_1.useEffect;
var flushPromises = function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
return [2 /*return*/, new Promise(function (resolve) { scheduler(resolve); })];
}); }); };
exports.flushPromises = flushPromises;
var scheduler = typeof setImmediate === "function" ? setImmediate : setTimeout;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.flushPromises=exports.useIsomorphicLayoutEffect=exports.isBrowserEnvironment=exports.getBaseUrl=exports.getCookie=exports.env=exports.exists=exports.deepFreeze=exports.markVariantInSession=exports.makeNewSession=exports.encodeMetricsBatch=void 0;var b=require("tslib"),a=require("react"),f=b.__importStar(require("./constants"));function c(a,b){return(void 0===b&&(b=new WeakMap),function(a){return null!=a}(a))?(b.set(a,!0),Object.getOwnPropertyNames(a).filter(function(c){return!b.has(a[c])}).filter(function(b){return a[b]&&"object"==typeof a[b]}).forEach(function(d){return c(a[d],b)}),Object.freeze(a)):a}function d(a){return null!=a}function e(a){return"undefined"==typeof process?void 0:exports.isBrowserEnvironment?process.env[f.NEXTJS_PUBLIC_PREFIX+a]:process.env[a]}exports.encodeMetricsBatch=function(a){var c=a.site,d=a.experiment,e=a.variant,f=a.entries,g=JSON.stringify({site:c,experiment:d,variant:e}),h=f.map(function(a){var f=a.name,g=a.ts,b=a.payload,c=function(a){return null!=a}(b),d={ts:g,name:f,payload:void 0};c&&(d.payload=1);var e=JSON.stringify(d);return c?e+"\n"+JSON.stringify(b):e});return b.__spreadArray([g],h,!0).join("\n")},exports.makeNewSession=function(a){return void 0===a&&(a=""),{sid:a,selections:{}}},exports.markVariantInSession=function(e,f,d,g){var h,b=e.selections;b||(b=e.selections={});var c=b[f];c||(c=b[f]=((h={})[d]=[],h));var i,a=c[d];i=a,null!=i||(a=c[d]=[]);var j=a.indexOf(g);j> -1&&a.splice(j,1),a.push(g)},exports.deepFreeze=c,exports.exists=d,exports.env=e,exports.getCookie=function(d,a){void 0===a&&(a="");var c="";if("undefined"==typeof document&&""===a)return null;var b=(c=""!==a?a:document.cookie).split(";").map(function(a){return a.trim()}).filter(function(a){return 0===a.indexOf(d)}).map(function(a){return a.substring(d.length+1,a.length)}).pop();return null!=b?b:null},exports.getBaseUrl=function(){var a;return null!==(a=e(f.VARNAME_BASE_URL))&& void 0!==a?a:f.DEFAULT_BASE_URL},exports.isBrowserEnvironment="undefined"!=typeof window,exports.useIsomorphicLayoutEffect=exports.isBrowserEnvironment?a.useLayoutEffect:a.useEffect,exports.flushPromises=function(){return b.__awaiter(void 0,void 0,void 0,function(){return b.__generator(this,function(a){return[2,new Promise(function(a){g(a)})]})})};var g="function"==typeof setImmediate?setImmediate:setTimeout

@@ -1,72 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMetricsEndpoint = void 0;
var tslib_1 = require("tslib");
var server_internal_1 = require("../../lib/server/server-internal");
var server_utils_1 = require("../../lib/server/server-utils");
var MetricsEndpoint = createMetricsEndpoint();
exports.default = MetricsEndpoint;
/**
* Creates a Next.js endpoint for ingesting metrics.
* Remember to return the resulting endpoint function as your module's default export.
* See [metrics].ts.
*/
function createMetricsEndpoint(server) {
// This endpoint accepts POST requests bearing batches of metrics to ingest.
// In development environments, shows site metrics on GET
function handleMetricsRequest(req, res) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var metrics, origins, sessions, method, headers, sid, needsSession, batch, validatedReq, session;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!server) {
server = (0, server_internal_1.getInternalDevServer)();
}
return [4 /*yield*/, server.init()];
case 1:
_a.sent();
metrics = server.metrics, origins = server.origins, sessions = server.sessions;
method = req.method, headers = req.headers;
if (server.isBackendConnected(metrics) === false) {
res.status(503).end();
return [2 /*return*/];
}
return [4 /*yield*/, (0, server_utils_1.getSessionIdFromHeaders)(headers)];
case 2:
sid = _a.sent();
needsSession = (!sid || sid === "");
if (!(method === "POST")) return [3 /*break*/, 6];
batch = req.body;
return [4 /*yield*/, (0, server_utils_1.validateUserRequest)({
allowedOrigins: origins,
headers: headers,
url: req.url,
allowNoSession: true,
siteName: batch.site,
})];
case 3:
validatedReq = _a.sent();
return [4 /*yield*/, sessions.getOrCreateSession(validatedReq)];
case 4:
session = _a.sent();
validatedReq.session = session;
return [4 /*yield*/, metrics.ingestBatch(validatedReq, req.body)];
case 5:
_a.sent();
// Grant a session if the request needs one (is new), or if we created a new one
if (needsSession || session.sid !== sid) {
res.setHeader("Set-Cookie", (0, server_utils_1.emitCookie)(validatedReq, session));
}
res.status(200).json(session);
return [2 /*return*/];
case 6:
res.status(400).end();
return [2 /*return*/];
}
});
});
}
return handleMetricsRequest;
}
exports.createMetricsEndpoint = createMetricsEndpoint;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createMetricsEndpoint=void 0;var c=require("tslib"),d=require("../../lib/server/server-internal"),e=require("../../lib/server/server-utils"),f=require("../../lib/utils"),g=require("../../lib/constants"),a=b();function b(a){return function(b,h){return c.__awaiter(this,void 0,void 0,function(){var i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;return c.__generator(this,function(c){switch(c.label){case 0:return a||(a=(0,d.getInternalDevServer)()),[4,a.init()];case 1:if(c.sent(),i=a.metrics,j=a.options,k=a.origins,l=a.sessions,m=b.method,n=b.headers,o=j.allowMetricsPayloads,p=j.maxBatchLength,q=j.maxBatchItemLength,!1===a.isBackendConnected(i))return h.status(503).end(),[2];return[4,(0,e.getSessionIdFromHeaders)(n)];case 2:if(s=!(r=c.sent())||""===r,"POST"!==m)return[3,9];if(t=b.headers["content-length"],(0,f.exists)(t)&&parseInt(t)>p)return console.warn("[IB] Recived metrics payload larger than ".concat(p," bytes")),h.status(400).end(),[2];u=b.body,v={allowMetricsPayloads:!!o,maxBatchLength:null!=p?p:g.METRICS_MAX_LENGTH,maxBatchItemLength:null!=q?q:g.METRICS_MAX_ITEM_LENGTH},w=void 0,x=void 0,c.label=3;case 3:return c.trys.push([3,5,,6]),w=(0,e.decodeMetricsBatch)(u,v),[4,(0,e.validateUserRequest)({allowedOrigins:k,headers:n,url:b.url,allowNoSession:!0,siteName:w.site})];case 4:return x=c.sent(),[3,6];case 5:return y=c.sent(),console.warn("[IB] Error handling incoming metrics: ".concat(y)),h.status(400).end(),[2];case 6:return[4,l.getOrCreateSession(x)];case 7:return z=c.sent(),x.session=z,[4,i.ingestBatch(x,w)];case 8:return c.sent(),(s||z.sid!==r)&&h.setHeader("Set-Cookie",(0,e.emitCookie)(x,z)),h.status(200).json(z),[2];case 9:return h.status(400).end(),[2]}})})}}exports.default=a,exports.createMetricsEndpoint=b

@@ -1,76 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSiteEndpoint = void 0;
var tslib_1 = require("tslib");
var environment_1 = tslib_1.__importDefault(require("../../../lib/server/environment"));
var defaults_1 = require("../../../lib/defaults");
var utils_1 = require("../../../lib/utils");
var server_utils_1 = require("../../../lib/server/server-utils");
var server_internal_1 = require("../../../lib/server/server-internal");
var SiteEndpoint = createSiteEndpoint();
exports.default = SiteEndpoint;
/**
* Creates a Next.js endpoint for serving site configurations.
* Remember to return the resulting endpoint function as your module's default export.
* See [siteName].ts.
*/
function createSiteEndpoint(server) {
// This endpoint serves site configurations by name and creates user sessions.
// Site are hydrated with variant probabilities inlined.
// If a site is not found, the default site is returned.
// If the user does not have a session, one is created.
// Session IDs are transmitted via a 1st-party cookie.
function handleSiteRequest(req, res) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var siteNameParam, origins, url, headers, siteName, validatedRequest, _a, site, responseHeaders;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!server) {
server = (0, server_internal_1.getInternalDevServer)();
}
return [4 /*yield*/, server.init()];
case 1:
_b.sent();
siteNameParam = req.query.siteName;
origins = server.origins;
url = req.url, headers = req.headers;
siteName = defaults_1.DEFAULT_SITE.name;
if ((0, utils_1.exists)(siteNameParam)) {
if (Array.isArray(siteNameParam)) {
siteName = siteNameParam.join("");
}
else {
siteName = siteNameParam;
}
}
return [4 /*yield*/, (0, server_utils_1.validateUserRequest)({
url: url,
headers: headers,
allowedOrigins: origins,
siteName: siteName,
// No session required for a site request. One will be created if need be.
allowNoSession: true,
})];
case 2:
validatedRequest = _b.sent();
return [4 /*yield*/, server.getSite(validatedRequest)];
case 3:
_a = _b.sent(), site = _a.site, responseHeaders = _a.responseHeaders;
// Relay headers
Object.keys(responseHeaders)
.forEach(function (header) { var _a; return res.setHeader(header, (_a = responseHeaders[header]) !== null && _a !== void 0 ? _a : ""); });
if (environment_1.default.isDev()) {
res.status(200).send(JSON.stringify(site, null, 2));
}
else {
res.status(200).send(site);
}
return [2 /*return*/];
}
});
});
}
return handleSiteRequest;
}
exports.createSiteEndpoint = createSiteEndpoint;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createSiteEndpoint=void 0;var a=require("tslib"),d=a.__importDefault(require("../../../lib/server/environment")),e=require("../../../lib/defaults"),f=require("../../../lib/utils"),g=require("../../../lib/server/server-utils"),h=require("../../../lib/server/server-internal"),b=c();function c(b){return function(c,i){return a.__awaiter(this,void 0,void 0,function(){var j,k,l,m,n,o,p,q,r;return a.__generator(this,function(a){switch(a.label){case 0:return b||(b=(0,h.getInternalDevServer)()),[4,b.init()];case 1:return a.sent(),j=c.query.siteName,k=b.origins,l=c.url,m=c.headers,n=e.DEFAULT_SITE.name,(0,f.exists)(j)&&(n=Array.isArray(j)?j.join(""):j),[4,(0,g.validateUserRequest)({url:l,headers:m,allowedOrigins:k,siteName:n,allowNoSession:!0})];case 2:return o=a.sent(),[4,b.getSite(o)];case 3:return q=(p=a.sent()).site,Object.keys(r=p.responseHeaders).forEach(function(b){var a;return i.setHeader(b,null!==(a=r[b])&& void 0!==a?a:"")}),d.default.isDev()?i.status(200).send(JSON.stringify(q,null,2)):i.status(200).send(q),[2]}})})}}exports.default=b,exports.createSiteEndpoint=c

@@ -1,17 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMetricsEndpoint = exports.createSiteEndpoint = exports.env = void 0;
var tslib_1 = require("tslib");
var environment_1 = tslib_1.__importDefault(require("./lib/server/environment"));
exports.env = environment_1.default;
tslib_1.__exportStar(require("./lib/server/environment"), exports);
tslib_1.__exportStar(require("./lib/server/server-core"), exports);
tslib_1.__exportStar(require("./lib/server/server-rendering"), exports);
tslib_1.__exportStar(require("./lib/server/server-types"), exports);
tslib_1.__exportStar(require("./lib/server/server-utils"), exports);
tslib_1.__exportStar(require("./lib/server/backends/json-sites"), exports);
tslib_1.__exportStar(require("./lib/server/backends/redis"), exports);
var _siteName_1 = require("./pages/api/sites/[siteName]");
Object.defineProperty(exports, "createSiteEndpoint", { enumerable: true, get: function () { return _siteName_1.createSiteEndpoint; } });
var metrics_1 = require("./pages/api/metrics");
Object.defineProperty(exports, "createMetricsEndpoint", { enumerable: true, get: function () { return metrics_1.createMetricsEndpoint; } });
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createMetricsEndpoint=exports.createSiteEndpoint=exports.env=void 0;var a=require("tslib"),b=a.__importDefault(require("./lib/server/environment"));exports.env=b.default,a.__exportStar(require("./lib/server/environment"),exports),a.__exportStar(require("./lib/server/server-core"),exports),a.__exportStar(require("./lib/server/server-rendering"),exports),a.__exportStar(require("./lib/server/server-types"),exports),a.__exportStar(require("./lib/server/server-utils"),exports),a.__exportStar(require("./lib/server/backends/json-sites"),exports),a.__exportStar(require("./lib/server/backends/redis"),exports);var c=require("./pages/api/sites/[siteName]");Object.defineProperty(exports,"createSiteEndpoint",{enumerable:!0,get:function(){return c.createSiteEndpoint}});var d=require("./pages/api/metrics");Object.defineProperty(exports,"createMetricsEndpoint",{enumerable:!0,get:function(){return d.createMetricsEndpoint}})
{
"name": "@instantdomain/bandit",
"version": "1.0.9",
"version": "1.0.10",
"license": "MIT",

@@ -5,0 +5,0 @@ "repository": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc