@obelism/improve-sdk 3.1.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/{base-m0tq6j54.js → base-gi7y83ve.js} +2 -2
- package/dist/base-gi7y83ve.js.map +1 -0
- package/dist/base-i6scrpy2.mjs +2 -0
- package/dist/base-i6scrpy2.mjs.map +1 -0
- package/dist/client.cjs +1 -1
- package/dist/client.d.ts +5 -5
- package/dist/client.mjs +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.ts +7 -7
- package/dist/server.mjs +1 -1
- package/dist/server.mjs.map +1 -1
- package/dist/types.d.ts +10 -10
- package/package.json +1 -1
- package/dist/base-f9lw4y65.mjs +0 -2
- package/dist/base-f9lw4y65.mjs.map +0 -1
- package/dist/base-m0tq6j54.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2026 Obelism
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=require("ua-parser-js");let t=["wearable","mobile","tablet","console","smarttv","embedded","desktop"],r=["chrome","safari","firefox","edge","ie","samsung internet","social","other"],i=["mac os","ios","android","windows","unix"],n=["tiktok","wechat","weibo","snapchat","klarna","Line","instagram","facebook","alipay","Baidu"],s=["wearable","mobile","tablet"],o="visi",a=e=>new Promise(t=>setTimeout(t,e)),u={timeout:"Configuration request timed out",network:"Configuration request could not reach the server",unauthorized:"Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment","rate-limited":"Configuration request was rate limited (429): too many requests, or the organization usage limit was reached",server:"Configuration request failed with a server error",client:"Configuration request was rejected by the server","invalid-response":"Configuration response could not be parsed as JSON"};class l extends Error{constructor(e,t){super(u[e]),this.name="ImproveFetchError",this.reason=e,this.status=t?.status,this.retryAfterMs=t?.retryAfterMs,t&&"cause"in t&&(this.cause=t.cause)}get isRetryable(){return"rate-limited"===this.reason||"server"===this.reason||"timeout"===this.reason||"network"===this.reason}}let h=e=>429===e?"rate-limited":401===e||403===e?"unauthorized":e>=500?"server":"client",c="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",f=e=>{if("object"!=typeof e||null===e||Array.isArray(e))throw new l("invalid-response");return{name:"string"==typeof e.name?e.name:"",version:"number"==typeof e.version?e.version:0,tests:e.tests??{},flags:e.flags??{},audience:e.audience??{}}},d=(e,t=Date.now())=>{if(!e)return;let r=Number(e);if(Number.isFinite(r))return Math.max(0,Math.round(1e3*r));let i=Date.parse(e);if(!Number.isNaN(i))return Math.max(0,i-t)},g=(e,t,r)=>Math.round(Math.random()*Math.min(r,t*2**e)),m=async(e=1e3,t)=>(await a(e),t?.abort(),null),w=(e=3e3,t,r)=>{let i=new AbortController;return Promise.race([fetch(t,{...r,signal:i.signal}),m(e,i)])};exports.ANALYTICS_PATH="/api/log",exports.BaseImproveSDK=class{#e;#t;constructor({organizationId:e,environment:t,state:r,config:i,fetchTimeout:n,baseUrl:s,configRetries:u,configRevalidate:m,disableWarnings:p}){this.environment="develop",this.#e=null,this.#t=!1,this.config=null,this._fetchConfig=async e=>{if(this.config)return;if(!this.#e)throw Error("No config fetch setup provided");let{url:t,timeout:r,retries:i,revalidate:n}=this.#e,s={...e};"number"==typeof n&&(s.next={...e?.next,revalidate:n});let o=new l("network");for(let e=0;e<=i;e++){let i;e>0&&await a(o.retryAfterMs??g(e-1,300,3e3));try{i=await w(r,t,s)}catch(e){o=new l(e instanceof Error&&"AbortError"===e.name?"timeout":"network",{cause:e});continue}if(!i){o=new l("timeout");continue}if(i.ok){try{this.config=f(await i.json())}catch(e){o=e instanceof l?e:new l("invalid-response",{cause:e});continue}return this.config}if(!(o=new l(h(i.status),{status:i.status,retryAfterMs:d(i.headers.get("Retry-After"))})).isRetryable)break}throw o},this.loadConfig=e=>{this.config=e},this.generateVisitorId=()=>[o,(function(e=5){return e&&"number"==typeof e?Array(e).fill("").reduce(e=>e+=c.charAt(Math.floor(Math.random()*c.length)),""):""})(26).toUpperCase()].join("_"),this.getVisitorCookieName=()=>"visitorId",this._warn=e=>{this.#t||"u">typeof console&&console.warn(`[Improve] ${e}`)},this.validateTestValue=(e,t)=>{if(!this.config)throw Error("Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup");let r=this.config.tests[e];if(!r)throw Error(`No config found for ${e}`);return!!r.options.find(e=>e.slug===t)},this.validateVisitorId=e=>{let t=e.split("_");if(2!==t.length)return!1;let[r,i]=t;return r===o&&26===i.length},this.organizationId=e,this.environment=t,this.state=r,this._baseUrl=s||"https://improve.obelism.studio",this.#t=p??!1,i?this.config=i:this.#e={url:[`${this._baseUrl}/config`,this.organizationId,this.environment,this.state||"active"].join("/"),timeout:n||3e3,retries:u??2,revalidate:m}}},exports.getRandomTestValue=e=>{if(
|
|
2
|
-
//# sourceMappingURL=base-
|
|
1
|
+
var e=require("ua-parser-js");let t=["wearable","mobile","tablet","console","smarttv","embedded","desktop"],r=["chrome","safari","firefox","edge","ie","samsung internet","social","other"],i=["mac os","ios","android","windows","unix"],n=["tiktok","wechat","weibo","snapchat","klarna","Line","instagram","facebook","alipay","Baidu"],s=["wearable","mobile","tablet"],o="visi",a=e=>new Promise(t=>setTimeout(t,e)),u={timeout:"Configuration request timed out",network:"Configuration request could not reach the server",unauthorized:"Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment","rate-limited":"Configuration request was rate limited (429): too many requests, or the organization usage limit was reached",server:"Configuration request failed with a server error",client:"Configuration request was rejected by the server","invalid-response":"Configuration response could not be parsed as JSON"};class l extends Error{constructor(e,t){super(u[e]),this.name="ImproveFetchError",this.reason=e,this.status=t?.status,this.retryAfterMs=t?.retryAfterMs,t&&"cause"in t&&(this.cause=t.cause)}get isRetryable(){return"rate-limited"===this.reason||"server"===this.reason||"timeout"===this.reason||"network"===this.reason}}let h=e=>429===e?"rate-limited":401===e||403===e?"unauthorized":e>=500?"server":"client",c="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",f=e=>{if("object"!=typeof e||null===e||Array.isArray(e))throw new l("invalid-response");return{name:"string"==typeof e.name?e.name:"",version:"number"==typeof e.version?e.version:0,tests:e.tests??{},flags:e.flags??{},audience:e.audience??{}}},d=(e,t=Date.now())=>{if(!e)return;let r=Number(e);if(Number.isFinite(r))return Math.max(0,Math.round(1e3*r));let i=Date.parse(e);if(!Number.isNaN(i))return Math.max(0,i-t)},g=(e,t,r)=>Math.round(Math.random()*Math.min(r,t*2**e)),m=async(e=1e3,t)=>(await a(e),t?.abort(),null),w=(e=3e3,t,r)=>{let i=new AbortController;return Promise.race([fetch(t,{...r,signal:i.signal}),m(e,i)])};exports.ANALYTICS_PATH="/api/log",exports.BaseImproveSDK=class{#e;#t;constructor({organizationId:e,environment:t,state:r,config:i,fetchTimeout:n,baseUrl:s,configRetries:u,configRevalidate:m,disableWarnings:p}){this.environment="develop",this.#e=null,this.#t=!1,this.config=null,this._fetchConfig=async e=>{if(this.config)return;if(!this.#e)throw Error("No config fetch setup provided");let{url:t,timeout:r,retries:i,revalidate:n}=this.#e,s={...e};"number"==typeof n&&(s.next={...e?.next,revalidate:n});let o=new l("network");for(let e=0;e<=i;e++){let i;e>0&&await a(o.retryAfterMs??g(e-1,300,3e3));try{i=await w(r,t,s)}catch(e){o=new l(e instanceof Error&&"AbortError"===e.name?"timeout":"network",{cause:e});continue}if(!i){o=new l("timeout");continue}if(i.ok){try{this.config=f(await i.json())}catch(e){o=e instanceof l?e:new l("invalid-response",{cause:e});continue}return this.config}if(!(o=new l(h(i.status),{status:i.status,retryAfterMs:d(i.headers.get("Retry-After"))})).isRetryable)break}throw o},this.loadConfig=e=>{this.config=e},this.generateVisitorId=()=>[o,(function(e=5){return e&&"number"==typeof e?Array(e).fill("").reduce(e=>e+=c.charAt(Math.floor(Math.random()*c.length)),""):""})(26).toUpperCase()].join("_"),this.getVisitorCookieName=()=>"visitorId",this._warn=e=>{this.#t||"u">typeof console&&console.warn(`[Improve] ${e}`)},this.validateTestValue=(e,t)=>{if(!this.config)throw Error("Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup");let r=this.config.tests[e];if(!r)throw Error(`No config found for ${e}`);return!!r.options.find(e=>e.slug===t)},this.validateVisitorId=e=>{let t=e.split("_");if(2!==t.length)return!1;let[r,i]=t;return r===o&&26===i.length},this.organizationId=e,this.environment=t,this.state=r,this._baseUrl=s||"https://improve.obelism.studio",this.#t=p??!1,i?this.config=i:this.#e={url:[`${this._baseUrl}/config`,this.organizationId,this.environment,this.state||"active"].join("/"),timeout:n||3e3,retries:u??2,revalidate:m}}},exports.getRandomTestValue=e=>{let[t]=e;if(!t)return null;if(1===e.length)return t.slug;let r=Math.random()*e.reduce((e,{split:t})=>e+t,0);return(e.find(({split:e})=>(r-=e)<=0)||t).slug},exports.getVisitorMatchesAudience=(e,t)=>!e||Object.entries(e).every(([e,r])=>t[e]===r),exports.parseUserAgent=o=>{var a;if(!o||"string"!=typeof o)return null;let u=new e.UAParser(o).getResult(),l=(a=u.device.type)&&t.includes(a)?a:"desktop";return{pointer:s.includes(l)?"coarse":"fine",device:l,browser:((e="")=>{if(!e)return"other";let t=e.toLowerCase(),i=r.find(e=>t.includes(e));return i||(n.includes(t)?"social":"other")})(u.browser.name),os:((e="")=>{if(!e)return"unix";let t=e.toLowerCase(),r=i.find(e=>t.includes(e));return r||"unix"})(u.os.name)}};
|
|
2
|
+
//# sourceMappingURL=base-gi7y83ve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-gi7y83ve.js","sources":["../src/config/audiences.ts","../src/utils/parseUserAgent.ts","../src/config/constants.ts","../src/utils/delay.ts","../src/utils/errors.ts","../src/utils/getRandomString.ts","../src/utils/normalizeConfig.ts","../src/utils/retry.ts","../src/utils/getTimeoutError.ts","../src/utils/timeoutFetch.ts","../src/config/urls.ts","../src/base.ts","../src/utils/getRandomTestValue.ts","../src/utils/getVisitorMatchesAudience.ts"],"sourcesContent":["/**\n * @constant AUDIENCE_PARAMS\n * @description All posibile audience tracking options\n */\nexport const AUDIENCE_PARAMS = {\n\t//? Location — coarse IP-geolocation (ISO 3166-1 alpha-2 country code). Values\n\t//? aren't enumerated here (any country code is valid); the key exists so\n\t//? audiences can target `country`. Resolved server-side (SSR/middleware) from\n\t//? the request, since the browser can't determine it from the user agent.\n\tcountry: [] as ReadonlyArray<string>,\n\t//? Technical\n\tpointer: ['coarse', 'fine'],\n\tdevice: [\n\t\t'wearable',\n\t\t'mobile',\n\t\t'tablet',\n\t\t'console',\n\t\t'smarttv',\n\t\t'embedded',\n\t\t'desktop',\n\t],\n\tbrowser: [\n\t\t'chrome',\n\t\t'safari',\n\t\t'firefox',\n\t\t'edge',\n\t\t'ie',\n\t\t'samsung internet',\n\t\t'social',\n\t\t'other',\n\t],\n\tos: ['mac os', 'ios', 'android', 'windows', 'unix'],\n} as const\n\n//? Generated types\nexport type AudienceParamKey = keyof typeof AUDIENCE_PARAMS\n\nexport type AudienceParamPointer = (typeof AUDIENCE_PARAMS.pointer)[number]\n\nexport type AudienceParamDevice = (typeof AUDIENCE_PARAMS.device)[number]\n\nexport type AudienceParamBrowser = (typeof AUDIENCE_PARAMS.browser)[number]\n\nexport type AudienceParamOs = (typeof AUDIENCE_PARAMS.os)[number]\n\nexport const AUDIENCE_PARAM_KEYS = Object.keys(\n\tAUDIENCE_PARAMS,\n) as ReadonlyArray<AudienceParamKey>\n","import { UAParser } from 'ua-parser-js'\n\nimport {\n\tAUDIENCE_PARAMS,\n\tAudienceParamBrowser,\n\tAudienceParamDevice,\n\tAudienceParamOs,\n\tAudienceParamPointer,\n} from '../config/audiences'\n\nexport type ParsedUserAgent = {\n\tpointer: AudienceParamPointer\n\tdevice: AudienceParamDevice\n\tbrowser: AudienceParamBrowser\n\tos: AudienceParamOs\n\t// Coarse geo, not derived from the user agent — merged in server-side\n\t// (SSR/middleware) so audiences can target by country. Absent client-side.\n\tcountry?: string\n}\n\nconst formatDeviceType = (deviceType?: string): AudienceParamDevice => {\n\tif (\n\t\tdeviceType &&\n\t\tAUDIENCE_PARAMS.device.includes(deviceType as AudienceParamDevice)\n\t) {\n\t\treturn deviceType as AudienceParamDevice\n\t}\n\treturn 'desktop'\n}\n\nconst SOCIAL_BROWSERS: ReadonlyArray<string> = [\n\t'tiktok',\n\t'wechat',\n\t'weibo',\n\t'snapchat',\n\t'klarna',\n\t'Line',\n\t'instagram',\n\t'facebook',\n\t'alipay',\n\t'Baidu',\n]\n\nconst formatBrowser = (browserName: string = ''): AudienceParamBrowser => {\n\tif (!browserName) return 'other'\n\n\tconst name = browserName.toLowerCase()\n\n\tconst browserTypeMatch = AUDIENCE_PARAMS.browser.find((browserType) => {\n\t\treturn name.includes(browserType)\n\t})\n\n\tif (browserTypeMatch) return browserTypeMatch\n\n\tif (SOCIAL_BROWSERS.includes(name)) return 'social'\n\treturn 'other'\n}\n\nconst PRIMARY_TOUCH_DEVICES: ReadonlyArray<AudienceParamDevice> = [\n\t'wearable',\n\t'mobile',\n\t'tablet',\n]\n\nconst formatPointer = (device: AudienceParamDevice) => {\n\treturn PRIMARY_TOUCH_DEVICES.includes(device) ? 'coarse' : 'fine'\n}\n\nconst formatOs = (osName: string = ''): AudienceParamOs => {\n\tif (!osName) return 'unix'\n\n\tconst os = osName.toLowerCase()\n\n\tconst osTypeMatch = AUDIENCE_PARAMS.os.find((osType) => {\n\t\treturn os.includes(osType)\n\t})\n\n\tif (osTypeMatch) return osTypeMatch\n\treturn 'unix'\n}\n\nexport const parseUserAgent = (userAgent: string): ParsedUserAgent | null => {\n\tif (!userAgent || typeof userAgent !== 'string') return null\n\n\tconst parser = new UAParser(userAgent)\n\tconst results = parser.getResult()\n\n\tconst device = formatDeviceType(results.device.type)\n\n\treturn {\n\t\tpointer: formatPointer(device),\n\t\tdevice: device,\n\t\tbrowser: formatBrowser(results.browser.name),\n\t\tos: formatOs(results.os.name),\n\t}\n}\n","export const COOKIE_NAME_VISITOR = 'visitorId'\n\nexport const VISITOR_ID_PREFIX = 'visi'\n\nexport const VISITOR_ID_LENGTH = 26\n\nexport const VISITOR_ID_SEPARATOR = '_'\n\n/** Default number of retries for a failed config fetch (network / 5xx / 429). */\nexport const CONFIG_RETRY_COUNT = 2\n\n/** Base delay for config-fetch backoff, in ms. */\nexport const CONFIG_RETRY_BASE_DELAY_MS = 300\n\n/** Upper bound for a single config-fetch backoff wait, in ms. */\nexport const CONFIG_RETRY_MAX_DELAY_MS = 3000\n","export const delay = (ms: number) =>\n\tnew Promise((resolve) => setTimeout(resolve, ms))\n","export type ImproveFetchErrorReason =\n\t| 'timeout'\n\t| 'network'\n\t| 'unauthorized'\n\t| 'rate-limited'\n\t| 'server'\n\t| 'client'\n\t| 'invalid-response'\n\nconst REASON_MESSAGES: Record<ImproveFetchErrorReason, string> = {\n\ttimeout: 'Configuration request timed out',\n\tnetwork: 'Configuration request could not reach the server',\n\tunauthorized:\n\t\t'Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment',\n\t'rate-limited':\n\t\t'Configuration request was rate limited (429): too many requests, or the organization usage limit was reached',\n\tserver: 'Configuration request failed with a server error',\n\tclient: 'Configuration request was rejected by the server',\n\t'invalid-response': 'Configuration response could not be parsed as JSON',\n}\n\n/**\n * Error thrown by the config fetch layer. Carries the HTTP `status`, a coarse\n * `reason`, and (when the server sent one) the `Retry-After` delay in ms, so\n * callers can distinguish a misconfiguration (unauthorized) from a transient\n * failure (rate-limited/server/timeout) instead of guessing from a string.\n */\nexport class ImproveFetchError extends Error {\n\treason: ImproveFetchErrorReason\n\tstatus?: number | undefined\n\tretryAfterMs?: number | undefined\n\n\tconstructor(\n\t\treason: ImproveFetchErrorReason,\n\t\toptions?: {\n\t\t\tstatus?: number | undefined\n\t\t\tretryAfterMs?: number | undefined\n\t\t\tcause?: unknown\n\t\t},\n\t) {\n\t\tsuper(REASON_MESSAGES[reason])\n\t\tthis.name = 'ImproveFetchError'\n\t\tthis.reason = reason\n\t\tthis.status = options?.status\n\t\tthis.retryAfterMs = options?.retryAfterMs\n\t\tif (options && 'cause' in options) {\n\t\t\t;(this as { cause?: unknown }).cause = options.cause\n\t\t}\n\t}\n\n\t/** Whether retrying the request could plausibly succeed. */\n\tget isRetryable() {\n\t\treturn (\n\t\t\tthis.reason === 'rate-limited' ||\n\t\t\tthis.reason === 'server' ||\n\t\t\tthis.reason === 'timeout' ||\n\t\t\tthis.reason === 'network'\n\t\t)\n\t}\n}\n\n/** Map an HTTP status onto a coarse {@link ImproveFetchErrorReason}. */\nexport const getReasonFromStatus = (\n\tstatus: number,\n): ImproveFetchErrorReason => {\n\tif (status === 429) return 'rate-limited'\n\tif (status === 401 || status === 403) return 'unauthorized'\n\tif (status >= 500) return 'server'\n\treturn 'client'\n}\n","/**\n * @constant CHAR_SET\n * @description Key/value set of characters to be used for generation strings\n */\nconst CHAR_SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'\n\n/**\n * @function getRandomString\n * @description Generate a random string\n */\nexport function getRandomString(characters: number = 5): string {\n\tif (!characters || typeof characters !== 'number') return ''\n\treturn Array(characters)\n\t\t.fill('')\n\t\t.reduce((acc) => {\n\t\t\tacc += CHAR_SET.charAt(Math.floor(Math.random() * CHAR_SET.length))\n\t\t\treturn acc\n\t\t}, '')\n}\n","import { ImproveConfiguration } from '../types'\nimport { ImproveFetchError } from './errors'\n\n/**\n * @function normalizeConfig\n * @description Coerce a parsed config response into a well-formed\n * {@link ImproveConfiguration}. The backend returns `200 {}` for an unknown\n * org/environment (and on its own internal error). An empty object is truthy,\n * so it slips past `if (!this.config)` guards and then crashes downstream on\n * `config.tests[slug]` (`Cannot read properties of undefined`). Guaranteeing\n * `tests`/`flags`/`audience` are always objects lets the SDK fail open to\n * default values instead of throwing during render/middleware.\n *\n * Throws {@link ImproveFetchError} `invalid-response` when the body isn't an\n * object at all (e.g. an array, string, or null).\n */\nexport const normalizeConfig = (body: unknown): ImproveConfiguration => {\n\tif (typeof body !== 'object' || body === null || Array.isArray(body)) {\n\t\tthrow new ImproveFetchError('invalid-response')\n\t}\n\n\tconst raw = body as Partial<ImproveConfiguration>\n\n\treturn {\n\t\tname: typeof raw.name === 'string' ? raw.name : '',\n\t\tversion: typeof raw.version === 'number' ? raw.version : 0,\n\t\ttests: raw.tests ?? {},\n\t\tflags: raw.flags ?? {},\n\t\taudience: raw.audience ?? {},\n\t}\n}\n","/**\n * @function parseRetryAfterMs\n * @description Parse an HTTP `Retry-After` header into milliseconds. Supports\n * both the delta-seconds form (`\"120\"`) and the HTTP-date form\n * (`\"Wed, 21 Oct 2026 07:28:00 GMT\"`). Returns `undefined` when the header is\n * absent or unparseable so the caller can fall back to computed backoff.\n *\n * @param {string | null} [header] - the raw `Retry-After` header value\n * @param {number} [now] - current epoch ms (injectable for testing)\n */\nexport const parseRetryAfterMs = (\n\theader: string | null | undefined,\n\tnow: number = Date.now(),\n): number | undefined => {\n\tif (!header) return undefined\n\n\tconst seconds = Number(header)\n\tif (Number.isFinite(seconds)) return Math.max(0, Math.round(seconds * 1000))\n\n\tconst date = Date.parse(header)\n\tif (Number.isNaN(date)) return undefined\n\n\treturn Math.max(0, date - now)\n}\n\n/**\n * @function getBackoffDelayMs\n * @description Exponential backoff with full jitter, capped at `maxDelay`.\n * Jitter spreads retries so concurrent clients don't stampede the origin in\n * lockstep after a shared failure.\n *\n * @param {number} attempt - zero-based retry attempt index\n * @param {number} baseDelay - base delay in ms\n * @param {number} maxDelay - upper bound in ms\n */\nexport const getBackoffDelayMs = (\n\tattempt: number,\n\tbaseDelay: number,\n\tmaxDelay: number,\n): number => {\n\tconst exponential = Math.min(maxDelay, baseDelay * 2 ** attempt)\n\treturn Math.round(Math.random() * exponential)\n}\n","import { delay } from './delay'\n\n/**\n * @async @function getTimeoutError\n * @description Throw an error after a delay\n *\n * @param {number} [timeout] - time in ms after to reject default: 1000\n * @param {AbortController} [controller] - (optional) AbortController to abort the request\n */\nexport const getTimeoutError = async (\n\ttimeout: number = 1000,\n\tcontroller: AbortController,\n): Promise<null> => {\n\tawait delay(timeout)\n\tcontroller?.abort()\n\treturn null\n}\n","import { getTimeoutError } from './getTimeoutError'\n\n/**\n * @async @function timeoutFetch\n * @description Fetch with a timeout\n *\n * @param {number} timeout - time in ms after to reject default: 3000\n * @param {string} url - url to fetch\n * @param {Object} [options] - (optional) options to pass to fetch\n */\nexport const timeoutFetch = (\n\ttimeout: number = 3000,\n\turl: string,\n\toptions?: object,\n) => {\n\tconst controller = new AbortController()\n\treturn Promise.race([\n\t\tfetch(url, {\n\t\t\t...options,\n\t\t\tsignal: controller.signal,\n\t\t}),\n\t\tgetTimeoutError(timeout, controller),\n\t])\n}\n","export const BASE_URL = 'https://improve.obelism.studio'\nexport const CONFIG_PATH = `/config`\n// Events go to Obelism numbers (the numbers SDK's default endpoint); only a\n// custom `baseUrl` sends them to its own /api/log.\nexport const ANALYTICS_PATH = `/api/log`\n","import {\n\tCONFIG_RETRY_BASE_DELAY_MS,\n\tCONFIG_RETRY_COUNT,\n\tCONFIG_RETRY_MAX_DELAY_MS,\n\tCOOKIE_NAME_VISITOR,\n\tVISITOR_ID_LENGTH,\n\tVISITOR_ID_PREFIX,\n\tVISITOR_ID_SEPARATOR,\n} from './config/constants'\nimport { CONFIG_PATH, BASE_URL } from './config/urls'\nimport {\n\tImproveConfiguration,\n\tImproveEnvironmentOption,\n\tImproveSetupArgs,\n\tImproveTestState,\n} from './types'\nimport { delay } from './utils/delay'\nimport { getReasonFromStatus, ImproveFetchError } from './utils/errors'\nimport { getRandomString } from './utils/getRandomString'\nimport { normalizeConfig } from './utils/normalizeConfig'\nimport { getBackoffDelayMs, parseRetryAfterMs } from './utils/retry'\nimport { timeoutFetch } from './utils/timeoutFetch'\n\ntype ConfigFetch = {\n\turl: string\n\ttimeout: number\n\tretries: number\n\trevalidate?: number | undefined\n}\n\n// Next.js augments RequestInit with a `next` field for its fetch cache.\ntype RequestInitWithNext = RequestInit & {\n\tnext?: { revalidate?: number; tags?: string[] }\n}\n\nexport class BaseImproveSDK {\n\torganizationId: string\n\tenvironment: ImproveEnvironmentOption = 'develop'\n\tstate: ImproveTestState | undefined\n\n\t#configFetch: ConfigFetch | null = null\n\n\t#warningsDisabled: boolean = false\n\n\tconfig: ImproveConfiguration | null = null\n\n\t_baseUrl: undefined | string\n\n\tconstructor({\n\t\torganizationId,\n\t\tenvironment,\n\t\tstate,\n\t\tconfig,\n\t\tfetchTimeout,\n\t\tbaseUrl,\n\t\tconfigRetries,\n\t\tconfigRevalidate,\n\t\tdisableWarnings,\n\t}: ImproveSetupArgs) {\n\t\tthis.organizationId = organizationId\n\t\tthis.environment = environment\n\t\tthis.state = state\n\t\tthis._baseUrl = baseUrl || BASE_URL\n\t\tthis.#warningsDisabled = disableWarnings ?? false\n\n\t\tif (config) {\n\t\t\tthis.config = config\n\t\t} else {\n\t\t\tthis.#configFetch = {\n\t\t\t\turl: [\n\t\t\t\t\t`${this._baseUrl}${CONFIG_PATH}`,\n\t\t\t\t\tthis.organizationId,\n\t\t\t\t\tthis.environment,\n\t\t\t\t\tthis.state || 'active',\n\t\t\t\t].join('/'),\n\t\t\t\ttimeout: fetchTimeout || 3000,\n\t\t\t\tretries: configRetries ?? CONFIG_RETRY_COUNT,\n\t\t\t\trevalidate: configRevalidate,\n\t\t\t}\n\t\t}\n\t}\n\n\t_fetchConfig = async (config?: RequestInit) => {\n\t\tif (this.config) return\n\n\t\tif (!this.#configFetch) throw new Error('No config fetch setup provided')\n\n\t\tconst { url, timeout, retries, revalidate } = this.#configFetch\n\n\t\t// Opt into the Next.js fetch cache when a revalidate window is configured,\n\t\t// so the datafile is reused across requests instead of re-fetched.\n\t\tconst requestInit: RequestInitWithNext = { ...config }\n\t\tif (typeof revalidate === 'number') {\n\t\t\trequestInit.next = {\n\t\t\t\t...(config as RequestInitWithNext | undefined)?.next,\n\t\t\t\trevalidate,\n\t\t\t}\n\t\t}\n\n\t\t// Seed with a generic network error so an exhausted loop always has a\n\t\t// meaningful, typed error to throw.\n\t\tlet lastError = new ImproveFetchError('network')\n\n\t\tfor (let attempt = 0; attempt <= retries; attempt++) {\n\t\t\tif (attempt > 0) {\n\t\t\t\t// Prefer a server-provided Retry-After; otherwise back off with jitter.\n\t\t\t\tawait delay(\n\t\t\t\t\tlastError.retryAfterMs ??\n\t\t\t\t\t\tgetBackoffDelayMs(\n\t\t\t\t\t\t\tattempt - 1,\n\t\t\t\t\t\t\tCONFIG_RETRY_BASE_DELAY_MS,\n\t\t\t\t\t\t\tCONFIG_RETRY_MAX_DELAY_MS,\n\t\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tlet res: Response | null\n\t\t\ttry {\n\t\t\t\tres = await timeoutFetch(timeout, url, requestInit)\n\t\t\t} catch (cause) {\n\t\t\t\t// timeoutFetch aborts on timeout (AbortError) and rejects on\n\t\t\t\t// network-level failures — both are transient and retryable.\n\t\t\t\tconst aborted = cause instanceof Error && cause.name === 'AbortError'\n\t\t\t\tlastError = new ImproveFetchError(aborted ? 'timeout' : 'network', {\n\t\t\t\t\tcause,\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// timeoutFetch resolves null when the timeout wins the race.\n\t\t\tif (!res) {\n\t\t\t\tlastError = new ImproveFetchError('timeout')\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (res.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tthis.config = normalizeConfig(await res.json())\n\t\t\t\t} catch (cause) {\n\t\t\t\t\tlastError =\n\t\t\t\t\t\tcause instanceof ImproveFetchError\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: new ImproveFetchError('invalid-response', { cause })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn this.config\n\t\t\t}\n\n\t\t\tlastError = new ImproveFetchError(getReasonFromStatus(res.status), {\n\t\t\t\tstatus: res.status,\n\t\t\t\tretryAfterMs: parseRetryAfterMs(res.headers.get('Retry-After')),\n\t\t\t})\n\n\t\t\t// Auth/validation rejections won't change on retry — fail fast.\n\t\t\tif (!lastError.isRetryable) break\n\t\t}\n\n\t\tthrow lastError\n\t}\n\n\tloadConfig = (config: ImproveConfiguration) => {\n\t\tthis.config = config\n\t}\n\n\tgenerateVisitorId = () => {\n\t\treturn [\n\t\t\tVISITOR_ID_PREFIX,\n\t\t\tgetRandomString(VISITOR_ID_LENGTH).toUpperCase(),\n\t\t].join(VISITOR_ID_SEPARATOR)\n\t}\n\n\tgetVisitorCookieName = () => COOKIE_NAME_VISITOR\n\n\t/**\n\t * Emit a development warning, unless warnings were disabled via the\n\t * `disableWarnings` setup option. Centralized so every SDK warning honors the\n\t * same switch. No-op where `console` is unavailable.\n\t */\n\tprotected _warn = (message: string) => {\n\t\tif (this.#warningsDisabled) return\n\t\tif (typeof console === 'undefined') return\n\t\tconsole.warn(`[Improve] ${message}`)\n\t}\n\n\tvalidateTestValue = (testName: string, testValue: string) => {\n\t\tif (!this.config)\n\t\t\tthrow new Error(\n\t\t\t\t'Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup',\n\t\t\t)\n\n\t\tconst testConfig = this.config.tests[testName]\n\n\t\tif (!testConfig) throw new Error(`No config found for ${testName}`)\n\n\t\treturn Boolean(\n\t\t\ttestConfig.options.find((option) => option.slug === testValue),\n\t\t)\n\t}\n\n\tvalidateVisitorId = (possibleVisitorId: string) => {\n\t\tconst visitorIdParts = possibleVisitorId.split(VISITOR_ID_SEPARATOR)\n\t\tif (visitorIdParts.length !== 2) return false\n\t\tconst [key, value] = visitorIdParts as [string, string]\n\t\treturn key === VISITOR_ID_PREFIX && value.length === VISITOR_ID_LENGTH\n\t}\n}\n","import { ImproveTestOption } from '../types'\n\n// function cryptoRandom() {\n// \treturn crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1)\n// }\n\nexport const getRandomTestValue = (options: ImproveTestOption[]) => {\n\tconst [first] = options\n\tif (!first) return null\n\tif (options.length === 1) return first.slug\n\n\t// Get a random number between 0 and 1\n\tconst sum = options.reduce((acc, { split }) => acc + split, 0)\n\tlet value = Math.random() * sum\n\n\tconst match =\n\t\toptions.find(({ split }) => {\n\t\t\tvalue -= split\n\t\t\treturn value <= 0\n\t\t}) || first\n\n\treturn match.slug\n}\n","import { ImproveAudienceValue } from '../types'\nimport { AudienceParamKey } from '../config/audiences'\nimport { ParsedUserAgent } from './parseUserAgent'\n\nexport const getVisitorMatchesAudience = (\n\taudience: ImproveAudienceValue | undefined,\n\tvisitorParams: ParsedUserAgent,\n) => {\n\tif (!audience) return true\n\treturn Object.entries(audience).every(([paramKey, paramValue]) => {\n\t\treturn visitorParams[paramKey as AudienceParamKey] === paramValue\n\t})\n}\n"],"names":["SOCIAL_BROWSERS","PRIMARY_TOUCH_DEVICES","VISITOR_ID_PREFIX","delay","ms","Promise","resolve","setTimeout","REASON_MESSAGES","timeout","network","unauthorized","server","client","ImproveFetchError","Error","reason","options","name","status","retryAfterMs","cause","isRetryable","getReasonFromStatus","CHAR_SET","normalizeConfig","body","Array","isArray","raw","version","tests","flags","audience","parseRetryAfterMs","header","now","Date","seconds","Number","isFinite","Math","max","round","date","parse","isNaN","getBackoffDelayMs","attempt","baseDelay","maxDelay","random","min","getTimeoutError","controller","abort","timeoutFetch","url","AbortController","race","fetch","signal","organizationId","environment","state","config","fetchTimeout","baseUrl","configRetries","configRevalidate","disableWarnings","_fetchConfig","retries","revalidate","requestInit","next","lastError","res","aborted","ok","json","headers","get","loadConfig","generateVisitorId","getRandomString","characters","fill","reduce","acc","charAt","floor","length","toUpperCase","join","getVisitorCookieName","_warn","message","console","warn","validateTestValue","testName","testValue","testConfig","Boolean","find","option","slug","validateVisitorId","possibleVisitorId","visitorIdParts","split","key","value","_baseUrl","first","match","visitorParams","Object","entries","every","paramKey","paramValue","userAgent","deviceType","results","parser","UAParser","getResult","device","type","AUDIENCE_PARAMS","includes","pointer","browser","formatBrowser","browserName","toLowerCase","browserTypeMatch","browserType","os","formatOs","osName","osTypeMatch","osType"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{UAParser as e}from"ua-parser-js";let t=["wearable","mobile","tablet","console","smarttv","embedded","desktop"],r=["chrome","safari","firefox","edge","ie","samsung internet","social","other"],i=["mac os","ios","android","windows","unix"],n=["tiktok","wechat","weibo","snapchat","klarna","Line","instagram","facebook","alipay","Baidu"],s=["wearable","mobile","tablet"],o=o=>{var a;if(!o||"string"!=typeof o)return null;let u=new e(o).getResult(),l=(a=u.device.type)&&t.includes(a)?a:"desktop";return{pointer:s.includes(l)?"coarse":"fine",device:l,browser:((e="")=>{if(!e)return"other";let t=e.toLowerCase(),i=r.find(e=>t.includes(e));return i||(n.includes(t)?"social":"other")})(u.browser.name),os:((e="")=>{if(!e)return"unix";let t=e.toLowerCase(),r=i.find(e=>t.includes(e));return r||"unix"})(u.os.name)}},a=(e,t)=>!e||Object.entries(e).every(([e,r])=>t[e]===r),u=e=>{let[t]=e;if(!t)return null;if(1===e.length)return t.slug;let r=Math.random()*e.reduce((e,{split:t})=>e+t,0);return(e.find(({split:e})=>(r-=e)<=0)||t).slug},l="visi",h="/api/log",c=e=>new Promise(t=>setTimeout(t,e)),f={timeout:"Configuration request timed out",network:"Configuration request could not reach the server",unauthorized:"Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment","rate-limited":"Configuration request was rate limited (429): too many requests, or the organization usage limit was reached",server:"Configuration request failed with a server error",client:"Configuration request was rejected by the server","invalid-response":"Configuration response could not be parsed as JSON"};class d extends Error{constructor(e,t){super(f[e]),this.name="ImproveFetchError",this.reason=e,this.status=t?.status,this.retryAfterMs=t?.retryAfterMs,t&&"cause"in t&&(this.cause=t.cause)}get isRetryable(){return"rate-limited"===this.reason||"server"===this.reason||"timeout"===this.reason||"network"===this.reason}}let g=e=>429===e?"rate-limited":401===e||403===e?"unauthorized":e>=500?"server":"client",m="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",w=e=>{if("object"!=typeof e||null===e||Array.isArray(e))throw new d("invalid-response");return{name:"string"==typeof e.name?e.name:"",version:"number"==typeof e.version?e.version:0,tests:e.tests??{},flags:e.flags??{},audience:e.audience??{}}},p=(e,t=Date.now())=>{if(!e)return;let r=Number(e);if(Number.isFinite(r))return Math.max(0,Math.round(1e3*r));let i=Date.parse(e);if(!Number.isNaN(i))return Math.max(0,i-t)},v=(e,t,r)=>Math.round(Math.random()*Math.min(r,t*2**e)),b=async(e=1e3,t)=>(await c(e),t?.abort(),null),y=(e=3e3,t,r)=>{let i=new AbortController;return Promise.race([fetch(t,{...r,signal:i.signal}),b(e,i)])};class C{#e;#t;constructor({organizationId:e,environment:t,state:r,config:i,fetchTimeout:n,baseUrl:s,configRetries:o,configRevalidate:a,disableWarnings:u}){this.environment="develop",this.#e=null,this.#t=!1,this.config=null,this._fetchConfig=async e=>{if(this.config)return;if(!this.#e)throw Error("No config fetch setup provided");let{url:t,timeout:r,retries:i,revalidate:n}=this.#e,s={...e};"number"==typeof n&&(s.next={...e?.next,revalidate:n});let o=new d("network");for(let e=0;e<=i;e++){let i;e>0&&await c(o.retryAfterMs??v(e-1,300,3e3));try{i=await y(r,t,s)}catch(e){o=new d(e instanceof Error&&"AbortError"===e.name?"timeout":"network",{cause:e});continue}if(!i){o=new d("timeout");continue}if(i.ok){try{this.config=w(await i.json())}catch(e){o=e instanceof d?e:new d("invalid-response",{cause:e});continue}return this.config}if(!(o=new d(g(i.status),{status:i.status,retryAfterMs:p(i.headers.get("Retry-After"))})).isRetryable)break}throw o},this.loadConfig=e=>{this.config=e},this.generateVisitorId=()=>[l,(function(e=5){return e&&"number"==typeof e?Array(e).fill("").reduce(e=>e+=m.charAt(Math.floor(Math.random()*m.length)),""):""})(26).toUpperCase()].join("_"),this.getVisitorCookieName=()=>"visitorId",this._warn=e=>{this.#t||"u">typeof console&&console.warn(`[Improve] ${e}`)},this.validateTestValue=(e,t)=>{if(!this.config)throw Error("Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup");let r=this.config.tests[e];if(!r)throw Error(`No config found for ${e}`);return!!r.options.find(e=>e.slug===t)},this.validateVisitorId=e=>{let t=e.split("_");if(2!==t.length)return!1;let[r,i]=t;return r===l&&26===i.length},this.organizationId=e,this.environment=t,this.state=r,this._baseUrl=s||"https://improve.obelism.studio",this.#t=u??!1,i?this.config=i:this.#e={url:[`${this._baseUrl}/config`,this.organizationId,this.environment,this.state||"active"].join("/"),timeout:n||3e3,retries:o??2,revalidate:a}}}export{h as A,C as B,u as a,a as g,o as p};
|
|
2
|
+
//# sourceMappingURL=base-i6scrpy2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-i6scrpy2.mjs","sources":["../src/config/audiences.ts","../src/utils/parseUserAgent.ts","../src/utils/getVisitorMatchesAudience.ts","../src/utils/getRandomTestValue.ts","../src/config/constants.ts","../src/config/urls.ts","../src/utils/delay.ts","../src/utils/errors.ts","../src/utils/getRandomString.ts","../src/utils/normalizeConfig.ts","../src/utils/retry.ts","../src/utils/getTimeoutError.ts","../src/utils/timeoutFetch.ts","../src/base.ts"],"sourcesContent":["/**\n * @constant AUDIENCE_PARAMS\n * @description All posibile audience tracking options\n */\nexport const AUDIENCE_PARAMS = {\n\t//? Location — coarse IP-geolocation (ISO 3166-1 alpha-2 country code). Values\n\t//? aren't enumerated here (any country code is valid); the key exists so\n\t//? audiences can target `country`. Resolved server-side (SSR/middleware) from\n\t//? the request, since the browser can't determine it from the user agent.\n\tcountry: [] as ReadonlyArray<string>,\n\t//? Technical\n\tpointer: ['coarse', 'fine'],\n\tdevice: [\n\t\t'wearable',\n\t\t'mobile',\n\t\t'tablet',\n\t\t'console',\n\t\t'smarttv',\n\t\t'embedded',\n\t\t'desktop',\n\t],\n\tbrowser: [\n\t\t'chrome',\n\t\t'safari',\n\t\t'firefox',\n\t\t'edge',\n\t\t'ie',\n\t\t'samsung internet',\n\t\t'social',\n\t\t'other',\n\t],\n\tos: ['mac os', 'ios', 'android', 'windows', 'unix'],\n} as const\n\n//? Generated types\nexport type AudienceParamKey = keyof typeof AUDIENCE_PARAMS\n\nexport type AudienceParamPointer = (typeof AUDIENCE_PARAMS.pointer)[number]\n\nexport type AudienceParamDevice = (typeof AUDIENCE_PARAMS.device)[number]\n\nexport type AudienceParamBrowser = (typeof AUDIENCE_PARAMS.browser)[number]\n\nexport type AudienceParamOs = (typeof AUDIENCE_PARAMS.os)[number]\n\nexport const AUDIENCE_PARAM_KEYS = Object.keys(\n\tAUDIENCE_PARAMS,\n) as ReadonlyArray<AudienceParamKey>\n","import { UAParser } from 'ua-parser-js'\n\nimport {\n\tAUDIENCE_PARAMS,\n\tAudienceParamBrowser,\n\tAudienceParamDevice,\n\tAudienceParamOs,\n\tAudienceParamPointer,\n} from '../config/audiences'\n\nexport type ParsedUserAgent = {\n\tpointer: AudienceParamPointer\n\tdevice: AudienceParamDevice\n\tbrowser: AudienceParamBrowser\n\tos: AudienceParamOs\n\t// Coarse geo, not derived from the user agent — merged in server-side\n\t// (SSR/middleware) so audiences can target by country. Absent client-side.\n\tcountry?: string\n}\n\nconst formatDeviceType = (deviceType?: string): AudienceParamDevice => {\n\tif (\n\t\tdeviceType &&\n\t\tAUDIENCE_PARAMS.device.includes(deviceType as AudienceParamDevice)\n\t) {\n\t\treturn deviceType as AudienceParamDevice\n\t}\n\treturn 'desktop'\n}\n\nconst SOCIAL_BROWSERS: ReadonlyArray<string> = [\n\t'tiktok',\n\t'wechat',\n\t'weibo',\n\t'snapchat',\n\t'klarna',\n\t'Line',\n\t'instagram',\n\t'facebook',\n\t'alipay',\n\t'Baidu',\n]\n\nconst formatBrowser = (browserName: string = ''): AudienceParamBrowser => {\n\tif (!browserName) return 'other'\n\n\tconst name = browserName.toLowerCase()\n\n\tconst browserTypeMatch = AUDIENCE_PARAMS.browser.find((browserType) => {\n\t\treturn name.includes(browserType)\n\t})\n\n\tif (browserTypeMatch) return browserTypeMatch\n\n\tif (SOCIAL_BROWSERS.includes(name)) return 'social'\n\treturn 'other'\n}\n\nconst PRIMARY_TOUCH_DEVICES: ReadonlyArray<AudienceParamDevice> = [\n\t'wearable',\n\t'mobile',\n\t'tablet',\n]\n\nconst formatPointer = (device: AudienceParamDevice) => {\n\treturn PRIMARY_TOUCH_DEVICES.includes(device) ? 'coarse' : 'fine'\n}\n\nconst formatOs = (osName: string = ''): AudienceParamOs => {\n\tif (!osName) return 'unix'\n\n\tconst os = osName.toLowerCase()\n\n\tconst osTypeMatch = AUDIENCE_PARAMS.os.find((osType) => {\n\t\treturn os.includes(osType)\n\t})\n\n\tif (osTypeMatch) return osTypeMatch\n\treturn 'unix'\n}\n\nexport const parseUserAgent = (userAgent: string): ParsedUserAgent | null => {\n\tif (!userAgent || typeof userAgent !== 'string') return null\n\n\tconst parser = new UAParser(userAgent)\n\tconst results = parser.getResult()\n\n\tconst device = formatDeviceType(results.device.type)\n\n\treturn {\n\t\tpointer: formatPointer(device),\n\t\tdevice: device,\n\t\tbrowser: formatBrowser(results.browser.name),\n\t\tos: formatOs(results.os.name),\n\t}\n}\n","import { ImproveAudienceValue } from '../types'\nimport { AudienceParamKey } from '../config/audiences'\nimport { ParsedUserAgent } from './parseUserAgent'\n\nexport const getVisitorMatchesAudience = (\n\taudience: ImproveAudienceValue | undefined,\n\tvisitorParams: ParsedUserAgent,\n) => {\n\tif (!audience) return true\n\treturn Object.entries(audience).every(([paramKey, paramValue]) => {\n\t\treturn visitorParams[paramKey as AudienceParamKey] === paramValue\n\t})\n}\n","import { ImproveTestOption } from '../types'\n\n// function cryptoRandom() {\n// \treturn crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1)\n// }\n\nexport const getRandomTestValue = (options: ImproveTestOption[]) => {\n\tconst [first] = options\n\tif (!first) return null\n\tif (options.length === 1) return first.slug\n\n\t// Get a random number between 0 and 1\n\tconst sum = options.reduce((acc, { split }) => acc + split, 0)\n\tlet value = Math.random() * sum\n\n\tconst match =\n\t\toptions.find(({ split }) => {\n\t\t\tvalue -= split\n\t\t\treturn value <= 0\n\t\t}) || first\n\n\treturn match.slug\n}\n","export const COOKIE_NAME_VISITOR = 'visitorId'\n\nexport const VISITOR_ID_PREFIX = 'visi'\n\nexport const VISITOR_ID_LENGTH = 26\n\nexport const VISITOR_ID_SEPARATOR = '_'\n\n/** Default number of retries for a failed config fetch (network / 5xx / 429). */\nexport const CONFIG_RETRY_COUNT = 2\n\n/** Base delay for config-fetch backoff, in ms. */\nexport const CONFIG_RETRY_BASE_DELAY_MS = 300\n\n/** Upper bound for a single config-fetch backoff wait, in ms. */\nexport const CONFIG_RETRY_MAX_DELAY_MS = 3000\n","export const BASE_URL = 'https://improve.obelism.studio'\nexport const CONFIG_PATH = `/config`\n// Events go to Obelism numbers (the numbers SDK's default endpoint); only a\n// custom `baseUrl` sends them to its own /api/log.\nexport const ANALYTICS_PATH = `/api/log`\n","export const delay = (ms: number) =>\n\tnew Promise((resolve) => setTimeout(resolve, ms))\n","export type ImproveFetchErrorReason =\n\t| 'timeout'\n\t| 'network'\n\t| 'unauthorized'\n\t| 'rate-limited'\n\t| 'server'\n\t| 'client'\n\t| 'invalid-response'\n\nconst REASON_MESSAGES: Record<ImproveFetchErrorReason, string> = {\n\ttimeout: 'Configuration request timed out',\n\tnetwork: 'Configuration request could not reach the server',\n\tunauthorized:\n\t\t'Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment',\n\t'rate-limited':\n\t\t'Configuration request was rate limited (429): too many requests, or the organization usage limit was reached',\n\tserver: 'Configuration request failed with a server error',\n\tclient: 'Configuration request was rejected by the server',\n\t'invalid-response': 'Configuration response could not be parsed as JSON',\n}\n\n/**\n * Error thrown by the config fetch layer. Carries the HTTP `status`, a coarse\n * `reason`, and (when the server sent one) the `Retry-After` delay in ms, so\n * callers can distinguish a misconfiguration (unauthorized) from a transient\n * failure (rate-limited/server/timeout) instead of guessing from a string.\n */\nexport class ImproveFetchError extends Error {\n\treason: ImproveFetchErrorReason\n\tstatus?: number | undefined\n\tretryAfterMs?: number | undefined\n\n\tconstructor(\n\t\treason: ImproveFetchErrorReason,\n\t\toptions?: {\n\t\t\tstatus?: number | undefined\n\t\t\tretryAfterMs?: number | undefined\n\t\t\tcause?: unknown\n\t\t},\n\t) {\n\t\tsuper(REASON_MESSAGES[reason])\n\t\tthis.name = 'ImproveFetchError'\n\t\tthis.reason = reason\n\t\tthis.status = options?.status\n\t\tthis.retryAfterMs = options?.retryAfterMs\n\t\tif (options && 'cause' in options) {\n\t\t\t;(this as { cause?: unknown }).cause = options.cause\n\t\t}\n\t}\n\n\t/** Whether retrying the request could plausibly succeed. */\n\tget isRetryable() {\n\t\treturn (\n\t\t\tthis.reason === 'rate-limited' ||\n\t\t\tthis.reason === 'server' ||\n\t\t\tthis.reason === 'timeout' ||\n\t\t\tthis.reason === 'network'\n\t\t)\n\t}\n}\n\n/** Map an HTTP status onto a coarse {@link ImproveFetchErrorReason}. */\nexport const getReasonFromStatus = (\n\tstatus: number,\n): ImproveFetchErrorReason => {\n\tif (status === 429) return 'rate-limited'\n\tif (status === 401 || status === 403) return 'unauthorized'\n\tif (status >= 500) return 'server'\n\treturn 'client'\n}\n","/**\n * @constant CHAR_SET\n * @description Key/value set of characters to be used for generation strings\n */\nconst CHAR_SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'\n\n/**\n * @function getRandomString\n * @description Generate a random string\n */\nexport function getRandomString(characters: number = 5): string {\n\tif (!characters || typeof characters !== 'number') return ''\n\treturn Array(characters)\n\t\t.fill('')\n\t\t.reduce((acc) => {\n\t\t\tacc += CHAR_SET.charAt(Math.floor(Math.random() * CHAR_SET.length))\n\t\t\treturn acc\n\t\t}, '')\n}\n","import { ImproveConfiguration } from '../types'\nimport { ImproveFetchError } from './errors'\n\n/**\n * @function normalizeConfig\n * @description Coerce a parsed config response into a well-formed\n * {@link ImproveConfiguration}. The backend returns `200 {}` for an unknown\n * org/environment (and on its own internal error). An empty object is truthy,\n * so it slips past `if (!this.config)` guards and then crashes downstream on\n * `config.tests[slug]` (`Cannot read properties of undefined`). Guaranteeing\n * `tests`/`flags`/`audience` are always objects lets the SDK fail open to\n * default values instead of throwing during render/middleware.\n *\n * Throws {@link ImproveFetchError} `invalid-response` when the body isn't an\n * object at all (e.g. an array, string, or null).\n */\nexport const normalizeConfig = (body: unknown): ImproveConfiguration => {\n\tif (typeof body !== 'object' || body === null || Array.isArray(body)) {\n\t\tthrow new ImproveFetchError('invalid-response')\n\t}\n\n\tconst raw = body as Partial<ImproveConfiguration>\n\n\treturn {\n\t\tname: typeof raw.name === 'string' ? raw.name : '',\n\t\tversion: typeof raw.version === 'number' ? raw.version : 0,\n\t\ttests: raw.tests ?? {},\n\t\tflags: raw.flags ?? {},\n\t\taudience: raw.audience ?? {},\n\t}\n}\n","/**\n * @function parseRetryAfterMs\n * @description Parse an HTTP `Retry-After` header into milliseconds. Supports\n * both the delta-seconds form (`\"120\"`) and the HTTP-date form\n * (`\"Wed, 21 Oct 2026 07:28:00 GMT\"`). Returns `undefined` when the header is\n * absent or unparseable so the caller can fall back to computed backoff.\n *\n * @param {string | null} [header] - the raw `Retry-After` header value\n * @param {number} [now] - current epoch ms (injectable for testing)\n */\nexport const parseRetryAfterMs = (\n\theader: string | null | undefined,\n\tnow: number = Date.now(),\n): number | undefined => {\n\tif (!header) return undefined\n\n\tconst seconds = Number(header)\n\tif (Number.isFinite(seconds)) return Math.max(0, Math.round(seconds * 1000))\n\n\tconst date = Date.parse(header)\n\tif (Number.isNaN(date)) return undefined\n\n\treturn Math.max(0, date - now)\n}\n\n/**\n * @function getBackoffDelayMs\n * @description Exponential backoff with full jitter, capped at `maxDelay`.\n * Jitter spreads retries so concurrent clients don't stampede the origin in\n * lockstep after a shared failure.\n *\n * @param {number} attempt - zero-based retry attempt index\n * @param {number} baseDelay - base delay in ms\n * @param {number} maxDelay - upper bound in ms\n */\nexport const getBackoffDelayMs = (\n\tattempt: number,\n\tbaseDelay: number,\n\tmaxDelay: number,\n): number => {\n\tconst exponential = Math.min(maxDelay, baseDelay * 2 ** attempt)\n\treturn Math.round(Math.random() * exponential)\n}\n","import { delay } from './delay'\n\n/**\n * @async @function getTimeoutError\n * @description Throw an error after a delay\n *\n * @param {number} [timeout] - time in ms after to reject default: 1000\n * @param {AbortController} [controller] - (optional) AbortController to abort the request\n */\nexport const getTimeoutError = async (\n\ttimeout: number = 1000,\n\tcontroller: AbortController,\n): Promise<null> => {\n\tawait delay(timeout)\n\tcontroller?.abort()\n\treturn null\n}\n","import { getTimeoutError } from './getTimeoutError'\n\n/**\n * @async @function timeoutFetch\n * @description Fetch with a timeout\n *\n * @param {number} timeout - time in ms after to reject default: 3000\n * @param {string} url - url to fetch\n * @param {Object} [options] - (optional) options to pass to fetch\n */\nexport const timeoutFetch = (\n\ttimeout: number = 3000,\n\turl: string,\n\toptions?: object,\n) => {\n\tconst controller = new AbortController()\n\treturn Promise.race([\n\t\tfetch(url, {\n\t\t\t...options,\n\t\t\tsignal: controller.signal,\n\t\t}),\n\t\tgetTimeoutError(timeout, controller),\n\t])\n}\n","import {\n\tCONFIG_RETRY_BASE_DELAY_MS,\n\tCONFIG_RETRY_COUNT,\n\tCONFIG_RETRY_MAX_DELAY_MS,\n\tCOOKIE_NAME_VISITOR,\n\tVISITOR_ID_LENGTH,\n\tVISITOR_ID_PREFIX,\n\tVISITOR_ID_SEPARATOR,\n} from './config/constants'\nimport { CONFIG_PATH, BASE_URL } from './config/urls'\nimport {\n\tImproveConfiguration,\n\tImproveEnvironmentOption,\n\tImproveSetupArgs,\n\tImproveTestState,\n} from './types'\nimport { delay } from './utils/delay'\nimport { getReasonFromStatus, ImproveFetchError } from './utils/errors'\nimport { getRandomString } from './utils/getRandomString'\nimport { normalizeConfig } from './utils/normalizeConfig'\nimport { getBackoffDelayMs, parseRetryAfterMs } from './utils/retry'\nimport { timeoutFetch } from './utils/timeoutFetch'\n\ntype ConfigFetch = {\n\turl: string\n\ttimeout: number\n\tretries: number\n\trevalidate?: number | undefined\n}\n\n// Next.js augments RequestInit with a `next` field for its fetch cache.\ntype RequestInitWithNext = RequestInit & {\n\tnext?: { revalidate?: number; tags?: string[] }\n}\n\nexport class BaseImproveSDK {\n\torganizationId: string\n\tenvironment: ImproveEnvironmentOption = 'develop'\n\tstate: ImproveTestState | undefined\n\n\t#configFetch: ConfigFetch | null = null\n\n\t#warningsDisabled: boolean = false\n\n\tconfig: ImproveConfiguration | null = null\n\n\t_baseUrl: undefined | string\n\n\tconstructor({\n\t\torganizationId,\n\t\tenvironment,\n\t\tstate,\n\t\tconfig,\n\t\tfetchTimeout,\n\t\tbaseUrl,\n\t\tconfigRetries,\n\t\tconfigRevalidate,\n\t\tdisableWarnings,\n\t}: ImproveSetupArgs) {\n\t\tthis.organizationId = organizationId\n\t\tthis.environment = environment\n\t\tthis.state = state\n\t\tthis._baseUrl = baseUrl || BASE_URL\n\t\tthis.#warningsDisabled = disableWarnings ?? false\n\n\t\tif (config) {\n\t\t\tthis.config = config\n\t\t} else {\n\t\t\tthis.#configFetch = {\n\t\t\t\turl: [\n\t\t\t\t\t`${this._baseUrl}${CONFIG_PATH}`,\n\t\t\t\t\tthis.organizationId,\n\t\t\t\t\tthis.environment,\n\t\t\t\t\tthis.state || 'active',\n\t\t\t\t].join('/'),\n\t\t\t\ttimeout: fetchTimeout || 3000,\n\t\t\t\tretries: configRetries ?? CONFIG_RETRY_COUNT,\n\t\t\t\trevalidate: configRevalidate,\n\t\t\t}\n\t\t}\n\t}\n\n\t_fetchConfig = async (config?: RequestInit) => {\n\t\tif (this.config) return\n\n\t\tif (!this.#configFetch) throw new Error('No config fetch setup provided')\n\n\t\tconst { url, timeout, retries, revalidate } = this.#configFetch\n\n\t\t// Opt into the Next.js fetch cache when a revalidate window is configured,\n\t\t// so the datafile is reused across requests instead of re-fetched.\n\t\tconst requestInit: RequestInitWithNext = { ...config }\n\t\tif (typeof revalidate === 'number') {\n\t\t\trequestInit.next = {\n\t\t\t\t...(config as RequestInitWithNext | undefined)?.next,\n\t\t\t\trevalidate,\n\t\t\t}\n\t\t}\n\n\t\t// Seed with a generic network error so an exhausted loop always has a\n\t\t// meaningful, typed error to throw.\n\t\tlet lastError = new ImproveFetchError('network')\n\n\t\tfor (let attempt = 0; attempt <= retries; attempt++) {\n\t\t\tif (attempt > 0) {\n\t\t\t\t// Prefer a server-provided Retry-After; otherwise back off with jitter.\n\t\t\t\tawait delay(\n\t\t\t\t\tlastError.retryAfterMs ??\n\t\t\t\t\t\tgetBackoffDelayMs(\n\t\t\t\t\t\t\tattempt - 1,\n\t\t\t\t\t\t\tCONFIG_RETRY_BASE_DELAY_MS,\n\t\t\t\t\t\t\tCONFIG_RETRY_MAX_DELAY_MS,\n\t\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tlet res: Response | null\n\t\t\ttry {\n\t\t\t\tres = await timeoutFetch(timeout, url, requestInit)\n\t\t\t} catch (cause) {\n\t\t\t\t// timeoutFetch aborts on timeout (AbortError) and rejects on\n\t\t\t\t// network-level failures — both are transient and retryable.\n\t\t\t\tconst aborted = cause instanceof Error && cause.name === 'AbortError'\n\t\t\t\tlastError = new ImproveFetchError(aborted ? 'timeout' : 'network', {\n\t\t\t\t\tcause,\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// timeoutFetch resolves null when the timeout wins the race.\n\t\t\tif (!res) {\n\t\t\t\tlastError = new ImproveFetchError('timeout')\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (res.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tthis.config = normalizeConfig(await res.json())\n\t\t\t\t} catch (cause) {\n\t\t\t\t\tlastError =\n\t\t\t\t\t\tcause instanceof ImproveFetchError\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: new ImproveFetchError('invalid-response', { cause })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn this.config\n\t\t\t}\n\n\t\t\tlastError = new ImproveFetchError(getReasonFromStatus(res.status), {\n\t\t\t\tstatus: res.status,\n\t\t\t\tretryAfterMs: parseRetryAfterMs(res.headers.get('Retry-After')),\n\t\t\t})\n\n\t\t\t// Auth/validation rejections won't change on retry — fail fast.\n\t\t\tif (!lastError.isRetryable) break\n\t\t}\n\n\t\tthrow lastError\n\t}\n\n\tloadConfig = (config: ImproveConfiguration) => {\n\t\tthis.config = config\n\t}\n\n\tgenerateVisitorId = () => {\n\t\treturn [\n\t\t\tVISITOR_ID_PREFIX,\n\t\t\tgetRandomString(VISITOR_ID_LENGTH).toUpperCase(),\n\t\t].join(VISITOR_ID_SEPARATOR)\n\t}\n\n\tgetVisitorCookieName = () => COOKIE_NAME_VISITOR\n\n\t/**\n\t * Emit a development warning, unless warnings were disabled via the\n\t * `disableWarnings` setup option. Centralized so every SDK warning honors the\n\t * same switch. No-op where `console` is unavailable.\n\t */\n\tprotected _warn = (message: string) => {\n\t\tif (this.#warningsDisabled) return\n\t\tif (typeof console === 'undefined') return\n\t\tconsole.warn(`[Improve] ${message}`)\n\t}\n\n\tvalidateTestValue = (testName: string, testValue: string) => {\n\t\tif (!this.config)\n\t\t\tthrow new Error(\n\t\t\t\t'Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup',\n\t\t\t)\n\n\t\tconst testConfig = this.config.tests[testName]\n\n\t\tif (!testConfig) throw new Error(`No config found for ${testName}`)\n\n\t\treturn Boolean(\n\t\t\ttestConfig.options.find((option) => option.slug === testValue),\n\t\t)\n\t}\n\n\tvalidateVisitorId = (possibleVisitorId: string) => {\n\t\tconst visitorIdParts = possibleVisitorId.split(VISITOR_ID_SEPARATOR)\n\t\tif (visitorIdParts.length !== 2) return false\n\t\tconst [key, value] = visitorIdParts as [string, string]\n\t\treturn key === VISITOR_ID_PREFIX && value.length === VISITOR_ID_LENGTH\n\t}\n}\n"],"names":["SOCIAL_BROWSERS","PRIMARY_TOUCH_DEVICES","parseUserAgent","userAgent","deviceType","results","parser","UAParser","getResult","device","type","AUDIENCE_PARAMS","includes","pointer","browser","formatBrowser","browserName","name","toLowerCase","browserTypeMatch","find","browserType","os","formatOs","osName","osTypeMatch","osType","getVisitorMatchesAudience","audience","visitorParams","Object","entries","every","paramKey","paramValue","getRandomTestValue","options","first","length","slug","value","Math","random","reduce","acc","split","match","VISITOR_ID_PREFIX","ANALYTICS_PATH","delay","ms","Promise","resolve","setTimeout","REASON_MESSAGES","timeout","network","unauthorized","server","client","ImproveFetchError","Error","reason","status","retryAfterMs","cause","isRetryable","getReasonFromStatus","CHAR_SET","normalizeConfig","body","Array","isArray","raw","version","tests","flags","parseRetryAfterMs","header","now","Date","seconds","Number","isFinite","max","round","date","parse","isNaN","getBackoffDelayMs","attempt","baseDelay","maxDelay","min","getTimeoutError","controller","abort","timeoutFetch","url","AbortController","race","fetch","signal","BaseImproveSDK","organizationId","environment","state","config","fetchTimeout","baseUrl","configRetries","configRevalidate","disableWarnings","_fetchConfig","retries","revalidate","requestInit","next","lastError","res","aborted","ok","json","headers","get","loadConfig","generateVisitorId","getRandomString","characters","fill","charAt","floor","toUpperCase","join","getVisitorCookieName","_warn","message","console","warn","validateTestValue","testName","testValue","testConfig","Boolean","option","validateVisitorId","possibleVisitorId","visitorIdParts","key","_baseUrl"],"mappings":"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"}
|
package/dist/client.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,"__esModule",{value:!0});var t=require("@obelism/numbers-sdk"),i=require("./base-
|
|
1
|
+
Object.defineProperty(exports,"__esModule",{value:!0});var t=require("@obelism/numbers-sdk"),i=require("./base-gi7y83ve.js");let e=t=>{if(!t)return!1;let i=document.cookie.split("; ").find(i=>{let[e]=i.split("=");return t===e});return!!i&&i.split("=")[1]},s=(t,i)=>{let e=new Date;e.setDate(e.getDate()+30),document.cookie=`${t}=${i};path=/;expires=${e.toUTCString()};SameSite=Lax;Secure`};class r extends i.BaseImproveSDK{#t;#i;#e;#s;#r;#n;constructor(r){super(r),this.#i="",this.#e={},this.#r=!1,this.#n=!0,this.fetchConfig=this._fetchConfig,this.setupVisitor=(t=window.navigator.userAgent)=>{let e=this.#s.visitor();if(!e)return null;this.#i=e.id;let s=i.parseUserAgent(t);if(!s)return null;this.#t=s;let{pointer:r,device:n,browser:o,os:a}=s;return this.#s.setDevice({pointer:r,device:n,browser:o,os:a}),this.#i},this.getFlagValue=t=>{if(!this.config)return null;let r=this.config.flags[t];if(!r||!r.options[0])return null;if(this.#t||this.setupVisitor(),!this.#i||!this.#t)return r.options[0].slug;if(this.#t?.[t])return this.#t[t];let n=e(t);if(n&&r.options.some(t=>t.slug===n))return this.#t[t]=n,this.#o("flag",r.id,n),n;if(!i.getVisitorMatchesAudience(this.config.audience[r.audience],this.#t))return r.options[0].slug;let o=i.getRandomTestValue(r.options);return o?(this.#t[t]=o,s(t,o),this.#o("flag",r.id,o),o):null},this.getTestValue=t=>{if(!this.config)return null;let r=this.config.tests[t];if(!r)return null;if(this.#t||this.setupVisitor(),!this.#i||!this.#t)return r.defaultValue;if(this.#t?.[t])return this.#t[t];let n=e(t);if(n&&this.validateTestValue(t,n))return this.#t[t]=n,this.#o("test",r.id,n),n;if(!i.getVisitorMatchesAudience(this.config.audience[r.audience],this.#t))return r.defaultValue;if(r.allocation<100&&100*Math.random()>r.allocation)return this.#t[t]=r.defaultValue,this.#t?.[t];let o=i.getRandomTestValue(r.options);return o?(this.#t[t]=o,s(t,o),this.#o("test",r.id,o),o):null},this.setAnalyticsUrls=t=>{this.#s.setEndpoint(t)},this.#o=(t,i,e)=>{if(this.#t&&this.#i)return this.#s.expose({subjectKind:t,subjectId:i,variant:e})},this.postAnalytic=(t,i,e)=>{if(!this.config||t.startsWith("gtm."))return null;i||this.#r||(this.#r=!0,this._warn(`postAnalytic("${t}", ...) was called with an empty scope. scope is required — pass a short, stable identifier for where/what this event refers to, e.g. "homepage_hero_login".`));let{value:s,currency:r,params:n,dedupeKey:o}=e??{},a="number"==typeof s&&Number.isFinite(s),u=n&&"object"==typeof n&&!Array.isArray(n)?n:void 0;this.#t||this.setupVisitor();let l=o?`${t}:${o}`:t;if(!this.#t||this.#e[l])return null;let h=this.#s.track(t,i,{...a?{value:s}:{},...r?{currency:r}:{},...u?{params:u}:{}});if(!h)return null;if(this.#e[l]=!0,this.#n){var d;u&&"ecommerce"in u&&"u">typeof window&&(window.dataLayer=window.dataLayer||[],window.dataLayer.push({ecommerce:null})),d={event:t,scope:i,improve:{visitorId:this.#i},...a?{value:s}:{},...r?{currency:r}:{},...u,_improve:!0},"u">typeof window&&(window.dataLayer=window.dataLayer||[],window.dataLayer.push(d))}return h},this.#s=t.createNumbers({organizationId:this.organizationId,environment:this.environment,endpoint:r.baseUrl?`${r.baseUrl}${i.ANALYTICS_PATH}`:t.DEFAULT_ENDPOINT,disableWarnings:r.disableWarnings??!1}),this.#n=r.dataLayer??!0}#o}exports.ImproveClientSDK=r;
|
|
2
2
|
//# sourceMappingURL=client.cjs.map
|
package/dist/client.d.ts
CHANGED
|
@@ -16,13 +16,13 @@ type CreateEvent = EventBeacon;
|
|
|
16
16
|
type CreateExposure = ExposureBeacon;
|
|
17
17
|
declare class ImproveClientSDK extends BaseImproveSDK {
|
|
18
18
|
#private;
|
|
19
|
-
fetchConfig: (config?: RequestInit) => Promise<ImproveConfiguration>;
|
|
19
|
+
fetchConfig: (config?: RequestInit) => Promise<ImproveConfiguration | undefined>;
|
|
20
20
|
constructor(args: ImproveSetupArgs);
|
|
21
|
-
setupVisitor: (userAgent?: string) => string;
|
|
22
|
-
getFlagValue: (flagSlug: string) => string;
|
|
23
|
-
getTestValue: (testSlug: string) => string;
|
|
21
|
+
setupVisitor: (userAgent?: string) => string | null;
|
|
22
|
+
getFlagValue: (flagSlug: string) => string | null;
|
|
23
|
+
getTestValue: (testSlug: string) => string | null;
|
|
24
24
|
setAnalyticsUrls: (url: string) => void;
|
|
25
|
-
postAnalytic: (event: ImproveEventName, scope: string, payload?: ImproveAnalyticPayload) => _obelism_numbers_sdk.Sent;
|
|
25
|
+
postAnalytic: (event: ImproveEventName, scope: string, payload?: ImproveAnalyticPayload) => _obelism_numbers_sdk.Sent | null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export { ImproveClientSDK };
|
package/dist/client.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createNumbers as i,DEFAULT_ENDPOINT as t}from"@obelism/numbers-sdk";import{B as s,p as e,g as r,a as o,A as n}from"./base-
|
|
1
|
+
import{createNumbers as i,DEFAULT_ENDPOINT as t}from"@obelism/numbers-sdk";import{B as s,p as e,g as r,a as o,A as n}from"./base-i6scrpy2.mjs";let a=i=>{if(!i)return!1;let t=document.cookie.split("; ").find(t=>{let[s]=t.split("=");return i===s});return!!t&&t.split("=")[1]},l=(i,t)=>{let s=new Date;s.setDate(s.getDate()+30),document.cookie=`${i}=${t};path=/;expires=${s.toUTCString()};SameSite=Lax;Secure`};class u extends s{#i;#t;#s;#e;#r;#o;constructor(s){super(s),this.#t="",this.#s={},this.#r=!1,this.#o=!0,this.fetchConfig=this._fetchConfig,this.setupVisitor=(i=window.navigator.userAgent)=>{let t=this.#e.visitor();if(!t)return null;this.#t=t.id;let s=e(i);if(!s)return null;this.#i=s;let{pointer:r,device:o,browser:n,os:a}=s;return this.#e.setDevice({pointer:r,device:o,browser:n,os:a}),this.#t},this.getFlagValue=i=>{if(!this.config)return null;let t=this.config.flags[i];if(!t||!t.options[0])return null;if(this.#i||this.setupVisitor(),!this.#t||!this.#i)return t.options[0].slug;if(this.#i?.[i])return this.#i[i];let s=a(i);if(s&&t.options.some(i=>i.slug===s))return this.#i[i]=s,this.#n("flag",t.id,s),s;if(!r(this.config.audience[t.audience],this.#i))return t.options[0].slug;let e=o(t.options);return e?(this.#i[i]=e,l(i,e),this.#n("flag",t.id,e),e):null},this.getTestValue=i=>{if(!this.config)return null;let t=this.config.tests[i];if(!t)return null;if(this.#i||this.setupVisitor(),!this.#t||!this.#i)return t.defaultValue;if(this.#i?.[i])return this.#i[i];let s=a(i);if(s&&this.validateTestValue(i,s))return this.#i[i]=s,this.#n("test",t.id,s),s;if(!r(this.config.audience[t.audience],this.#i))return t.defaultValue;if(t.allocation<100&&100*Math.random()>t.allocation)return this.#i[i]=t.defaultValue,this.#i?.[i];let e=o(t.options);return e?(this.#i[i]=e,l(i,e),this.#n("test",t.id,e),e):null},this.setAnalyticsUrls=i=>{this.#e.setEndpoint(i)},this.#n=(i,t,s)=>{if(this.#i&&this.#t)return this.#e.expose({subjectKind:i,subjectId:t,variant:s})},this.postAnalytic=(i,t,s)=>{if(!this.config||i.startsWith("gtm."))return null;t||this.#r||(this.#r=!0,this._warn(`postAnalytic("${i}", ...) was called with an empty scope. scope is required — pass a short, stable identifier for where/what this event refers to, e.g. "homepage_hero_login".`));let{value:e,currency:r,params:o,dedupeKey:n}=s??{},a="number"==typeof e&&Number.isFinite(e),l=o&&"object"==typeof o&&!Array.isArray(o)?o:void 0;this.#i||this.setupVisitor();let u=n?`${i}:${n}`:i;if(!this.#i||this.#s[u])return null;let h=this.#e.track(i,t,{...a?{value:e}:{},...r?{currency:r}:{},...l?{params:l}:{}});if(!h)return null;if(this.#s[u]=!0,this.#o){var d;l&&"ecommerce"in l&&"u">typeof window&&(window.dataLayer=window.dataLayer||[],window.dataLayer.push({ecommerce:null})),d={event:i,scope:t,improve:{visitorId:this.#t},...a?{value:e}:{},...r?{currency:r}:{},...l,_improve:!0},"u">typeof window&&(window.dataLayer=window.dataLayer||[],window.dataLayer.push(d))}return h},this.#e=i({organizationId:this.organizationId,environment:this.environment,endpoint:s.baseUrl?`${s.baseUrl}${n}`:t,disableWarnings:s.disableWarnings??!1}),this.#o=s.dataLayer??!0}#n}export{u as ImproveClientSDK};
|
|
2
2
|
//# sourceMappingURL=client.mjs.map
|
package/dist/server.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,"__esModule",{value:!0});var t=require("./base-
|
|
1
|
+
Object.defineProperty(exports,"__esModule",{value:!0});var t=require("./base-gi7y83ve.js");class e extends t.BaseImproveSDK{#t;#e;#i;constructor({token:e,maxVisitors:i,...s}){super(s),this.#t=new Map,this.fetchConfig=async t=>this._fetchConfig({...t,headers:{...t?.headers,token:this.#i}}),this.getFlagConfig=t=>this.config?.flags?.[t],this.getTestConfig=t=>this.config?.tests?.[t],this.#s=(e,i)=>{let s=this.#t.get(e);if(s)this.#t.delete(e),this.#t.set(e,s);else{if(this.#t.size>=this.#e){let t=this.#t.keys().next().value;t&&this.#t.delete(t)}s={},this.#t.set(e,s)}let r=s[i]||t.parseUserAgent(i);return r?(s[i]=r,r):null},this.getFlagValue=(e,i,s,r)=>{let n=this.getFlagConfig(e);if(!n||!this.config)return null;let o=n.options[0]?.slug??null;if(!i)return o;let l=this.#s(i,s);if(!l)return o;r?.country&&(l.country=r.country);let u=l[e];if(u)return u;if(!t.getVisitorMatchesAudience(this.config.audience[n.audience],l))return o;let a=t.getRandomTestValue(n.options);return a?(l[e]=a,a):null},this.getTestValue=(e,i,s,r)=>{let n=this.getTestConfig(e);if(!n||!this.config)return null;if(!i||!s)return n.defaultValue;let o=this.#s(i,s);if(!o)return n.defaultValue;r?.country&&(o.country=r.country);let l=o[e];if(l)return l;if(!t.getVisitorMatchesAudience(this.config.audience[n.audience],o))return n.defaultValue;if(n.allocation<100&&100*Math.random()>n.allocation)return o[e]=n.defaultValue,n.defaultValue;let u=t.getRandomTestValue(n.options);return u?(o[e]=u,u):null},this.#i=e,this.#e=i??1e4}#s}exports.ImproveServerSDK=e;
|
|
2
2
|
//# sourceMappingURL=server.cjs.map
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","sources":["../src/server.ts"],"sourcesContent":["import { ParsedUserAgent, parseUserAgent } from './utils/parseUserAgent'\nimport { getVisitorMatchesAudience } from './utils/getVisitorMatchesAudience'\nimport { getRandomTestValue } from './utils/getRandomTestValue'\nimport { BaseImproveSDK } from './base'\nimport { ImproveConfiguration, ImproveSetupArgs } from './types'\n\nconst DEFAULT_MAX_VISITORS = 10_000\n\ntype
|
|
1
|
+
{"version":3,"file":"server.cjs","sources":["../src/server.ts"],"sourcesContent":["import { ParsedUserAgent, parseUserAgent } from './utils/parseUserAgent'\nimport { getVisitorMatchesAudience } from './utils/getVisitorMatchesAudience'\nimport { getRandomTestValue } from './utils/getRandomTestValue'\nimport { BaseImproveSDK } from './base'\nimport { ImproveConfiguration, ImproveSetupArgs } from './types'\n\nconst DEFAULT_MAX_VISITORS = 10_000\n\ntype Visitor = ParsedUserAgent & {\n\t[testSlug: string]: string\n}\n\ntype VisitorData = {\n\t[userAgent: string]: Visitor\n}\n\ntype ImproveServerSetupArgs =\n\t| (Omit<ImproveSetupArgs, 'config' | 'baseUrl'> & {\n\t\t\tconfig: ImproveConfiguration\n\t\t\tmaxVisitors?: number\n\t })\n\t| (Omit<ImproveSetupArgs, 'config'> & {\n\t\t\ttoken: string\n\t\t\tmaxVisitors?: number\n\t })\n\nexport class ImproveServerSDK extends BaseImproveSDK {\n\t#visitors: Map<string, VisitorData> = new Map()\n\t#maxVisitors: number\n\t#token: string\n\n\t// @ts-ignore It could be there\n\tconstructor({ token, maxVisitors, ...args }: ImproveServerSetupArgs) {\n\t\tsuper(args)\n\t\tthis.#token = token\n\t\tthis.#maxVisitors = maxVisitors ?? DEFAULT_MAX_VISITORS\n\t}\n\n\tfetchConfig = async (config?: RequestInit) => {\n\t\treturn this._fetchConfig({\n\t\t\t...config,\n\t\t\theaders: {\n\t\t\t\t...config?.headers,\n\t\t\t\ttoken: this.#token,\n\t\t\t},\n\t\t})\n\t}\n\n\tgetFlagConfig = (flagSlug: string) => this.config?.flags?.[flagSlug]\n\n\tgetTestConfig = (testSlug: string) => this.config?.tests?.[testSlug]\n\n\t// The visitor as seen with this user agent, or null when it can't be\n\t// parsed (an empty user agent), which gets the default like a holdout.\n\t#getVisitor = (visitorId: string, userAgent: string): Visitor | null => {\n\t\tlet visitor = this.#visitors.get(visitorId)\n\t\tif (visitor) {\n\t\t\t// Move to end for LRU freshness\n\t\t\tthis.#visitors.delete(visitorId)\n\t\t\tthis.#visitors.set(visitorId, visitor)\n\t\t} else {\n\t\t\t// Evict oldest entries when at capacity\n\t\t\tif (this.#visitors.size >= this.#maxVisitors) {\n\t\t\t\tconst oldest = this.#visitors.keys().next().value\n\t\t\t\tif (oldest) this.#visitors.delete(oldest)\n\t\t\t}\n\t\t\tvisitor = {}\n\t\t\tthis.#visitors.set(visitorId, visitor)\n\t\t}\n\t\tconst parsed = visitor[userAgent] || parseUserAgent(userAgent)\n\t\tif (!parsed) return null\n\t\tvisitor[userAgent] = parsed\n\t\treturn parsed\n\t}\n\n\tgetFlagValue = (\n\t\tflagSlug: string,\n\t\tvisitorId: string,\n\t\tuserAgent: string,\n\t\t// Coarse geo from the request (SSR/middleware), so audiences can target by\n\t\t// country. The browser can't determine this, so it's passed in here.\n\t\tgeo?: { country?: string | undefined },\n\t) => {\n\t\tconst flagConfig = this.getFlagConfig(flagSlug)\n\n\t\tif (!flagConfig || !this.config) return null\n\n\t\tconst defaultValue = flagConfig.options[0]?.slug ?? null\n\t\tif (!visitorId) return defaultValue\n\n\t\tconst visitor = this.#getVisitor(visitorId, userAgent)\n\t\tif (!visitor) return defaultValue\n\t\tif (geo?.country) visitor.country = geo.country\n\n\t\tconst assigned = visitor[flagSlug]\n\t\tif (assigned) return assigned\n\n\t\tconst visitorMatchesAudience = getVisitorMatchesAudience(\n\t\t\tthis.config.audience[flagConfig.audience],\n\t\t\tvisitor,\n\t\t)\n\n\t\tif (!visitorMatchesAudience) return defaultValue\n\n\t\tconst flagValue = getRandomTestValue(flagConfig.options)\n\n\t\tif (!flagValue) return null\n\n\t\tvisitor[flagSlug] = flagValue\n\t\treturn flagValue\n\t}\n\n\tgetTestValue = (\n\t\ttestSlug: string,\n\t\tvisitorId: string,\n\t\tuserAgent: string,\n\t\t// Coarse geo from the request (SSR/middleware), so audiences can target by\n\t\t// country. The browser can't determine this, so it's passed in here.\n\t\tgeo?: { country?: string | undefined },\n\t) => {\n\t\tconst testConfig = this.getTestConfig(testSlug)\n\n\t\tif (!testConfig || !this.config) return null\n\n\t\tif (!visitorId || !userAgent) return testConfig.defaultValue\n\n\t\tconst visitor = this.#getVisitor(visitorId, userAgent)\n\t\tif (!visitor) return testConfig.defaultValue\n\t\tif (geo?.country) visitor.country = geo.country\n\n\t\tconst assigned = visitor[testSlug]\n\t\tif (assigned) return assigned\n\n\t\tconst visitorMatchesAudience = getVisitorMatchesAudience(\n\t\t\tthis.config.audience[testConfig.audience],\n\t\t\tvisitor,\n\t\t)\n\n\t\tif (!visitorMatchesAudience) return testConfig.defaultValue\n\n\t\tif (\n\t\t\ttestConfig.allocation < 100 &&\n\t\t\tMath.random() * 100 > testConfig.allocation\n\t\t) {\n\t\t\tvisitor[testSlug] = testConfig.defaultValue\n\t\t\treturn testConfig.defaultValue\n\t\t}\n\n\t\tconst testValue = getRandomTestValue(testConfig.options)\n\n\t\tif (!testValue) return null\n\n\t\tvisitor[testSlug] = testValue\n\t\treturn testValue\n\t}\n}\n"],"names":["ImproveServerSDK","BaseImproveSDK","token","maxVisitors","args","Map","fetchConfig","config","_fetchConfig","headers","getFlagConfig","flagSlug","flags","getTestConfig","testSlug","tests","visitorId","userAgent","visitor","get","delete","set","size","oldest","keys","next","value","parsed","parseUserAgent","getFlagValue","geo","flagConfig","defaultValue","options","slug","country","assigned","getVisitorMatchesAudience","audience","flagValue","getRandomTestValue","getTestValue","testConfig","allocation","Math","random","testValue"],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -10,15 +10,15 @@ type ImproveServerSetupArgs = (Omit<ImproveSetupArgs, 'config' | 'baseUrl'> & {
|
|
|
10
10
|
declare class ImproveServerSDK extends BaseImproveSDK {
|
|
11
11
|
#private;
|
|
12
12
|
constructor({ token, maxVisitors, ...args }: ImproveServerSetupArgs);
|
|
13
|
-
fetchConfig: (config?: RequestInit) => Promise<ImproveConfiguration>;
|
|
14
|
-
getFlagConfig: (flagSlug: string) => ImproveFlag;
|
|
15
|
-
getTestConfig: (testSlug: string) => ImproveTest;
|
|
13
|
+
fetchConfig: (config?: RequestInit) => Promise<ImproveConfiguration | undefined>;
|
|
14
|
+
getFlagConfig: (flagSlug: string) => ImproveFlag | undefined;
|
|
15
|
+
getTestConfig: (testSlug: string) => ImproveTest | undefined;
|
|
16
16
|
getFlagValue: (flagSlug: string, visitorId: string, userAgent: string, geo?: {
|
|
17
|
-
country?: string;
|
|
18
|
-
}) => string;
|
|
17
|
+
country?: string | undefined;
|
|
18
|
+
}) => string | null;
|
|
19
19
|
getTestValue: (testSlug: string, visitorId: string, userAgent: string, geo?: {
|
|
20
|
-
country?: string;
|
|
21
|
-
}) => string;
|
|
20
|
+
country?: string | undefined;
|
|
21
|
+
}) => string | null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export { ImproveServerSDK };
|
package/dist/server.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{B as t,p as i,g as
|
|
1
|
+
import{B as t,p as i,g as e,a as s}from"./base-i6scrpy2.mjs";class r extends t{#t;#i;#e;constructor({token:t,maxVisitors:r,...n}){super(n),this.#t=new Map,this.fetchConfig=async t=>this._fetchConfig({...t,headers:{...t?.headers,token:this.#e}}),this.getFlagConfig=t=>this.config?.flags?.[t],this.getTestConfig=t=>this.config?.tests?.[t],this.#s=(t,e)=>{let s=this.#t.get(t);if(s)this.#t.delete(t),this.#t.set(t,s);else{if(this.#t.size>=this.#i){let t=this.#t.keys().next().value;t&&this.#t.delete(t)}s={},this.#t.set(t,s)}let r=s[e]||i(e);return r?(s[e]=r,r):null},this.getFlagValue=(t,i,r,n)=>{let o=this.getFlagConfig(t);if(!o||!this.config)return null;let l=o.options[0]?.slug??null;if(!i)return l;let u=this.#s(i,r);if(!u)return l;n?.country&&(u.country=n.country);let a=u[t];if(a)return a;if(!e(this.config.audience[o.audience],u))return l;let f=s(o.options);return f?(u[t]=f,f):null},this.getTestValue=(t,i,r,n)=>{let o=this.getTestConfig(t);if(!o||!this.config)return null;if(!i||!r)return o.defaultValue;let l=this.#s(i,r);if(!l)return o.defaultValue;n?.country&&(l.country=n.country);let u=l[t];if(u)return u;if(!e(this.config.audience[o.audience],l))return o.defaultValue;if(o.allocation<100&&100*Math.random()>o.allocation)return l[t]=o.defaultValue,o.defaultValue;let a=s(o.options);return a?(l[t]=a,a):null},this.#e=t,this.#i=r??1e4}#s}export{r as ImproveServerSDK};
|
|
2
2
|
//# sourceMappingURL=server.mjs.map
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","sources":["../src/server.ts"],"sourcesContent":["import { ParsedUserAgent, parseUserAgent } from './utils/parseUserAgent'\nimport { getVisitorMatchesAudience } from './utils/getVisitorMatchesAudience'\nimport { getRandomTestValue } from './utils/getRandomTestValue'\nimport { BaseImproveSDK } from './base'\nimport { ImproveConfiguration, ImproveSetupArgs } from './types'\n\nconst DEFAULT_MAX_VISITORS = 10_000\n\ntype
|
|
1
|
+
{"version":3,"file":"server.mjs","sources":["../src/server.ts"],"sourcesContent":["import { ParsedUserAgent, parseUserAgent } from './utils/parseUserAgent'\nimport { getVisitorMatchesAudience } from './utils/getVisitorMatchesAudience'\nimport { getRandomTestValue } from './utils/getRandomTestValue'\nimport { BaseImproveSDK } from './base'\nimport { ImproveConfiguration, ImproveSetupArgs } from './types'\n\nconst DEFAULT_MAX_VISITORS = 10_000\n\ntype Visitor = ParsedUserAgent & {\n\t[testSlug: string]: string\n}\n\ntype VisitorData = {\n\t[userAgent: string]: Visitor\n}\n\ntype ImproveServerSetupArgs =\n\t| (Omit<ImproveSetupArgs, 'config' | 'baseUrl'> & {\n\t\t\tconfig: ImproveConfiguration\n\t\t\tmaxVisitors?: number\n\t })\n\t| (Omit<ImproveSetupArgs, 'config'> & {\n\t\t\ttoken: string\n\t\t\tmaxVisitors?: number\n\t })\n\nexport class ImproveServerSDK extends BaseImproveSDK {\n\t#visitors: Map<string, VisitorData> = new Map()\n\t#maxVisitors: number\n\t#token: string\n\n\t// @ts-ignore It could be there\n\tconstructor({ token, maxVisitors, ...args }: ImproveServerSetupArgs) {\n\t\tsuper(args)\n\t\tthis.#token = token\n\t\tthis.#maxVisitors = maxVisitors ?? DEFAULT_MAX_VISITORS\n\t}\n\n\tfetchConfig = async (config?: RequestInit) => {\n\t\treturn this._fetchConfig({\n\t\t\t...config,\n\t\t\theaders: {\n\t\t\t\t...config?.headers,\n\t\t\t\ttoken: this.#token,\n\t\t\t},\n\t\t})\n\t}\n\n\tgetFlagConfig = (flagSlug: string) => this.config?.flags?.[flagSlug]\n\n\tgetTestConfig = (testSlug: string) => this.config?.tests?.[testSlug]\n\n\t// The visitor as seen with this user agent, or null when it can't be\n\t// parsed (an empty user agent), which gets the default like a holdout.\n\t#getVisitor = (visitorId: string, userAgent: string): Visitor | null => {\n\t\tlet visitor = this.#visitors.get(visitorId)\n\t\tif (visitor) {\n\t\t\t// Move to end for LRU freshness\n\t\t\tthis.#visitors.delete(visitorId)\n\t\t\tthis.#visitors.set(visitorId, visitor)\n\t\t} else {\n\t\t\t// Evict oldest entries when at capacity\n\t\t\tif (this.#visitors.size >= this.#maxVisitors) {\n\t\t\t\tconst oldest = this.#visitors.keys().next().value\n\t\t\t\tif (oldest) this.#visitors.delete(oldest)\n\t\t\t}\n\t\t\tvisitor = {}\n\t\t\tthis.#visitors.set(visitorId, visitor)\n\t\t}\n\t\tconst parsed = visitor[userAgent] || parseUserAgent(userAgent)\n\t\tif (!parsed) return null\n\t\tvisitor[userAgent] = parsed\n\t\treturn parsed\n\t}\n\n\tgetFlagValue = (\n\t\tflagSlug: string,\n\t\tvisitorId: string,\n\t\tuserAgent: string,\n\t\t// Coarse geo from the request (SSR/middleware), so audiences can target by\n\t\t// country. The browser can't determine this, so it's passed in here.\n\t\tgeo?: { country?: string | undefined },\n\t) => {\n\t\tconst flagConfig = this.getFlagConfig(flagSlug)\n\n\t\tif (!flagConfig || !this.config) return null\n\n\t\tconst defaultValue = flagConfig.options[0]?.slug ?? null\n\t\tif (!visitorId) return defaultValue\n\n\t\tconst visitor = this.#getVisitor(visitorId, userAgent)\n\t\tif (!visitor) return defaultValue\n\t\tif (geo?.country) visitor.country = geo.country\n\n\t\tconst assigned = visitor[flagSlug]\n\t\tif (assigned) return assigned\n\n\t\tconst visitorMatchesAudience = getVisitorMatchesAudience(\n\t\t\tthis.config.audience[flagConfig.audience],\n\t\t\tvisitor,\n\t\t)\n\n\t\tif (!visitorMatchesAudience) return defaultValue\n\n\t\tconst flagValue = getRandomTestValue(flagConfig.options)\n\n\t\tif (!flagValue) return null\n\n\t\tvisitor[flagSlug] = flagValue\n\t\treturn flagValue\n\t}\n\n\tgetTestValue = (\n\t\ttestSlug: string,\n\t\tvisitorId: string,\n\t\tuserAgent: string,\n\t\t// Coarse geo from the request (SSR/middleware), so audiences can target by\n\t\t// country. The browser can't determine this, so it's passed in here.\n\t\tgeo?: { country?: string | undefined },\n\t) => {\n\t\tconst testConfig = this.getTestConfig(testSlug)\n\n\t\tif (!testConfig || !this.config) return null\n\n\t\tif (!visitorId || !userAgent) return testConfig.defaultValue\n\n\t\tconst visitor = this.#getVisitor(visitorId, userAgent)\n\t\tif (!visitor) return testConfig.defaultValue\n\t\tif (geo?.country) visitor.country = geo.country\n\n\t\tconst assigned = visitor[testSlug]\n\t\tif (assigned) return assigned\n\n\t\tconst visitorMatchesAudience = getVisitorMatchesAudience(\n\t\t\tthis.config.audience[testConfig.audience],\n\t\t\tvisitor,\n\t\t)\n\n\t\tif (!visitorMatchesAudience) return testConfig.defaultValue\n\n\t\tif (\n\t\t\ttestConfig.allocation < 100 &&\n\t\t\tMath.random() * 100 > testConfig.allocation\n\t\t) {\n\t\t\tvisitor[testSlug] = testConfig.defaultValue\n\t\t\treturn testConfig.defaultValue\n\t\t}\n\n\t\tconst testValue = getRandomTestValue(testConfig.options)\n\n\t\tif (!testValue) return null\n\n\t\tvisitor[testSlug] = testValue\n\t\treturn testValue\n\t}\n}\n"],"names":["ImproveServerSDK","BaseImproveSDK","token","maxVisitors","args","Map","fetchConfig","config","_fetchConfig","headers","getFlagConfig","flagSlug","flags","getTestConfig","testSlug","tests","visitorId","userAgent","visitor","get","delete","set","size","oldest","keys","next","value","parsed","parseUserAgent","getFlagValue","geo","flagConfig","defaultValue","options","slug","country","assigned","getVisitorMatchesAudience","audience","flagValue","getRandomTestValue","getTestValue","testConfig","allocation","Math","random","testValue"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -15,11 +15,11 @@ declare class BaseImproveSDK {
|
|
|
15
15
|
#private;
|
|
16
16
|
organizationId: string;
|
|
17
17
|
environment: ImproveEnvironmentOption;
|
|
18
|
-
state: ImproveTestState;
|
|
18
|
+
state: ImproveTestState | undefined;
|
|
19
19
|
config: ImproveConfiguration | null;
|
|
20
20
|
_baseUrl: undefined | string;
|
|
21
21
|
constructor({ organizationId, environment, state, config, fetchTimeout, baseUrl, configRetries, configRevalidate, disableWarnings, }: ImproveSetupArgs);
|
|
22
|
-
_fetchConfig: (config?: RequestInit) => Promise<ImproveConfiguration>;
|
|
22
|
+
_fetchConfig: (config?: RequestInit) => Promise<ImproveConfiguration | undefined>;
|
|
23
23
|
loadConfig: (config: ImproveConfiguration) => void;
|
|
24
24
|
generateVisitorId: () => string;
|
|
25
25
|
getVisitorCookieName: () => string;
|
|
@@ -38,10 +38,10 @@ type ImproveTestState = 'draft' | 'active' | 'finished' | 'archived';
|
|
|
38
38
|
type ImproveSetupArgs = {
|
|
39
39
|
organizationId: string;
|
|
40
40
|
environment: ImproveEnvironmentOption;
|
|
41
|
-
state?: ImproveTestState;
|
|
42
|
-
config?: ImproveConfiguration;
|
|
43
|
-
baseUrl?: string;
|
|
44
|
-
fetchTimeout?: number;
|
|
41
|
+
state?: ImproveTestState | undefined;
|
|
42
|
+
config?: ImproveConfiguration | undefined;
|
|
43
|
+
baseUrl?: string | undefined;
|
|
44
|
+
fetchTimeout?: number | undefined;
|
|
45
45
|
/**
|
|
46
46
|
* How many times to retry a failed config fetch before giving up. Only
|
|
47
47
|
* transient failures are retried (network errors, timeouts, HTTP 5xx, and
|
|
@@ -50,7 +50,7 @@ type ImproveSetupArgs = {
|
|
|
50
50
|
* Defaults to 2. Set to 0 to disable retries (e.g. in latency-sensitive
|
|
51
51
|
* middleware).
|
|
52
52
|
*/
|
|
53
|
-
configRetries?: number;
|
|
53
|
+
configRetries?: number | undefined;
|
|
54
54
|
/**
|
|
55
55
|
* When set, forwarded to the config fetch as `next: { revalidate }`
|
|
56
56
|
* (seconds), so Next.js / edge runtimes cache the config datafile between
|
|
@@ -60,19 +60,19 @@ type ImproveSetupArgs = {
|
|
|
60
60
|
* under load. No effect outside fetch implementations that honor
|
|
61
61
|
* `next.revalidate`.
|
|
62
62
|
*/
|
|
63
|
-
configRevalidate?: number;
|
|
63
|
+
configRevalidate?: number | undefined;
|
|
64
64
|
/**
|
|
65
65
|
* Mirror analytics onto the GTM dataLayer (window.dataLayer) with experiment
|
|
66
66
|
* dimensions, so they can drive Google Tag Manager / Google Ads conversions.
|
|
67
67
|
* Enabled by default; set to `false` to opt out. No effect server-side.
|
|
68
68
|
*/
|
|
69
|
-
dataLayer?: boolean;
|
|
69
|
+
dataLayer?: boolean | undefined;
|
|
70
70
|
/**
|
|
71
71
|
* Silence all Improve development warnings (e.g. the nudge when an event name
|
|
72
72
|
* isn't `snake_case`). Warnings are on by default to steer teams toward the
|
|
73
73
|
* naming convention; set to `true` in production or to opt out entirely.
|
|
74
74
|
*/
|
|
75
|
-
disableWarnings?: boolean;
|
|
75
|
+
disableWarnings?: boolean | undefined;
|
|
76
76
|
};
|
|
77
77
|
type ImproveFlagOption = {
|
|
78
78
|
name: string;
|
package/package.json
CHANGED
package/dist/base-f9lw4y65.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{UAParser as e}from"ua-parser-js";let t=["wearable","mobile","tablet","console","smarttv","embedded","desktop"],r=["chrome","safari","firefox","edge","ie","samsung internet","social","other"],i=["mac os","ios","android","windows","unix"],n=["tiktok","wechat","weibo","snapchat","klarna","Line","instagram","facebook","alipay","Baidu"],s=["wearable","mobile","tablet"],o=o=>{var a;if(!o||"string"!=typeof o)return null;let u=new e(o).getResult(),l=(a=u.device.type)&&t.includes(a)?a:"desktop";return{pointer:s.includes(l)?"coarse":"fine",device:l,browser:((e="")=>{if(!e)return"other";let t=e.toLowerCase(),i=r.find(e=>t.includes(e));return i||(n.includes(t)?"social":"other")})(u.browser.name),os:((e="")=>{if(!e)return"unix";let t=e.toLowerCase(),r=i.find(e=>t.includes(e));return r||"unix"})(u.os.name)}},a=(e,t)=>!e||Object.entries(e).every(([e,r])=>t[e]===r),u=e=>{if(0===e.length)return null;if(1===e.length)return e[0].slug;let t=Math.random()*e.reduce((e,{split:t})=>e+t,0);return(e.find(({split:e})=>(t-=e)<=0)||e[0]).slug},l="visi",h="/api/log",c=e=>new Promise(t=>setTimeout(t,e)),f={timeout:"Configuration request timed out",network:"Configuration request could not reach the server",unauthorized:"Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment","rate-limited":"Configuration request was rate limited (429): too many requests, or the organization usage limit was reached",server:"Configuration request failed with a server error",client:"Configuration request was rejected by the server","invalid-response":"Configuration response could not be parsed as JSON"};class d extends Error{constructor(e,t){super(f[e]),this.name="ImproveFetchError",this.reason=e,this.status=t?.status,this.retryAfterMs=t?.retryAfterMs,t&&"cause"in t&&(this.cause=t.cause)}get isRetryable(){return"rate-limited"===this.reason||"server"===this.reason||"timeout"===this.reason||"network"===this.reason}}let g=e=>429===e?"rate-limited":401===e||403===e?"unauthorized":e>=500?"server":"client",m="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",w=e=>{if("object"!=typeof e||null===e||Array.isArray(e))throw new d("invalid-response");return{name:"string"==typeof e.name?e.name:"",version:"number"==typeof e.version?e.version:0,tests:e.tests??{},flags:e.flags??{},audience:e.audience??{}}},p=(e,t=Date.now())=>{if(!e)return;let r=Number(e);if(Number.isFinite(r))return Math.max(0,Math.round(1e3*r));let i=Date.parse(e);if(!Number.isNaN(i))return Math.max(0,i-t)},v=(e,t,r)=>Math.round(Math.random()*Math.min(r,t*2**e)),b=async(e=1e3,t)=>(await c(e),t?.abort(),null),y=(e=3e3,t,r)=>{let i=new AbortController;return Promise.race([fetch(t,{...r,signal:i.signal}),b(e,i)])};class C{#e;#t;constructor({organizationId:e,environment:t,state:r,config:i,fetchTimeout:n,baseUrl:s,configRetries:o,configRevalidate:a,disableWarnings:u}){this.environment="develop",this.#e=null,this.#t=!1,this.config=null,this._fetchConfig=async e=>{if(this.config)return;if(!this.#e)throw Error("No config fetch setup provided");let{url:t,timeout:r,retries:i,revalidate:n}=this.#e,s={...e};"number"==typeof n&&(s.next={...e?.next,revalidate:n});let o=new d("network");for(let e=0;e<=i;e++){let i;e>0&&await c(o.retryAfterMs??v(e-1,300,3e3));try{i=await y(r,t,s)}catch(e){o=new d(e instanceof Error&&"AbortError"===e.name?"timeout":"network",{cause:e});continue}if(!i){o=new d("timeout");continue}if(i.ok){try{this.config=w(await i.json())}catch(e){o=e instanceof d?e:new d("invalid-response",{cause:e});continue}return this.config}if(!(o=new d(g(i.status),{status:i.status,retryAfterMs:p(i.headers.get("Retry-After"))})).isRetryable)break}throw o},this.loadConfig=e=>{this.config=e},this.generateVisitorId=()=>[l,(function(e=5){return e&&"number"==typeof e?Array(e).fill("").reduce(e=>e+=m.charAt(Math.floor(Math.random()*m.length)),""):""})(26).toUpperCase()].join("_"),this.getVisitorCookieName=()=>"visitorId",this._warn=e=>{this.#t||"u">typeof console&&console.warn(`[Improve] ${e}`)},this.validateTestValue=(e,t)=>{if(!this.config)throw Error("Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup");let r=this.config.tests[e];if(!r)throw Error(`No config found for ${e}`);return!!r.options.find(e=>e.slug===t)},this.validateVisitorId=e=>{let t=e.split("_");if(2!==t.length)return!1;let[r,i]=t;return r===l&&26===i.length},this.organizationId=e,this.environment=t,this.state=r,this._baseUrl=s||"https://improve.obelism.studio",this.#t=u??!1,i?this.config=i:this.#e={url:[`${this._baseUrl}/config`,this.organizationId,this.environment,this.state||"active"].join("/"),timeout:n||3e3,retries:o??2,revalidate:a}}}export{h as A,C as B,u as a,a as g,o as p};
|
|
2
|
-
//# sourceMappingURL=base-f9lw4y65.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"base-f9lw4y65.mjs","sources":["../src/config/audiences.ts","../src/utils/parseUserAgent.ts","../src/utils/getVisitorMatchesAudience.ts","../src/utils/getRandomTestValue.ts","../src/config/constants.ts","../src/config/urls.ts","../src/utils/delay.ts","../src/utils/errors.ts","../src/utils/getRandomString.ts","../src/utils/normalizeConfig.ts","../src/utils/retry.ts","../src/utils/getTimeoutError.ts","../src/utils/timeoutFetch.ts","../src/base.ts"],"sourcesContent":["/**\n * @constant AUDIENCE_PARAMS\n * @description All posibile audience tracking options\n */\nexport const AUDIENCE_PARAMS = {\n\t//? Location — coarse IP-geolocation (ISO 3166-1 alpha-2 country code). Values\n\t//? aren't enumerated here (any country code is valid); the key exists so\n\t//? audiences can target `country`. Resolved server-side (SSR/middleware) from\n\t//? the request, since the browser can't determine it from the user agent.\n\tcountry: [] as ReadonlyArray<string>,\n\t//? Technical\n\tpointer: ['coarse', 'fine'],\n\tdevice: [\n\t\t'wearable',\n\t\t'mobile',\n\t\t'tablet',\n\t\t'console',\n\t\t'smarttv',\n\t\t'embedded',\n\t\t'desktop',\n\t],\n\tbrowser: [\n\t\t'chrome',\n\t\t'safari',\n\t\t'firefox',\n\t\t'edge',\n\t\t'ie',\n\t\t'samsung internet',\n\t\t'social',\n\t\t'other',\n\t],\n\tos: ['mac os', 'ios', 'android', 'windows', 'unix'],\n} as const\n\n//? Generated types\nexport type AudienceParamKey = keyof typeof AUDIENCE_PARAMS\n\nexport type AudienceParamPointer = (typeof AUDIENCE_PARAMS.pointer)[number]\n\nexport type AudienceParamDevice = (typeof AUDIENCE_PARAMS.device)[number]\n\nexport type AudienceParamBrowser = (typeof AUDIENCE_PARAMS.browser)[number]\n\nexport type AudienceParamOs = (typeof AUDIENCE_PARAMS.os)[number]\n\nexport const AUDIENCE_PARAM_KEYS = Object.keys(\n\tAUDIENCE_PARAMS,\n) as ReadonlyArray<AudienceParamKey>\n","import { UAParser } from 'ua-parser-js'\n\nimport {\n\tAUDIENCE_PARAMS,\n\tAudienceParamBrowser,\n\tAudienceParamDevice,\n\tAudienceParamOs,\n\tAudienceParamPointer,\n} from '../config/audiences'\n\nexport type ParsedUserAgent = {\n\tpointer: AudienceParamPointer\n\tdevice: AudienceParamDevice\n\tbrowser: AudienceParamBrowser\n\tos: AudienceParamOs\n\t// Coarse geo, not derived from the user agent — merged in server-side\n\t// (SSR/middleware) so audiences can target by country. Absent client-side.\n\tcountry?: string\n}\n\nconst formatDeviceType = (deviceType?: string): AudienceParamDevice => {\n\tif (\n\t\tdeviceType &&\n\t\tAUDIENCE_PARAMS.device.includes(deviceType as AudienceParamDevice)\n\t) {\n\t\treturn deviceType as AudienceParamDevice\n\t}\n\treturn 'desktop'\n}\n\nconst SOCIAL_BROWSERS: ReadonlyArray<string> = [\n\t'tiktok',\n\t'wechat',\n\t'weibo',\n\t'snapchat',\n\t'klarna',\n\t'Line',\n\t'instagram',\n\t'facebook',\n\t'alipay',\n\t'Baidu',\n]\n\nconst formatBrowser = (browserName: string = ''): AudienceParamBrowser => {\n\tif (!browserName) return 'other'\n\n\tconst name = browserName.toLowerCase()\n\n\tconst browserTypeMatch = AUDIENCE_PARAMS.browser.find((browserType) => {\n\t\treturn name.includes(browserType)\n\t})\n\n\tif (browserTypeMatch) return browserTypeMatch\n\n\tif (SOCIAL_BROWSERS.includes(name)) return 'social'\n\treturn 'other'\n}\n\nconst PRIMARY_TOUCH_DEVICES: ReadonlyArray<AudienceParamDevice> = [\n\t'wearable',\n\t'mobile',\n\t'tablet',\n]\n\nconst formatPointer = (device: AudienceParamDevice) => {\n\treturn PRIMARY_TOUCH_DEVICES.includes(device) ? 'coarse' : 'fine'\n}\n\nconst formatOs = (osName: string = ''): AudienceParamOs => {\n\tif (!osName) return 'unix'\n\n\tconst os = osName.toLowerCase()\n\n\tconst osTypeMatch = AUDIENCE_PARAMS.os.find((osType) => {\n\t\treturn os.includes(osType)\n\t})\n\n\tif (osTypeMatch) return osTypeMatch\n\treturn 'unix'\n}\n\nexport const parseUserAgent = (userAgent: string): ParsedUserAgent | null => {\n\tif (!userAgent || typeof userAgent !== 'string') return null\n\n\tconst parser = new UAParser(userAgent)\n\tconst results = parser.getResult()\n\n\tconst device = formatDeviceType(results.device.type)\n\n\treturn {\n\t\tpointer: formatPointer(device),\n\t\tdevice: device,\n\t\tbrowser: formatBrowser(results.browser.name),\n\t\tos: formatOs(results.os.name),\n\t}\n}\n","import { ImproveAudienceValue } from '../types'\nimport { AudienceParamKey } from '../config/audiences'\nimport { ParsedUserAgent } from './parseUserAgent'\n\nexport const getVisitorMatchesAudience = (\n\taudience: ImproveAudienceValue | undefined,\n\tvisitorParams: ParsedUserAgent,\n) => {\n\tif (!audience) return true\n\treturn Object.entries(audience).every(([paramKey, paramValue]) => {\n\t\treturn visitorParams[paramKey as AudienceParamKey] === paramValue\n\t})\n}\n","import { ImproveTestOption } from '../types'\n\n// function cryptoRandom() {\n// \treturn crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1)\n// }\n\nexport const getRandomTestValue = (options: ImproveTestOption[]) => {\n\tif (options.length === 0) return null\n\tif (options.length === 1) return options[0].slug\n\n\t// Get a random number between 0 and 1\n\tconst sum = options.reduce((acc, { split }) => acc + split, 0)\n\tlet value = Math.random() * sum\n\n\tconst match =\n\t\toptions.find(({ split }) => {\n\t\t\tvalue -= split\n\t\t\treturn value <= 0\n\t\t}) || options[0]\n\n\treturn match.slug\n}\n","export const COOKIE_NAME_VISITOR = 'visitorId'\n\nexport const VISITOR_ID_PREFIX = 'visi'\n\nexport const VISITOR_ID_LENGTH = 26\n\nexport const VISITOR_ID_SEPARATOR = '_'\n\n/** Default number of retries for a failed config fetch (network / 5xx / 429). */\nexport const CONFIG_RETRY_COUNT = 2\n\n/** Base delay for config-fetch backoff, in ms. */\nexport const CONFIG_RETRY_BASE_DELAY_MS = 300\n\n/** Upper bound for a single config-fetch backoff wait, in ms. */\nexport const CONFIG_RETRY_MAX_DELAY_MS = 3000\n","export const BASE_URL = 'https://improve.obelism.studio'\nexport const CONFIG_PATH = `/config`\n// Events go to Obelism numbers (the numbers SDK's default endpoint); only a\n// custom `baseUrl` sends them to its own /api/log.\nexport const ANALYTICS_PATH = `/api/log`\n","export const delay = (ms: number) =>\n\tnew Promise((resolve) => setTimeout(resolve, ms))\n","export type ImproveFetchErrorReason =\n\t| 'timeout'\n\t| 'network'\n\t| 'unauthorized'\n\t| 'rate-limited'\n\t| 'server'\n\t| 'client'\n\t| 'invalid-response'\n\nconst REASON_MESSAGES: Record<ImproveFetchErrorReason, string> = {\n\ttimeout: 'Configuration request timed out',\n\tnetwork: 'Configuration request could not reach the server',\n\tunauthorized:\n\t\t'Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment',\n\t'rate-limited':\n\t\t'Configuration request was rate limited (429): too many requests, or the organization usage limit was reached',\n\tserver: 'Configuration request failed with a server error',\n\tclient: 'Configuration request was rejected by the server',\n\t'invalid-response': 'Configuration response could not be parsed as JSON',\n}\n\n/**\n * Error thrown by the config fetch layer. Carries the HTTP `status`, a coarse\n * `reason`, and (when the server sent one) the `Retry-After` delay in ms, so\n * callers can distinguish a misconfiguration (unauthorized) from a transient\n * failure (rate-limited/server/timeout) instead of guessing from a string.\n */\nexport class ImproveFetchError extends Error {\n\treason: ImproveFetchErrorReason\n\tstatus?: number\n\tretryAfterMs?: number\n\n\tconstructor(\n\t\treason: ImproveFetchErrorReason,\n\t\toptions?: { status?: number; retryAfterMs?: number; cause?: unknown },\n\t) {\n\t\tsuper(REASON_MESSAGES[reason])\n\t\tthis.name = 'ImproveFetchError'\n\t\tthis.reason = reason\n\t\tthis.status = options?.status\n\t\tthis.retryAfterMs = options?.retryAfterMs\n\t\tif (options && 'cause' in options) {\n\t\t\t;(this as { cause?: unknown }).cause = options.cause\n\t\t}\n\t}\n\n\t/** Whether retrying the request could plausibly succeed. */\n\tget isRetryable() {\n\t\treturn (\n\t\t\tthis.reason === 'rate-limited' ||\n\t\t\tthis.reason === 'server' ||\n\t\t\tthis.reason === 'timeout' ||\n\t\t\tthis.reason === 'network'\n\t\t)\n\t}\n}\n\n/** Map an HTTP status onto a coarse {@link ImproveFetchErrorReason}. */\nexport const getReasonFromStatus = (\n\tstatus: number,\n): ImproveFetchErrorReason => {\n\tif (status === 429) return 'rate-limited'\n\tif (status === 401 || status === 403) return 'unauthorized'\n\tif (status >= 500) return 'server'\n\treturn 'client'\n}\n","/**\n * @constant CHAR_SET\n * @description Key/value set of characters to be used for generation strings\n */\nconst CHAR_SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'\n\n/**\n * @function getRandomString\n * @description Generate a random string\n */\nexport function getRandomString(characters: number = 5): string {\n\tif (!characters || typeof characters !== 'number') return ''\n\treturn Array(characters)\n\t\t.fill('')\n\t\t.reduce((acc) => {\n\t\t\tacc += CHAR_SET.charAt(Math.floor(Math.random() * CHAR_SET.length))\n\t\t\treturn acc\n\t\t}, '')\n}\n","import { ImproveConfiguration } from '../types'\nimport { ImproveFetchError } from './errors'\n\n/**\n * @function normalizeConfig\n * @description Coerce a parsed config response into a well-formed\n * {@link ImproveConfiguration}. The backend returns `200 {}` for an unknown\n * org/environment (and on its own internal error). An empty object is truthy,\n * so it slips past `if (!this.config)` guards and then crashes downstream on\n * `config.tests[slug]` (`Cannot read properties of undefined`). Guaranteeing\n * `tests`/`flags`/`audience` are always objects lets the SDK fail open to\n * default values instead of throwing during render/middleware.\n *\n * Throws {@link ImproveFetchError} `invalid-response` when the body isn't an\n * object at all (e.g. an array, string, or null).\n */\nexport const normalizeConfig = (body: unknown): ImproveConfiguration => {\n\tif (typeof body !== 'object' || body === null || Array.isArray(body)) {\n\t\tthrow new ImproveFetchError('invalid-response')\n\t}\n\n\tconst raw = body as Partial<ImproveConfiguration>\n\n\treturn {\n\t\tname: typeof raw.name === 'string' ? raw.name : '',\n\t\tversion: typeof raw.version === 'number' ? raw.version : 0,\n\t\ttests: raw.tests ?? {},\n\t\tflags: raw.flags ?? {},\n\t\taudience: raw.audience ?? {},\n\t}\n}\n","/**\n * @function parseRetryAfterMs\n * @description Parse an HTTP `Retry-After` header into milliseconds. Supports\n * both the delta-seconds form (`\"120\"`) and the HTTP-date form\n * (`\"Wed, 21 Oct 2026 07:28:00 GMT\"`). Returns `undefined` when the header is\n * absent or unparseable so the caller can fall back to computed backoff.\n *\n * @param {string | null} [header] - the raw `Retry-After` header value\n * @param {number} [now] - current epoch ms (injectable for testing)\n */\nexport const parseRetryAfterMs = (\n\theader: string | null | undefined,\n\tnow: number = Date.now(),\n): number | undefined => {\n\tif (!header) return undefined\n\n\tconst seconds = Number(header)\n\tif (Number.isFinite(seconds)) return Math.max(0, Math.round(seconds * 1000))\n\n\tconst date = Date.parse(header)\n\tif (Number.isNaN(date)) return undefined\n\n\treturn Math.max(0, date - now)\n}\n\n/**\n * @function getBackoffDelayMs\n * @description Exponential backoff with full jitter, capped at `maxDelay`.\n * Jitter spreads retries so concurrent clients don't stampede the origin in\n * lockstep after a shared failure.\n *\n * @param {number} attempt - zero-based retry attempt index\n * @param {number} baseDelay - base delay in ms\n * @param {number} maxDelay - upper bound in ms\n */\nexport const getBackoffDelayMs = (\n\tattempt: number,\n\tbaseDelay: number,\n\tmaxDelay: number,\n): number => {\n\tconst exponential = Math.min(maxDelay, baseDelay * 2 ** attempt)\n\treturn Math.round(Math.random() * exponential)\n}\n","import { delay } from './delay'\n\n/**\n * @async @function getTimeoutError\n * @description Throw an error after a delay\n *\n * @param {number} [timeout] - time in ms after to reject default: 1000\n * @param {AbortController} [controller] - (optional) AbortController to abort the request\n */\nexport const getTimeoutError = async (\n\ttimeout: number = 1000,\n\tcontroller: AbortController,\n): Promise<null> => {\n\tawait delay(timeout)\n\tcontroller?.abort()\n\treturn null\n}\n","import { getTimeoutError } from './getTimeoutError'\n\n/**\n * @async @function timeoutFetch\n * @description Fetch with a timeout\n *\n * @param {number} timeout - time in ms after to reject default: 3000\n * @param {string} url - url to fetch\n * @param {Object} [options] - (optional) options to pass to fetch\n */\nexport const timeoutFetch = (\n\ttimeout: number = 3000,\n\turl: string,\n\toptions?: object,\n) => {\n\tconst controller = new AbortController()\n\treturn Promise.race([\n\t\tfetch(url, {\n\t\t\t...options,\n\t\t\tsignal: controller.signal,\n\t\t}),\n\t\tgetTimeoutError(timeout, controller),\n\t])\n}\n","import {\n\tCONFIG_RETRY_BASE_DELAY_MS,\n\tCONFIG_RETRY_COUNT,\n\tCONFIG_RETRY_MAX_DELAY_MS,\n\tCOOKIE_NAME_VISITOR,\n\tVISITOR_ID_LENGTH,\n\tVISITOR_ID_PREFIX,\n\tVISITOR_ID_SEPARATOR,\n} from './config/constants'\nimport { CONFIG_PATH, BASE_URL } from './config/urls'\nimport {\n\tImproveConfiguration,\n\tImproveEnvironmentOption,\n\tImproveSetupArgs,\n\tImproveTestState,\n} from './types'\nimport { delay } from './utils/delay'\nimport { getReasonFromStatus, ImproveFetchError } from './utils/errors'\nimport { getRandomString } from './utils/getRandomString'\nimport { normalizeConfig } from './utils/normalizeConfig'\nimport { getBackoffDelayMs, parseRetryAfterMs } from './utils/retry'\nimport { timeoutFetch } from './utils/timeoutFetch'\n\ntype ConfigFetch = {\n\turl: string\n\ttimeout: number\n\tretries: number\n\trevalidate?: number\n}\n\n// Next.js augments RequestInit with a `next` field for its fetch cache.\ntype RequestInitWithNext = RequestInit & {\n\tnext?: { revalidate?: number; tags?: string[] }\n}\n\nexport class BaseImproveSDK {\n\torganizationId: string\n\tenvironment: ImproveEnvironmentOption = 'develop'\n\tstate: ImproveTestState\n\n\t#configFetch: ConfigFetch | null = null\n\n\t#warningsDisabled: boolean = false\n\n\tconfig: ImproveConfiguration | null = null\n\n\t_baseUrl: undefined | string\n\n\tconstructor({\n\t\torganizationId,\n\t\tenvironment,\n\t\tstate,\n\t\tconfig,\n\t\tfetchTimeout,\n\t\tbaseUrl,\n\t\tconfigRetries,\n\t\tconfigRevalidate,\n\t\tdisableWarnings,\n\t}: ImproveSetupArgs) {\n\t\tthis.organizationId = organizationId\n\t\tthis.environment = environment\n\t\tthis.state = state\n\t\tthis._baseUrl = baseUrl || BASE_URL\n\t\tthis.#warningsDisabled = disableWarnings ?? false\n\n\t\tif (config) {\n\t\t\tthis.config = config\n\t\t} else {\n\t\t\tthis.#configFetch = {\n\t\t\t\turl: [\n\t\t\t\t\t`${this._baseUrl}${CONFIG_PATH}`,\n\t\t\t\t\tthis.organizationId,\n\t\t\t\t\tthis.environment,\n\t\t\t\t\tthis.state || 'active',\n\t\t\t\t].join('/'),\n\t\t\t\ttimeout: fetchTimeout || 3000,\n\t\t\t\tretries: configRetries ?? CONFIG_RETRY_COUNT,\n\t\t\t\trevalidate: configRevalidate,\n\t\t\t}\n\t\t}\n\t}\n\n\t_fetchConfig = async (config?: RequestInit) => {\n\t\tif (this.config) return\n\n\t\tif (!this.#configFetch) throw new Error('No config fetch setup provided')\n\n\t\tconst { url, timeout, retries, revalidate } = this.#configFetch\n\n\t\t// Opt into the Next.js fetch cache when a revalidate window is configured,\n\t\t// so the datafile is reused across requests instead of re-fetched.\n\t\tconst requestInit: RequestInitWithNext = { ...config }\n\t\tif (typeof revalidate === 'number') {\n\t\t\trequestInit.next = {\n\t\t\t\t...(config as RequestInitWithNext | undefined)?.next,\n\t\t\t\trevalidate,\n\t\t\t}\n\t\t}\n\n\t\t// Seed with a generic network error so an exhausted loop always has a\n\t\t// meaningful, typed error to throw.\n\t\tlet lastError = new ImproveFetchError('network')\n\n\t\tfor (let attempt = 0; attempt <= retries; attempt++) {\n\t\t\tif (attempt > 0) {\n\t\t\t\t// Prefer a server-provided Retry-After; otherwise back off with jitter.\n\t\t\t\tawait delay(\n\t\t\t\t\tlastError.retryAfterMs ??\n\t\t\t\t\t\tgetBackoffDelayMs(\n\t\t\t\t\t\t\tattempt - 1,\n\t\t\t\t\t\t\tCONFIG_RETRY_BASE_DELAY_MS,\n\t\t\t\t\t\t\tCONFIG_RETRY_MAX_DELAY_MS,\n\t\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tlet res: Response | null\n\t\t\ttry {\n\t\t\t\tres = await timeoutFetch(timeout, url, requestInit)\n\t\t\t} catch (cause) {\n\t\t\t\t// timeoutFetch aborts on timeout (AbortError) and rejects on\n\t\t\t\t// network-level failures — both are transient and retryable.\n\t\t\t\tconst aborted = cause instanceof Error && cause.name === 'AbortError'\n\t\t\t\tlastError = new ImproveFetchError(aborted ? 'timeout' : 'network', {\n\t\t\t\t\tcause,\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// timeoutFetch resolves null when the timeout wins the race.\n\t\t\tif (!res) {\n\t\t\t\tlastError = new ImproveFetchError('timeout')\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (res.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tthis.config = normalizeConfig(await res.json())\n\t\t\t\t} catch (cause) {\n\t\t\t\t\tlastError =\n\t\t\t\t\t\tcause instanceof ImproveFetchError\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: new ImproveFetchError('invalid-response', { cause })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn this.config\n\t\t\t}\n\n\t\t\tlastError = new ImproveFetchError(getReasonFromStatus(res.status), {\n\t\t\t\tstatus: res.status,\n\t\t\t\tretryAfterMs: parseRetryAfterMs(res.headers.get('Retry-After')),\n\t\t\t})\n\n\t\t\t// Auth/validation rejections won't change on retry — fail fast.\n\t\t\tif (!lastError.isRetryable) break\n\t\t}\n\n\t\tthrow lastError\n\t}\n\n\tloadConfig = (config: ImproveConfiguration) => {\n\t\tthis.config = config\n\t}\n\n\tgenerateVisitorId = () => {\n\t\treturn [\n\t\t\tVISITOR_ID_PREFIX,\n\t\t\tgetRandomString(VISITOR_ID_LENGTH).toUpperCase(),\n\t\t].join(VISITOR_ID_SEPARATOR)\n\t}\n\n\tgetVisitorCookieName = () => COOKIE_NAME_VISITOR\n\n\t/**\n\t * Emit a development warning, unless warnings were disabled via the\n\t * `disableWarnings` setup option. Centralized so every SDK warning honors the\n\t * same switch. No-op where `console` is unavailable.\n\t */\n\tprotected _warn = (message: string) => {\n\t\tif (this.#warningsDisabled) return\n\t\tif (typeof console === 'undefined') return\n\t\tconsole.warn(`[Improve] ${message}`)\n\t}\n\n\tvalidateTestValue = (testName: string, testValue: string) => {\n\t\tif (!this.config)\n\t\t\tthrow new Error(\n\t\t\t\t'Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup',\n\t\t\t)\n\n\t\tconst testConfig = this.config.tests[testName]\n\n\t\tif (!testConfig) throw new Error(`No config found for ${testName}`)\n\n\t\treturn Boolean(\n\t\t\ttestConfig.options.find((option) => option.slug === testValue),\n\t\t)\n\t}\n\n\tvalidateVisitorId = (possibleVisitorId: string) => {\n\t\tconst visitorIdParts = possibleVisitorId.split(VISITOR_ID_SEPARATOR)\n\t\tif (visitorIdParts.length !== 2) return false\n\t\tconst [key, value] = visitorIdParts as [string, string]\n\t\treturn key === VISITOR_ID_PREFIX && value.length === VISITOR_ID_LENGTH\n\t}\n}\n"],"names":["SOCIAL_BROWSERS","PRIMARY_TOUCH_DEVICES","parseUserAgent","userAgent","deviceType","results","parser","UAParser","getResult","device","type","AUDIENCE_PARAMS","includes","pointer","browser","formatBrowser","browserName","name","toLowerCase","browserTypeMatch","find","browserType","os","formatOs","osName","osTypeMatch","osType","getVisitorMatchesAudience","audience","visitorParams","Object","entries","every","paramKey","paramValue","getRandomTestValue","options","length","slug","value","Math","random","reduce","acc","split","match","VISITOR_ID_PREFIX","ANALYTICS_PATH","delay","ms","Promise","resolve","setTimeout","REASON_MESSAGES","timeout","network","unauthorized","server","client","ImproveFetchError","Error","reason","status","retryAfterMs","cause","isRetryable","getReasonFromStatus","CHAR_SET","normalizeConfig","body","Array","isArray","raw","version","tests","flags","parseRetryAfterMs","header","now","Date","seconds","Number","isFinite","max","round","date","parse","isNaN","getBackoffDelayMs","attempt","baseDelay","maxDelay","min","getTimeoutError","controller","abort","timeoutFetch","url","AbortController","race","fetch","signal","BaseImproveSDK","organizationId","environment","state","config","fetchTimeout","baseUrl","configRetries","configRevalidate","disableWarnings","_fetchConfig","retries","revalidate","requestInit","next","lastError","res","aborted","ok","json","headers","get","loadConfig","generateVisitorId","getRandomString","characters","fill","charAt","floor","toUpperCase","join","getVisitorCookieName","_warn","message","console","warn","validateTestValue","testName","testValue","testConfig","Boolean","option","validateVisitorId","possibleVisitorId","visitorIdParts","key","_baseUrl"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"base-m0tq6j54.js","sources":["../src/config/audiences.ts","../src/utils/parseUserAgent.ts","../src/config/constants.ts","../src/utils/delay.ts","../src/utils/errors.ts","../src/utils/getRandomString.ts","../src/utils/normalizeConfig.ts","../src/utils/retry.ts","../src/utils/getTimeoutError.ts","../src/utils/timeoutFetch.ts","../src/config/urls.ts","../src/base.ts","../src/utils/getRandomTestValue.ts","../src/utils/getVisitorMatchesAudience.ts"],"sourcesContent":["/**\n * @constant AUDIENCE_PARAMS\n * @description All posibile audience tracking options\n */\nexport const AUDIENCE_PARAMS = {\n\t//? Location — coarse IP-geolocation (ISO 3166-1 alpha-2 country code). Values\n\t//? aren't enumerated here (any country code is valid); the key exists so\n\t//? audiences can target `country`. Resolved server-side (SSR/middleware) from\n\t//? the request, since the browser can't determine it from the user agent.\n\tcountry: [] as ReadonlyArray<string>,\n\t//? Technical\n\tpointer: ['coarse', 'fine'],\n\tdevice: [\n\t\t'wearable',\n\t\t'mobile',\n\t\t'tablet',\n\t\t'console',\n\t\t'smarttv',\n\t\t'embedded',\n\t\t'desktop',\n\t],\n\tbrowser: [\n\t\t'chrome',\n\t\t'safari',\n\t\t'firefox',\n\t\t'edge',\n\t\t'ie',\n\t\t'samsung internet',\n\t\t'social',\n\t\t'other',\n\t],\n\tos: ['mac os', 'ios', 'android', 'windows', 'unix'],\n} as const\n\n//? Generated types\nexport type AudienceParamKey = keyof typeof AUDIENCE_PARAMS\n\nexport type AudienceParamPointer = (typeof AUDIENCE_PARAMS.pointer)[number]\n\nexport type AudienceParamDevice = (typeof AUDIENCE_PARAMS.device)[number]\n\nexport type AudienceParamBrowser = (typeof AUDIENCE_PARAMS.browser)[number]\n\nexport type AudienceParamOs = (typeof AUDIENCE_PARAMS.os)[number]\n\nexport const AUDIENCE_PARAM_KEYS = Object.keys(\n\tAUDIENCE_PARAMS,\n) as ReadonlyArray<AudienceParamKey>\n","import { UAParser } from 'ua-parser-js'\n\nimport {\n\tAUDIENCE_PARAMS,\n\tAudienceParamBrowser,\n\tAudienceParamDevice,\n\tAudienceParamOs,\n\tAudienceParamPointer,\n} from '../config/audiences'\n\nexport type ParsedUserAgent = {\n\tpointer: AudienceParamPointer\n\tdevice: AudienceParamDevice\n\tbrowser: AudienceParamBrowser\n\tos: AudienceParamOs\n\t// Coarse geo, not derived from the user agent — merged in server-side\n\t// (SSR/middleware) so audiences can target by country. Absent client-side.\n\tcountry?: string\n}\n\nconst formatDeviceType = (deviceType?: string): AudienceParamDevice => {\n\tif (\n\t\tdeviceType &&\n\t\tAUDIENCE_PARAMS.device.includes(deviceType as AudienceParamDevice)\n\t) {\n\t\treturn deviceType as AudienceParamDevice\n\t}\n\treturn 'desktop'\n}\n\nconst SOCIAL_BROWSERS: ReadonlyArray<string> = [\n\t'tiktok',\n\t'wechat',\n\t'weibo',\n\t'snapchat',\n\t'klarna',\n\t'Line',\n\t'instagram',\n\t'facebook',\n\t'alipay',\n\t'Baidu',\n]\n\nconst formatBrowser = (browserName: string = ''): AudienceParamBrowser => {\n\tif (!browserName) return 'other'\n\n\tconst name = browserName.toLowerCase()\n\n\tconst browserTypeMatch = AUDIENCE_PARAMS.browser.find((browserType) => {\n\t\treturn name.includes(browserType)\n\t})\n\n\tif (browserTypeMatch) return browserTypeMatch\n\n\tif (SOCIAL_BROWSERS.includes(name)) return 'social'\n\treturn 'other'\n}\n\nconst PRIMARY_TOUCH_DEVICES: ReadonlyArray<AudienceParamDevice> = [\n\t'wearable',\n\t'mobile',\n\t'tablet',\n]\n\nconst formatPointer = (device: AudienceParamDevice) => {\n\treturn PRIMARY_TOUCH_DEVICES.includes(device) ? 'coarse' : 'fine'\n}\n\nconst formatOs = (osName: string = ''): AudienceParamOs => {\n\tif (!osName) return 'unix'\n\n\tconst os = osName.toLowerCase()\n\n\tconst osTypeMatch = AUDIENCE_PARAMS.os.find((osType) => {\n\t\treturn os.includes(osType)\n\t})\n\n\tif (osTypeMatch) return osTypeMatch\n\treturn 'unix'\n}\n\nexport const parseUserAgent = (userAgent: string): ParsedUserAgent | null => {\n\tif (!userAgent || typeof userAgent !== 'string') return null\n\n\tconst parser = new UAParser(userAgent)\n\tconst results = parser.getResult()\n\n\tconst device = formatDeviceType(results.device.type)\n\n\treturn {\n\t\tpointer: formatPointer(device),\n\t\tdevice: device,\n\t\tbrowser: formatBrowser(results.browser.name),\n\t\tos: formatOs(results.os.name),\n\t}\n}\n","export const COOKIE_NAME_VISITOR = 'visitorId'\n\nexport const VISITOR_ID_PREFIX = 'visi'\n\nexport const VISITOR_ID_LENGTH = 26\n\nexport const VISITOR_ID_SEPARATOR = '_'\n\n/** Default number of retries for a failed config fetch (network / 5xx / 429). */\nexport const CONFIG_RETRY_COUNT = 2\n\n/** Base delay for config-fetch backoff, in ms. */\nexport const CONFIG_RETRY_BASE_DELAY_MS = 300\n\n/** Upper bound for a single config-fetch backoff wait, in ms. */\nexport const CONFIG_RETRY_MAX_DELAY_MS = 3000\n","export const delay = (ms: number) =>\n\tnew Promise((resolve) => setTimeout(resolve, ms))\n","export type ImproveFetchErrorReason =\n\t| 'timeout'\n\t| 'network'\n\t| 'unauthorized'\n\t| 'rate-limited'\n\t| 'server'\n\t| 'client'\n\t| 'invalid-response'\n\nconst REASON_MESSAGES: Record<ImproveFetchErrorReason, string> = {\n\ttimeout: 'Configuration request timed out',\n\tnetwork: 'Configuration request could not reach the server',\n\tunauthorized:\n\t\t'Configuration request was rejected (403): verify the organizationId, the server token, and that the request origin is whitelisted for this environment',\n\t'rate-limited':\n\t\t'Configuration request was rate limited (429): too many requests, or the organization usage limit was reached',\n\tserver: 'Configuration request failed with a server error',\n\tclient: 'Configuration request was rejected by the server',\n\t'invalid-response': 'Configuration response could not be parsed as JSON',\n}\n\n/**\n * Error thrown by the config fetch layer. Carries the HTTP `status`, a coarse\n * `reason`, and (when the server sent one) the `Retry-After` delay in ms, so\n * callers can distinguish a misconfiguration (unauthorized) from a transient\n * failure (rate-limited/server/timeout) instead of guessing from a string.\n */\nexport class ImproveFetchError extends Error {\n\treason: ImproveFetchErrorReason\n\tstatus?: number\n\tretryAfterMs?: number\n\n\tconstructor(\n\t\treason: ImproveFetchErrorReason,\n\t\toptions?: { status?: number; retryAfterMs?: number; cause?: unknown },\n\t) {\n\t\tsuper(REASON_MESSAGES[reason])\n\t\tthis.name = 'ImproveFetchError'\n\t\tthis.reason = reason\n\t\tthis.status = options?.status\n\t\tthis.retryAfterMs = options?.retryAfterMs\n\t\tif (options && 'cause' in options) {\n\t\t\t;(this as { cause?: unknown }).cause = options.cause\n\t\t}\n\t}\n\n\t/** Whether retrying the request could plausibly succeed. */\n\tget isRetryable() {\n\t\treturn (\n\t\t\tthis.reason === 'rate-limited' ||\n\t\t\tthis.reason === 'server' ||\n\t\t\tthis.reason === 'timeout' ||\n\t\t\tthis.reason === 'network'\n\t\t)\n\t}\n}\n\n/** Map an HTTP status onto a coarse {@link ImproveFetchErrorReason}. */\nexport const getReasonFromStatus = (\n\tstatus: number,\n): ImproveFetchErrorReason => {\n\tif (status === 429) return 'rate-limited'\n\tif (status === 401 || status === 403) return 'unauthorized'\n\tif (status >= 500) return 'server'\n\treturn 'client'\n}\n","/**\n * @constant CHAR_SET\n * @description Key/value set of characters to be used for generation strings\n */\nconst CHAR_SET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'\n\n/**\n * @function getRandomString\n * @description Generate a random string\n */\nexport function getRandomString(characters: number = 5): string {\n\tif (!characters || typeof characters !== 'number') return ''\n\treturn Array(characters)\n\t\t.fill('')\n\t\t.reduce((acc) => {\n\t\t\tacc += CHAR_SET.charAt(Math.floor(Math.random() * CHAR_SET.length))\n\t\t\treturn acc\n\t\t}, '')\n}\n","import { ImproveConfiguration } from '../types'\nimport { ImproveFetchError } from './errors'\n\n/**\n * @function normalizeConfig\n * @description Coerce a parsed config response into a well-formed\n * {@link ImproveConfiguration}. The backend returns `200 {}` for an unknown\n * org/environment (and on its own internal error). An empty object is truthy,\n * so it slips past `if (!this.config)` guards and then crashes downstream on\n * `config.tests[slug]` (`Cannot read properties of undefined`). Guaranteeing\n * `tests`/`flags`/`audience` are always objects lets the SDK fail open to\n * default values instead of throwing during render/middleware.\n *\n * Throws {@link ImproveFetchError} `invalid-response` when the body isn't an\n * object at all (e.g. an array, string, or null).\n */\nexport const normalizeConfig = (body: unknown): ImproveConfiguration => {\n\tif (typeof body !== 'object' || body === null || Array.isArray(body)) {\n\t\tthrow new ImproveFetchError('invalid-response')\n\t}\n\n\tconst raw = body as Partial<ImproveConfiguration>\n\n\treturn {\n\t\tname: typeof raw.name === 'string' ? raw.name : '',\n\t\tversion: typeof raw.version === 'number' ? raw.version : 0,\n\t\ttests: raw.tests ?? {},\n\t\tflags: raw.flags ?? {},\n\t\taudience: raw.audience ?? {},\n\t}\n}\n","/**\n * @function parseRetryAfterMs\n * @description Parse an HTTP `Retry-After` header into milliseconds. Supports\n * both the delta-seconds form (`\"120\"`) and the HTTP-date form\n * (`\"Wed, 21 Oct 2026 07:28:00 GMT\"`). Returns `undefined` when the header is\n * absent or unparseable so the caller can fall back to computed backoff.\n *\n * @param {string | null} [header] - the raw `Retry-After` header value\n * @param {number} [now] - current epoch ms (injectable for testing)\n */\nexport const parseRetryAfterMs = (\n\theader: string | null | undefined,\n\tnow: number = Date.now(),\n): number | undefined => {\n\tif (!header) return undefined\n\n\tconst seconds = Number(header)\n\tif (Number.isFinite(seconds)) return Math.max(0, Math.round(seconds * 1000))\n\n\tconst date = Date.parse(header)\n\tif (Number.isNaN(date)) return undefined\n\n\treturn Math.max(0, date - now)\n}\n\n/**\n * @function getBackoffDelayMs\n * @description Exponential backoff with full jitter, capped at `maxDelay`.\n * Jitter spreads retries so concurrent clients don't stampede the origin in\n * lockstep after a shared failure.\n *\n * @param {number} attempt - zero-based retry attempt index\n * @param {number} baseDelay - base delay in ms\n * @param {number} maxDelay - upper bound in ms\n */\nexport const getBackoffDelayMs = (\n\tattempt: number,\n\tbaseDelay: number,\n\tmaxDelay: number,\n): number => {\n\tconst exponential = Math.min(maxDelay, baseDelay * 2 ** attempt)\n\treturn Math.round(Math.random() * exponential)\n}\n","import { delay } from './delay'\n\n/**\n * @async @function getTimeoutError\n * @description Throw an error after a delay\n *\n * @param {number} [timeout] - time in ms after to reject default: 1000\n * @param {AbortController} [controller] - (optional) AbortController to abort the request\n */\nexport const getTimeoutError = async (\n\ttimeout: number = 1000,\n\tcontroller: AbortController,\n): Promise<null> => {\n\tawait delay(timeout)\n\tcontroller?.abort()\n\treturn null\n}\n","import { getTimeoutError } from './getTimeoutError'\n\n/**\n * @async @function timeoutFetch\n * @description Fetch with a timeout\n *\n * @param {number} timeout - time in ms after to reject default: 3000\n * @param {string} url - url to fetch\n * @param {Object} [options] - (optional) options to pass to fetch\n */\nexport const timeoutFetch = (\n\ttimeout: number = 3000,\n\turl: string,\n\toptions?: object,\n) => {\n\tconst controller = new AbortController()\n\treturn Promise.race([\n\t\tfetch(url, {\n\t\t\t...options,\n\t\t\tsignal: controller.signal,\n\t\t}),\n\t\tgetTimeoutError(timeout, controller),\n\t])\n}\n","export const BASE_URL = 'https://improve.obelism.studio'\nexport const CONFIG_PATH = `/config`\n// Events go to Obelism numbers (the numbers SDK's default endpoint); only a\n// custom `baseUrl` sends them to its own /api/log.\nexport const ANALYTICS_PATH = `/api/log`\n","import {\n\tCONFIG_RETRY_BASE_DELAY_MS,\n\tCONFIG_RETRY_COUNT,\n\tCONFIG_RETRY_MAX_DELAY_MS,\n\tCOOKIE_NAME_VISITOR,\n\tVISITOR_ID_LENGTH,\n\tVISITOR_ID_PREFIX,\n\tVISITOR_ID_SEPARATOR,\n} from './config/constants'\nimport { CONFIG_PATH, BASE_URL } from './config/urls'\nimport {\n\tImproveConfiguration,\n\tImproveEnvironmentOption,\n\tImproveSetupArgs,\n\tImproveTestState,\n} from './types'\nimport { delay } from './utils/delay'\nimport { getReasonFromStatus, ImproveFetchError } from './utils/errors'\nimport { getRandomString } from './utils/getRandomString'\nimport { normalizeConfig } from './utils/normalizeConfig'\nimport { getBackoffDelayMs, parseRetryAfterMs } from './utils/retry'\nimport { timeoutFetch } from './utils/timeoutFetch'\n\ntype ConfigFetch = {\n\turl: string\n\ttimeout: number\n\tretries: number\n\trevalidate?: number\n}\n\n// Next.js augments RequestInit with a `next` field for its fetch cache.\ntype RequestInitWithNext = RequestInit & {\n\tnext?: { revalidate?: number; tags?: string[] }\n}\n\nexport class BaseImproveSDK {\n\torganizationId: string\n\tenvironment: ImproveEnvironmentOption = 'develop'\n\tstate: ImproveTestState\n\n\t#configFetch: ConfigFetch | null = null\n\n\t#warningsDisabled: boolean = false\n\n\tconfig: ImproveConfiguration | null = null\n\n\t_baseUrl: undefined | string\n\n\tconstructor({\n\t\torganizationId,\n\t\tenvironment,\n\t\tstate,\n\t\tconfig,\n\t\tfetchTimeout,\n\t\tbaseUrl,\n\t\tconfigRetries,\n\t\tconfigRevalidate,\n\t\tdisableWarnings,\n\t}: ImproveSetupArgs) {\n\t\tthis.organizationId = organizationId\n\t\tthis.environment = environment\n\t\tthis.state = state\n\t\tthis._baseUrl = baseUrl || BASE_URL\n\t\tthis.#warningsDisabled = disableWarnings ?? false\n\n\t\tif (config) {\n\t\t\tthis.config = config\n\t\t} else {\n\t\t\tthis.#configFetch = {\n\t\t\t\turl: [\n\t\t\t\t\t`${this._baseUrl}${CONFIG_PATH}`,\n\t\t\t\t\tthis.organizationId,\n\t\t\t\t\tthis.environment,\n\t\t\t\t\tthis.state || 'active',\n\t\t\t\t].join('/'),\n\t\t\t\ttimeout: fetchTimeout || 3000,\n\t\t\t\tretries: configRetries ?? CONFIG_RETRY_COUNT,\n\t\t\t\trevalidate: configRevalidate,\n\t\t\t}\n\t\t}\n\t}\n\n\t_fetchConfig = async (config?: RequestInit) => {\n\t\tif (this.config) return\n\n\t\tif (!this.#configFetch) throw new Error('No config fetch setup provided')\n\n\t\tconst { url, timeout, retries, revalidate } = this.#configFetch\n\n\t\t// Opt into the Next.js fetch cache when a revalidate window is configured,\n\t\t// so the datafile is reused across requests instead of re-fetched.\n\t\tconst requestInit: RequestInitWithNext = { ...config }\n\t\tif (typeof revalidate === 'number') {\n\t\t\trequestInit.next = {\n\t\t\t\t...(config as RequestInitWithNext | undefined)?.next,\n\t\t\t\trevalidate,\n\t\t\t}\n\t\t}\n\n\t\t// Seed with a generic network error so an exhausted loop always has a\n\t\t// meaningful, typed error to throw.\n\t\tlet lastError = new ImproveFetchError('network')\n\n\t\tfor (let attempt = 0; attempt <= retries; attempt++) {\n\t\t\tif (attempt > 0) {\n\t\t\t\t// Prefer a server-provided Retry-After; otherwise back off with jitter.\n\t\t\t\tawait delay(\n\t\t\t\t\tlastError.retryAfterMs ??\n\t\t\t\t\t\tgetBackoffDelayMs(\n\t\t\t\t\t\t\tattempt - 1,\n\t\t\t\t\t\t\tCONFIG_RETRY_BASE_DELAY_MS,\n\t\t\t\t\t\t\tCONFIG_RETRY_MAX_DELAY_MS,\n\t\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tlet res: Response | null\n\t\t\ttry {\n\t\t\t\tres = await timeoutFetch(timeout, url, requestInit)\n\t\t\t} catch (cause) {\n\t\t\t\t// timeoutFetch aborts on timeout (AbortError) and rejects on\n\t\t\t\t// network-level failures — both are transient and retryable.\n\t\t\t\tconst aborted = cause instanceof Error && cause.name === 'AbortError'\n\t\t\t\tlastError = new ImproveFetchError(aborted ? 'timeout' : 'network', {\n\t\t\t\t\tcause,\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// timeoutFetch resolves null when the timeout wins the race.\n\t\t\tif (!res) {\n\t\t\t\tlastError = new ImproveFetchError('timeout')\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (res.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tthis.config = normalizeConfig(await res.json())\n\t\t\t\t} catch (cause) {\n\t\t\t\t\tlastError =\n\t\t\t\t\t\tcause instanceof ImproveFetchError\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: new ImproveFetchError('invalid-response', { cause })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn this.config\n\t\t\t}\n\n\t\t\tlastError = new ImproveFetchError(getReasonFromStatus(res.status), {\n\t\t\t\tstatus: res.status,\n\t\t\t\tretryAfterMs: parseRetryAfterMs(res.headers.get('Retry-After')),\n\t\t\t})\n\n\t\t\t// Auth/validation rejections won't change on retry — fail fast.\n\t\t\tif (!lastError.isRetryable) break\n\t\t}\n\n\t\tthrow lastError\n\t}\n\n\tloadConfig = (config: ImproveConfiguration) => {\n\t\tthis.config = config\n\t}\n\n\tgenerateVisitorId = () => {\n\t\treturn [\n\t\t\tVISITOR_ID_PREFIX,\n\t\t\tgetRandomString(VISITOR_ID_LENGTH).toUpperCase(),\n\t\t].join(VISITOR_ID_SEPARATOR)\n\t}\n\n\tgetVisitorCookieName = () => COOKIE_NAME_VISITOR\n\n\t/**\n\t * Emit a development warning, unless warnings were disabled via the\n\t * `disableWarnings` setup option. Centralized so every SDK warning honors the\n\t * same switch. No-op where `console` is unavailable.\n\t */\n\tprotected _warn = (message: string) => {\n\t\tif (this.#warningsDisabled) return\n\t\tif (typeof console === 'undefined') return\n\t\tconsole.warn(`[Improve] ${message}`)\n\t}\n\n\tvalidateTestValue = (testName: string, testValue: string) => {\n\t\tif (!this.config)\n\t\t\tthrow new Error(\n\t\t\t\t'Config is required before validating, either use `.fetchConfig()`, .loadConfig(config) or provide it during setup',\n\t\t\t)\n\n\t\tconst testConfig = this.config.tests[testName]\n\n\t\tif (!testConfig) throw new Error(`No config found for ${testName}`)\n\n\t\treturn Boolean(\n\t\t\ttestConfig.options.find((option) => option.slug === testValue),\n\t\t)\n\t}\n\n\tvalidateVisitorId = (possibleVisitorId: string) => {\n\t\tconst visitorIdParts = possibleVisitorId.split(VISITOR_ID_SEPARATOR)\n\t\tif (visitorIdParts.length !== 2) return false\n\t\tconst [key, value] = visitorIdParts as [string, string]\n\t\treturn key === VISITOR_ID_PREFIX && value.length === VISITOR_ID_LENGTH\n\t}\n}\n","import { ImproveTestOption } from '../types'\n\n// function cryptoRandom() {\n// \treturn crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1)\n// }\n\nexport const getRandomTestValue = (options: ImproveTestOption[]) => {\n\tif (options.length === 0) return null\n\tif (options.length === 1) return options[0].slug\n\n\t// Get a random number between 0 and 1\n\tconst sum = options.reduce((acc, { split }) => acc + split, 0)\n\tlet value = Math.random() * sum\n\n\tconst match =\n\t\toptions.find(({ split }) => {\n\t\t\tvalue -= split\n\t\t\treturn value <= 0\n\t\t}) || options[0]\n\n\treturn match.slug\n}\n","import { ImproveAudienceValue } from '../types'\nimport { AudienceParamKey } from '../config/audiences'\nimport { ParsedUserAgent } from './parseUserAgent'\n\nexport const getVisitorMatchesAudience = (\n\taudience: ImproveAudienceValue | undefined,\n\tvisitorParams: ParsedUserAgent,\n) => {\n\tif (!audience) return true\n\treturn Object.entries(audience).every(([paramKey, paramValue]) => {\n\t\treturn visitorParams[paramKey as AudienceParamKey] === paramValue\n\t})\n}\n"],"names":["SOCIAL_BROWSERS","PRIMARY_TOUCH_DEVICES","VISITOR_ID_PREFIX","delay","ms","Promise","resolve","setTimeout","REASON_MESSAGES","timeout","network","unauthorized","server","client","ImproveFetchError","Error","reason","options","name","status","retryAfterMs","cause","isRetryable","getReasonFromStatus","CHAR_SET","normalizeConfig","body","Array","isArray","raw","version","tests","flags","audience","parseRetryAfterMs","header","now","Date","seconds","Number","isFinite","Math","max","round","date","parse","isNaN","getBackoffDelayMs","attempt","baseDelay","maxDelay","random","min","getTimeoutError","controller","abort","timeoutFetch","url","AbortController","race","fetch","signal","organizationId","environment","state","config","fetchTimeout","baseUrl","configRetries","configRevalidate","disableWarnings","_fetchConfig","retries","revalidate","requestInit","next","lastError","res","aborted","ok","json","headers","get","loadConfig","generateVisitorId","getRandomString","characters","fill","reduce","acc","charAt","floor","length","toUpperCase","join","getVisitorCookieName","_warn","message","console","warn","validateTestValue","testName","testValue","testConfig","Boolean","find","option","slug","validateVisitorId","possibleVisitorId","visitorIdParts","split","key","value","_baseUrl","match","visitorParams","Object","entries","every","paramKey","paramValue","userAgent","deviceType","results","parser","UAParser","getResult","device","type","AUDIENCE_PARAMS","includes","pointer","browser","formatBrowser","browserName","toLowerCase","browserTypeMatch","browserType","os","formatOs","osName","osTypeMatch","osType"],"mappings":"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"}
|