@runbandit/sdk 3.1.2 → 3.3.0

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/dist/index.js CHANGED
@@ -1,3 +1,21 @@
1
+ import {
2
+ DECISIONS_COOKIE,
3
+ DEFAULT_SESSION_TTL_MS,
4
+ SESSION_CREATED_AT_KEY,
5
+ SESSION_ID_KEY,
6
+ SnapshotClient,
7
+ VISITOR_COOKIE,
8
+ VISITOR_ID_KEY,
9
+ defaultSessionStorage,
10
+ generateId,
11
+ getOrCreateSessionId,
12
+ getOrCreateVisitorId,
13
+ parseDecisions,
14
+ pickTreatment,
15
+ readCookie,
16
+ serializeDecisions
17
+ } from "./chunk-BG5L4PBC.js";
18
+
1
19
  // ../shared/dist/types.js
2
20
  var ExperimentStatus;
3
21
  (function(ExperimentStatus2) {
@@ -898,54 +916,6 @@ function platformById(id) {
898
916
  return SITE_PLATFORMS.find((platform) => platform.id === id);
899
917
  }
900
918
 
901
- // src/session.ts
902
- var SESSION_ID_KEY = "bandits_session_id";
903
- var SESSION_CREATED_AT_KEY = "bandits_session_created_at";
904
- var VISITOR_ID_KEY = "bandits_visitor_id";
905
- var DEFAULT_SESSION_TTL_MS = 864e5;
906
- function generateId() {
907
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
908
- return crypto.randomUUID();
909
- }
910
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
911
- const r = Math.random() * 16 | 0;
912
- return (c === "x" ? r : r & 3 | 8).toString(16);
913
- });
914
- }
915
- function getOrCreateSessionId(options = {}) {
916
- const { storage, now = () => Date.now(), ttlMs = DEFAULT_SESSION_TTL_MS } = options;
917
- if (!storage) return generateId();
918
- try {
919
- const stored = storage.getItem(SESSION_ID_KEY);
920
- const storedAt = storage.getItem(SESSION_CREATED_AT_KEY);
921
- if (stored && storedAt) {
922
- const age = now() - parseInt(storedAt, 10);
923
- if (age < ttlMs) return stored;
924
- }
925
- const id = generateId();
926
- storage.setItem(SESSION_ID_KEY, id);
927
- storage.setItem(SESSION_CREATED_AT_KEY, String(now()));
928
- return id;
929
- } catch {
930
- return generateId();
931
- }
932
- }
933
- function getOrCreateVisitorId(storage) {
934
- if (!storage) return generateId();
935
- try {
936
- const stored = storage.getItem(VISITOR_ID_KEY);
937
- if (stored) return stored;
938
- const id = generateId();
939
- storage.setItem(VISITOR_ID_KEY, id);
940
- return id;
941
- } catch {
942
- return generateId();
943
- }
944
- }
945
- function defaultSessionStorage() {
946
- return typeof window !== "undefined" && window.localStorage ? window.localStorage : null;
947
- }
948
-
949
919
  // src/client.ts
950
920
  var BanditsRequestError = class extends Error {
951
921
  status;
@@ -979,7 +949,7 @@ var BanditsClient = class {
979
949
  ...config
980
950
  };
981
951
  this.sessionId = this.getOrCreateSessionId();
982
- this.visitorId = getOrCreateVisitorId(defaultSessionStorage());
952
+ this.visitorId = config.visitorId ?? getOrCreateVisitorId(defaultSessionStorage());
983
953
  this.startBatchFlush();
984
954
  }
985
955
  async getAssignment(experimentId, userId = this.visitorId, context) {
@@ -1198,206 +1168,6 @@ var BanditsClient = class {
1198
1168
  }
1199
1169
  };
1200
1170
 
