@apex-inc/sdk 0.20.0 → 0.20.2
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/esm/index.js +57 -19
- package/dist/generated/event-payloads.d.ts +17 -586
- package/dist/generated/event-payloads.d.ts.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/apex-communications/SKILL.md +9 -8
- package/skills/apex-experimentation/SKILL.md +17 -3
- package/skills/apex-integration-cookbook/SKILL.md +4 -4
- package/skills/graduation-pipeline/SKILL.md +5 -1
package/dist/esm/index.js
CHANGED
|
@@ -366,15 +366,36 @@ function getExperimentVisitorId() {
|
|
|
366
366
|
document.cookie = `${key}=${encodeURIComponent(id)};expires=${d.toUTCString()};path=/;SameSite=Lax`;
|
|
367
367
|
return id;
|
|
368
368
|
}
|
|
369
|
-
function
|
|
370
|
-
if (!
|
|
369
|
+
function readExperimentPreview(search) {
|
|
370
|
+
if (!search)
|
|
371
371
|
return null;
|
|
372
|
-
const
|
|
372
|
+
const q = search.startsWith("?") ? search.slice(1) : search;
|
|
373
|
+
const params = new URLSearchParams(q);
|
|
373
374
|
const variant = params.get("_apex_preview");
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return
|
|
375
|
+
if (!isKnownVariant(variant))
|
|
376
|
+
return null;
|
|
377
|
+
const expId = params.get("_apex_exp")?.trim() || null;
|
|
378
|
+
return { variant, expId };
|
|
379
|
+
}
|
|
380
|
+
function getExperimentPreview(search) {
|
|
381
|
+
const resolved = search ?? (isBrowser ? window.location.search : "");
|
|
382
|
+
return readExperimentPreview(resolved);
|
|
383
|
+
}
|
|
384
|
+
async function fetchExperimentLineIds(apiBase, experimentId, workspaceKey) {
|
|
385
|
+
try {
|
|
386
|
+
const res = await fetch(`${apiBase}/api/experiments/${experimentId}/line?source=sdk`, {
|
|
387
|
+
credentials: "include",
|
|
388
|
+
headers: workspaceKey ? { "x-apex-workspace": workspaceKey } : {},
|
|
389
|
+
});
|
|
390
|
+
if (!res.ok)
|
|
391
|
+
return null;
|
|
392
|
+
const body = (await res.json());
|
|
393
|
+
const ids = body.data?.lineMemberIds ?? body.lineMemberIds;
|
|
394
|
+
return Array.isArray(ids) && ids.length > 0 ? ids : null;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
378
399
|
}
|
|
379
400
|
// DBS-1 — durable browser session id (`apex_sid`), GA4-parity 30-min
|
|
380
401
|
// inactivity rotation. Mirrors the snippet's cookie format
|
|
@@ -519,7 +540,8 @@ function fireExperimentExposure(experimentId, variant, apiBase, workspaceKey, bu
|
|
|
519
540
|
* the server cannot be reached.
|
|
520
541
|
* 4. Deterministic murmur-hash of `visitorId + experimentId` (fallback).
|
|
521
542
|
*
|
|
522
|
-
* Returns `"control"` during SSR / non-browser contexts
|
|
543
|
+
* Returns `"control"` during SSR / non-browser contexts — unless
|
|
544
|
+
* `opts.search` carries a matching `_apex_preview`. Fires the
|
|
523
545
|
* canonical `experiment_exposure` event (deduped) only when the visitor
|
|
524
546
|
* is still eligible.
|
|
525
547
|
*
|
|
@@ -529,20 +551,32 @@ function fireExperimentExposure(experimentId, variant, apiBase, workspaceKey, bu
|
|
|
529
551
|
* if (arm === "variant_a" || arm === "variant_b") renderVariant(); else renderControl();
|
|
530
552
|
*/
|
|
531
553
|
export async function getVariant(experimentId, opts = {}) {
|
|
554
|
+
const apiBase = (opts.apiUrl ?? config?.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
555
|
+
const workspaceKey = opts.workspaceKey ?? config?.workspaceKey;
|
|
556
|
+
const buildId = opts.buildId ?? config?.buildId;
|
|
557
|
+
// 1. Preview mode — author previewing, not a real exposure. Runs
|
|
558
|
+
// before the browser/consent gates so SSR and a declined cookie
|
|
559
|
+
// banner still show the URL arm. A family member's `_apex_exp`
|
|
560
|
+
// applies once we know the line.
|
|
561
|
+
const preview = getExperimentPreview(opts.search);
|
|
562
|
+
if (preview) {
|
|
563
|
+
const lineIds = opts.lineMemberIds ??
|
|
564
|
+
(isBrowser && preview.expId && preview.expId !== experimentId
|
|
565
|
+
? await fetchExperimentLineIds(apiBase, experimentId, workspaceKey)
|
|
566
|
+
: null);
|
|
567
|
+
if (!preview.expId ||
|
|
568
|
+
preview.expId === experimentId ||
|
|
569
|
+
(lineIds && lineIds.includes(experimentId) && lineIds.includes(preview.expId))) {
|
|
570
|
+
return preview.variant;
|
|
571
|
+
}
|
|
572
|
+
return "control";
|
|
573
|
+
}
|
|
532
574
|
if (!isBrowser)
|
|
533
575
|
return "control";
|
|
534
576
|
// Consent gate — without consent we neither persist a visitor id nor fire
|
|
535
577
|
// an exposure, so everyone deterministically sees control until they opt in.
|
|
536
578
|
if (!consentGranted)
|
|
537
579
|
return "control";
|
|
538
|
-
const apiBase = (opts.apiUrl ?? config?.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
539
|
-
const workspaceKey = opts.workspaceKey ?? config?.workspaceKey;
|
|
540
|
-
const buildId = opts.buildId ?? config?.buildId;
|
|
541
|
-
// 1. Preview mode — author previewing, not a real exposure.
|
|
542
|
-
const preview = getExperimentPreview();
|
|
543
|
-
if (preview && (!preview.expId || preview.expId === experimentId)) {
|
|
544
|
-
return preview.variant;
|
|
545
|
-
}
|
|
546
580
|
const cacheKey = `apex_var_${experimentId}`;
|
|
547
581
|
const cached = localStorage.getItem(cacheKey);
|
|
548
582
|
const visitorId = getExperimentVisitorId();
|
|
@@ -563,18 +597,22 @@ export async function getVariant(experimentId, opts = {}) {
|
|
|
563
597
|
if (!res.ok)
|
|
564
598
|
throw new Error(`${res.status}`);
|
|
565
599
|
const data = (await res.json());
|
|
600
|
+
const runId = data.experimentId || experimentId;
|
|
566
601
|
if (data.eligible === false) {
|
|
567
602
|
// Staging / TestFlight / localhost: serve the arm so they can QA.
|
|
568
603
|
// Do not fire an exposure — that is what would move the result.
|
|
569
604
|
if (data.reason === "non_production_audience") {
|
|
570
|
-
localStorage.setItem(
|
|
605
|
+
localStorage.setItem(`apex_var_${runId}`, data.variant);
|
|
571
606
|
return data.variant;
|
|
572
607
|
}
|
|
573
608
|
localStorage.setItem(cacheKey, "control");
|
|
574
609
|
return "control";
|
|
575
610
|
}
|
|
576
|
-
|
|
577
|
-
|
|
611
|
+
if (runId !== experimentId) {
|
|
612
|
+
localStorage.removeItem(cacheKey);
|
|
613
|
+
}
|
|
614
|
+
localStorage.setItem(`apex_var_${runId}`, data.variant);
|
|
615
|
+
fireExperimentExposure(runId, data.variant, apiBase, workspaceKey, buildId);
|
|
578
616
|
return data.variant;
|
|
579
617
|
}
|
|
580
618
|
catch {
|