1201
- // src/snapshot-cache.ts
1202
- var STORAGE_KEY = "bandits_snapshot";
1203
- var REPORT_QUEUE_KEY = "bandits_pending_reports";
1204
- var DECISIONS_KEY = "bandits_client_assignments";
1205
- function pickTreatment(treatments, draw) {
1206
- if (treatments.length === 0) return null;
1207
- let cumulative = 0;
1208
- for (const treatment of treatments) {
1209
- cumulative += treatment.pct;
1210
- if (draw < cumulative) return treatment;
1211
- }
1212
- return treatments[treatments.length - 1];
1213
- }
1214
- var DEFAULT_ASSIGNMENT_TTL_SECONDS = 86400;
1215
- function randomId() {
1216
- const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
1217
- let id = "";
1218
- for (let i = 0; i < 24; i++) {
1219
- id += chars.charAt(Math.floor(Math.random() * chars.length));
1220
- }
1221
- return id;
1222
- }
1223
- var SnapshotClient = class {
1224
- config;
1225
- storage;
1226
- now;
1227
- fetchImpl;
1228
- snapshot = null;
1229
- queue = [];
1230
- sessionId;
1231
- inFlight = null;
1232
- constructor(config) {
1233
- this.config = config;
1234
- this.now = config.now ?? (() => Date.now());
1235
- this.fetchImpl = config.fetchImpl ?? ((...args) => fetch(...args));
1236
- this.storage = config.storage ?? (typeof window !== "undefined" && window.localStorage ? window.localStorage : null);
1237
- this.queue = this.readQueue();
1238
- this.sessionId = getOrCreateSessionId({
1239
- storage: this.storage ?? defaultSessionStorage(),
1240
- now: this.now,
1241
- ttlMs: config.sessionTtl
1242
- });
1243
- if (config.inlineSnapshot) {
1244
- this.snapshot = config.inlineSnapshot;
1245
- this.persistSnapshot(config.inlineSnapshot);
1246
- } else {
1247
- this.snapshot = this.readSnapshot();
1248
- }
1249
- }
1250
- getAssignmentSync(experimentId, userId) {
1251
- const snapshot = this.freshSnapshot();
1252
- if (!snapshot) return null;
1253
- const experiment = snapshot.experiments[experimentId];
1254
- if (!experiment || experiment.mode !== "snapshot") return null;
1255
- const key = `${experimentId}:${userId}`;
1256
- const decisions = this.readDecisions();
1257
- const stored = decisions[key];
1258
- if (stored && stored.expiresAt > this.now()) {
1259
- const held = experiment.treatments.find((t) => t.id === stored.treatmentId);
1260
- if (held) {
1261
- return {
1262
- clientAssignmentId: stored.clientAssignmentId,
1263
- experimentId,
1264
- treatmentId: held.id,
1265
- config: held.config
1266
- };
1267
- }
1268
- }
1269
- const treatment = pickTreatment(experiment.treatments, Math.random());
1270
- if (!treatment) return null;
1271
- const clientAssignmentId = randomId();
1272
- const ttlSeconds = experiment.assignmentTtl ?? DEFAULT_ASSIGNMENT_TTL_SECONDS;
1273
- decisions[key] = {
1274
- clientAssignmentId,
1275
- treatmentId: treatment.id,
1276
- expiresAt: this.now() + ttlSeconds * 1e3
1277
- };
1278
- this.writeDecisions(decisions);
1279
- this.queue.push({
1280
- clientAssignmentId,
1281
- experimentId,
1282
- treatmentId: treatment.id,
1283
- userId,
1284
- sessionId: this.sessionId
1285
- });
1286
- this.persistQueue();
1287
- return {
1288
- clientAssignmentId,
1289
- experimentId,
1290
- treatmentId: treatment.id,
1291
- config: treatment.config
1292
- };
1293
- }
1294
- async refresh() {
1295
- const response = await this.fetchImpl(`${this.config.apiUrl}/snapshot`, {
1296
- method: "GET",
1297
- headers: { "x-api-key": this.config.publishableKey }
1298
- });
1299
- if (!response.ok) return;
1300
- const snapshot = await response.json();
1301
- this.snapshot = snapshot;
1302
- this.persistSnapshot(snapshot);
1303
- }
1304
- async flush() {
1305
- if (this.inFlight) return this.inFlight;
1306
- this.inFlight = this.doFlush().finally(() => {
1307
- this.inFlight = null;
1308
- });
1309
- return this.inFlight;
1310
- }
1311
- async doFlush() {
1312
- if (this.queue.length === 0) return;
1313
- const reports = this.queue.slice(0, 1e3);
1314
- try {
1315
- const response = await this.fetchImpl(`${this.config.apiUrl}/assignments/report`, {
1316
- method: "POST",
1317
- headers: {
1318
- "content-type": "application/json",
1319
- "x-api-key": this.config.publishableKey
1320
- },
1321
- body: JSON.stringify({ reports }),
1322
- keepalive: true
1323
- });
1324
- if (!response.ok) return;
1325
- } catch {
1326
- return;
1327
- }
1328
- this.queue = this.queue.slice(reports.length);
1329
- this.persistQueue();
1330
- }
1331
- getSessionId() {
1332
- return this.sessionId;
1333
- }
1334
- pendingReportCount() {
1335
- return this.queue.length;
1336
- }
1337
- freshSnapshot() {
1338
- if (!this.snapshot) return null;
1339
- if (this.snapshot.ttl === 0) return null;
1340
- const ageMs = this.now() - this.snapshot.generatedAt;
1341
- if (ageMs > this.snapshot.ttl * 1e3) return null;
1342
- return this.snapshot;
1343
- }
1344
- readSnapshot() {
1345
- if (!this.storage) return null;
1346
- try {
1347
- const raw = this.storage.getItem(STORAGE_KEY);
1348
- return raw ? JSON.parse(raw) : null;
1349
- } catch {
1350
- return null;
1351
- }
1352
- }
1353
- persistSnapshot(snapshot) {
1354
- if (!this.storage) return;
1355
- try {
1356
- this.storage.setItem(STORAGE_KEY, JSON.stringify(snapshot));
1357
- } catch {
1358
- }
1359
- }
1360
- readQueue() {
1361
- if (!this.storage) return [];
1362
- try {
1363
- const raw = this.storage.getItem(REPORT_QUEUE_KEY);
1364
- return raw ? JSON.parse(raw) : [];
1365
- } catch {
1366
- return [];
1367
- }
1368
- }
1369
- readDecisions() {
1370
- if (!this.storage) return {};
1371
- try {
1372
- const raw = this.storage.getItem(DECISIONS_KEY);
1373
- if (!raw) return {};
1374
- const parsed = JSON.parse(raw);
1375
- const now = this.now();
1376
- const live = {};
1377
- for (const [k, d] of Object.entries(parsed)) {
1378
- if (d && d.expiresAt > now) live[k] = d;
1379
- }
1380
- return live;
1381
- } catch {
1382
- return {};
1383
- }
1384
- }
1385
- writeDecisions(decisions) {
1386
- if (!this.storage) return;
1387
- try {
1388
- this.storage.setItem(DECISIONS_KEY, JSON.stringify(decisions));
1389
- } catch {
1390
- }
1391
- }
1392
- persistQueue() {
1393
- if (!this.storage) return;
1394
- try {
1395
- this.storage.setItem(REPORT_QUEUE_KEY, JSON.stringify(this.queue));
1396
- } catch {
1397
- }
1398
- }
1399
- };
1400
-
1401
1171
  // src/email-links.ts
1402
1172
  var UNTRACKABLE_SCHEME = /^(mailto:|tel:|sms:|javascript:|data:|#)/i;
1403
1173
  var NOTRACK_ATTRIBUTE = "data-bandit-notrack";
@@ -1596,6 +1366,7 @@ export {
1596
1366
  BanditsRequestError,
1597
1367
  CONTENT_TYPE_BY_KIND,
1598
1368
  ContentType,
1369
+ DECISIONS_COOKIE,
1599
1370
  DEFAULT_HALF_LIFE_MS,
1600
1371
  DEFAULT_SESSION_TTL_MS,
1601
1372
  DEFAULT_WINDOW_MS,
@@ -1626,6 +1397,7 @@ export {
1626
1397
  STOREFRONT_KINDS,
1627
1398
  SUGGESTED_SITE_EXPERIMENTS,
1628
1399
  SnapshotClient,
1400
+ VISITOR_COOKIE,
1629
1401
  VISITOR_ID_KEY,
1630
1402
  appendEmailToken,
1631
1403
  buildSubdomainUrl,
@@ -1658,6 +1430,7 @@ export {
1658
1430
  normalizeSiteTarget,
1659
1431
  normalizeTreatmentWeights,
1660
1432
  parseAlternative,
1433
+ parseDecisions,
1661
1434
  parsePanelMessage,
1662
1435
  parsePickerMessage,
1663
1436
  parseSiteContent,
@@ -1667,6 +1440,7 @@ export {
1667
1440
  pickStep,
1668
1441
  pickTreatment,
1669
1442
  platformById,
1443
+ readCookie,
1670
1444
  renderEmail,
1671
1445
  renderEmailDocument,
1672
1446
  renderFromPlan,
@@ -1675,6 +1449,7 @@ export {
1675
1449
  rewriteEmailLinks,
1676
1450
  sampleCombination,
1677
1451
  sanitizeMetadata,
1452
+ serializeDecisions,
1678
1453
  serializeTreatmentConfig,
1679
1454
  shouldRefreshPlan,
1680
1455
  targetMatchesPage,