@probat/react 0.4.5 → 0.4.7
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.d.mts +42 -9
- package/dist/index.d.ts +42 -9
- package/dist/index.js +327 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +325 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/Experiment.test.tsx +6 -5
- package/src/__tests__/Track.test.tsx +1 -1
- package/src/__tests__/eventQueue.test.ts +68 -0
- package/src/__tests__/setup.ts +55 -0
- package/src/__tests__/useExperiment.test.tsx +3 -2
- package/src/__tests__/useTrack.test.tsx +4 -4
- package/src/__tests__/utils.test.ts +24 -1
- package/src/context/ProbatContext.tsx +27 -5
- package/src/hooks/useExperiment.ts +28 -2
- package/src/hooks/useProbatMetrics.ts +47 -3
- package/src/index.ts +11 -1
- package/src/types/events.ts +48 -0
- package/src/utils/api.ts +30 -18
- package/src/utils/environment.ts +6 -5
- package/src/utils/eventContext.ts +117 -17
- package/src/utils/eventQueue.ts +157 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context/ProbatContext.tsx","../src/components/ProbatProviderClient.tsx","../src/utils/environment.ts","../src/utils/eventContext.ts","../src/utils/api.ts","../src/hooks/useExperiment.ts","../src/utils/dedupeStorage.ts","../src/utils/stableInstanceId.ts","../src/hooks/useTrack.ts","../src/components/Experiment.tsx","../src/components/Track.tsx","../src/hooks/useProbatMetrics.ts","../src/utils/createExperimentContext.ts"],"names":["React","useRef","useEffect","useMemo","useCallback","createContext","useContext"],"mappings":";;AAWA,IAAM,aAAA,GAAgB,cAAyC,IAAI,CAAA;AAEnE,IAAM,YAAA,GAAe,wBAAA;AAmBd,SAAS,cAAA,CAAe;AAAA,EAC3B,UAAA;AAAA,EACA,IAAA,GAAO,YAAA;AAAA,EACP,MAAA;AAAA,EACA,SAAA;AAAA,EACA;AACJ,CAAA,EAAwB;AACpB,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACV,OAAO;AAAA,MACH,IAAA,EAAM,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA,MAC5B,MAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA,EAAW,aAAa;AAAC,KAC7B,CAAA;AAAA,IACA,CAAC,UAAA,EAAY,IAAA,EAAM,MAAA,EAAQ,SAAS;AAAA,GACxC;AAEA,EAAA,uBACIA,MAAA,CAAA,aAAA,CAAC,aAAA,CAAc,QAAA,EAAd,EAAuB,SACnB,QACL,CAAA;AAER;AAEO,SAAS,gBAAA,GAAuC;AACnD,EAAA,MAAM,GAAA,GAAM,WAAW,aAAa,CAAA;AACpC,EAAA,IAAI,CAAC,GAAA,EAAK;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACN;AAAA,KACJ;AAAA,EACJ;AACA,EAAA,OAAO,GAAA;AACX;;;ACvCO,SAAS,qBAAqB,KAAA,EAA4B;AAC7D,EAAA,OAAOA,MAAAA,CAAM,aAAA,CAAc,cAAA,EAAgB,KAAK,CAAA;AACpD;;;ACvBO,SAAS,iBAAA,GAAoC;AAChD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,OAAO,MAAA;AAAA,EACX;AAEA,EAAA,MAAM,QAAA,GAAW,OAAO,QAAA,CAAS,QAAA;AAGjC,EAAA,IACI,aAAa,WAAA,IACb,QAAA,KAAa,WAAA,IACb,QAAA,KAAa,aACb,QAAA,CAAS,UAAA,CAAW,UAAU,CAAA,IAC9B,SAAS,UAAA,CAAW,KAAK,CAAA,IACzB,QAAA,CAAS,WAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,SAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,WAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,SAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,WAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,SAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,EAC/B;AACE,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,OAAO,MAAA;AACX;;;AClCA,IAAM,eAAA,GAAkB,oBAAA;AACxB,IAAM,cAAA,GAAiB,mBAAA;AAEvB,IAAI,gBAAA,GAAkC,IAAA;AACtC,IAAI,eAAA,GAAiC,IAAA;AAErC,SAAS,UAAA,GAAqB;AAE1B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,UAAA,EAAY;AACpD,IAAA,OAAO,OAAO,UAAA,EAAW;AAAA,EAC7B;AAEA,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,EAAE,CAAA;AAC/B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,eAAA,EAAiB;AACzD,IAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAAA,EAChC,CAAA,MAAO;AACH,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,EAAA,EAAI,CAAA,EAAA,EAAK,KAAA,CAAM,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,KAAW,GAAG,CAAA;AAAA,EAC1E;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAC5E;AAEO,SAAS,aAAA,GAAwB;AACpC,EAAA,IAAI,kBAAkB,OAAO,gBAAA;AAC7B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,QAAA;AAC1C,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,eAAe,CAAA;AACnD,IAAA,IAAI,MAAA,EAAQ;AACR,MAAA,gBAAA,GAAmB,MAAA;AACnB,MAAA,OAAO,MAAA;AAAA,IACX;AAAA,EACJ,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,MAAM,EAAA,GAAK,CAAA,KAAA,EAAQ,UAAA,EAAY,CAAA,CAAA;AAC/B,EAAA,gBAAA,GAAmB,EAAA;AACnB,EAAA,IAAI;AACA,IAAA,YAAA,CAAa,OAAA,CAAQ,iBAAiB,EAAE,CAAA;AAAA,EAC5C,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,OAAO,EAAA;AACX;AAEO,SAAS,YAAA,GAAuB;AACnC,EAAA,IAAI,iBAAiB,OAAO,eAAA;AAC5B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,QAAA;AAC1C,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAS,cAAA,CAAe,OAAA,CAAQ,cAAc,CAAA;AACpD,IAAA,IAAI,MAAA,EAAQ;AACR,MAAA,eAAA,GAAkB,MAAA;AAClB,MAAA,OAAO,MAAA;AAAA,IACX;AAAA,EACJ,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,MAAM,EAAA,GAAK,CAAA,KAAA,EAAQ,UAAA,EAAY,CAAA,CAAA;AAC/B,EAAA,eAAA,GAAkB,EAAA;AAClB,EAAA,IAAI;AACA,IAAA,cAAA,CAAe,OAAA,CAAQ,gBAAgB,EAAE,CAAA;AAAA,EAC7C,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,OAAO,EAAA;AACX;AAEO,SAAS,UAAA,GAAqB;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,EAAA;AAC1C,EAAA,OAAO,MAAA,CAAO,QAAA,CAAS,QAAA,GAAW,MAAA,CAAO,QAAA,CAAS,MAAA;AACtD;AAEO,SAAS,UAAA,GAAqB;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,EAAA;AAC1C,EAAA,OAAO,OAAO,QAAA,CAAS,IAAA;AAC3B;AAEO,SAAS,WAAA,GAAsB;AAClC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,EAAA;AAC5C,EAAA,OAAO,QAAA,CAAS,QAAA;AACpB;AAUO,SAAS,iBAAA,GAAkC;AAC9C,EAAA,OAAO;AAAA,IACH,aAAa,aAAA,EAAc;AAAA,IAC3B,YAAY,YAAA,EAAa;AAAA,IACzB,WAAW,UAAA,EAAW;AAAA,IACtB,WAAW,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,EAAA;AAAA,IACtE,WAAW,WAAA;AAAY,GAC3B;AACJ;;;AC5EA,IAAM,gBAAA,uBAAuB,GAAA,EAA6B;AAO1D,eAAsB,aAAA,CAClB,IAAA,EACA,YAAA,EACA,UAAA,EACA,MAAA,EACe;AACf,EAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,GAAA,CAAI,YAAY,CAAA;AAClD,EAAA,IAAI,UAAU,OAAO,QAAA;AAErB,EAAA,MAAM,WAAW,YAAY;AACzB,IAAA,IAAI;AACA,MAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,kBAAA,CAAA;AACtC,MAAA,MAAM,OAAA,GAAkC;AAAA,QACpC,cAAA,EAAgB,kBAAA;AAAA,QAChB,MAAA,EAAQ;AAAA,OACZ;AACA,MAAA,IAAI,MAAA,EAAQ,OAAA,CAAQ,eAAe,CAAA,GAAI,UAAU,MAAM,CAAA,CAAA;AACvD,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QACzB,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA;AAAA,QACA,WAAA,EAAa,SAAA;AAAA,QACb,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACjB,aAAA,EAAe,YAAA;AAAA,UACf,WAAA,EAAa;AAAA,SAChB;AAAA,OACJ,CAAA;AACD,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACjD,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,MAAA,OAAO,KAAK,WAAA,IAAe,SAAA;AAAA,IAC/B,CAAA,SAAE;AACE,MAAA,gBAAA,CAAiB,OAAO,YAAY,CAAA;AAAA,IACxC;AAAA,EACJ,CAAA,GAAG;AAEH,EAAA,gBAAA,CAAiB,GAAA,CAAI,cAAc,OAAO,CAAA;AAC1C,EAAA,OAAO,OAAA;AACX;AAOO,SAAS,UAAA,CACZ,IAAA,EACA,KAAA,EACA,UAAA,EACA,MAAA,EACI;AACJ,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,EAAA,MAAM,MAAM,iBAAA,EAAkB;AAC9B,EAAA,MAAM,OAAA,GAAyB;AAAA,IAC3B,KAAA;AAAA,IACA,aAAa,iBAAA,EAAkB;AAAA,IAC/B,UAAA,EAAY;AAAA,MACR,GAAG,GAAA;AAAA,MACH,MAAA,EAAQ,WAAA;AAAA,MACR,WAAA,EAAA,iBAAa,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MACpC,GAAG;AAAA;AACP,GACJ;AAEA,EAAA,IAAI;AACA,IAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,mBAAA,CAAA;AACtC,IAAA,MAAM,OAAA,GAAkC,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAC7E,IAAA,IAAI,MAAA,EAAQ,OAAA,CAAQ,eAAe,CAAA,GAAI,UAAU,MAAM,CAAA,CAAA;AACvD,IAAA,KAAA,CAAM,GAAA,EAAK;AAAA,MACP,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA;AAAA,MACA,WAAA,EAAa,SAAA;AAAA,MACb,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC/B,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EACrB,CAAA,CAAA,MAAQ;AAAA,EAER;AACJ;AAgBO,SAAS,iBAAiB,MAAA,EAA8C;AAC3E,EAAA,IAAI,CAAC,MAAA,IAAU,EAAE,MAAA,YAAkB,cAAc,OAAO,IAAA;AAGxD,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,+BAA+B,CAAA;AAC9D,EAAA,IAAI,OAAA,EAAS,OAAO,SAAA,CAAU,OAAA,EAAwB,IAAI,CAAA;AAG1D,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,OAAA,CAAQ,4BAA4B,CAAA;AAC/D,EAAA,IAAI,WAAA,EAAa,OAAO,SAAA,CAAU,WAAA,EAA4B,KAAK,CAAA;AAEnE,EAAA,OAAO,IAAA;AACX;AAEA,SAAS,SAAA,CAAU,IAAiB,SAAA,EAA+B;AAC/D,EAAA,MAAM,IAAA,GAAkB;AAAA,IACpB,kBAAkB,EAAA,CAAG,OAAA;AAAA,IACrB,gBAAA,EAAkB;AAAA,GACtB;AACA,EAAA,IAAI,EAAA,CAAG,EAAA,EAAI,IAAA,CAAK,eAAA,GAAkB,EAAA,CAAG,EAAA;AACrC,EAAA,MAAM,IAAA,GAAO,EAAA,CAAG,WAAA,EAAa,IAAA,EAAK;AAClC,EAAA,IAAI,MAAM,IAAA,CAAK,iBAAA,GAAoB,IAAA,CAAK,KAAA,CAAM,GAAG,GAAG,CAAA;AACpD,EAAA,OAAO,IAAA;AACX;;;AClIA,IAAM,iBAAA,GAAoB,oBAAA;AAOnB,SAAS,eAAe,EAAA,EAA2B;AACtD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACA,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,OAAA,CAAQ,iBAAA,GAAoB,EAAE,CAAA;AACvD,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,MAAM,MAAA,GAA2B,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC/C,IAAA,OAAO,OAAO,UAAA,IAAc,IAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,IAAA;AAAA,EACX;AACJ;AAEO,SAAS,eAAA,CAAgB,IAAY,UAAA,EAA0B;AAClE,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACA,IAAA,MAAM,QAA0B,EAAE,UAAA,EAAY,EAAA,EAAI,IAAA,CAAK,KAAI,EAAE;AAC7D,IAAA,YAAA,CAAa,QAAQ,iBAAA,GAAoB,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,EACtE,CAAA,CAAA,MAAQ;AAAA,EAAC;AACb;AA0BO,SAAS,aAAA,CACZ,EAAA,EACA,OAAA,GAAgC,EAAC,EACd;AACnB,EAAA,MAAM,EAAE,QAAA,GAAW,SAAA,EAAW,KAAA,GAAQ,OAAM,GAAI,OAAA;AAChD,EAAA,MAAM,EAAE,IAAA,EAAM,SAAA,EAAW,UAAA,EAAY,MAAA,KAAW,gBAAA,EAAiB;AAEjE,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAiB,MAAM;AACvD,IAAA,IAAI,SAAA,CAAU,EAAE,CAAA,EAAG,OAAO,UAAU,EAAE,CAAA;AACtC,IAAA,OAAO,SAAA;AAAA,EACX,CAAC,CAAA;AACD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAkB,MAAM;AACpD,IAAA,OAAO,CAAC,CAAC,SAAA,CAAU,EAAE,CAAA;AAAA,EACzB,CAAC,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,IAAI,SAAA,CAAU,EAAE,CAAA,IAAK,cAAA,CAAe,EAAE,CAAA,EAAG;AACrC,MAAA,MAAM,MAAM,SAAA,CAAU,EAAE,CAAA,IAAK,cAAA,CAAe,EAAE,CAAA,IAAK,SAAA;AACnD,MAAA,aAAA,CAAc,GAAG,CAAA;AACjB,MAAA,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,SAAA,GAAY,KAAA;AAEhB,IAAA,CAAC,YAAY;AACT,MAAA,IAAI;AACA,QAAA,MAAM,UAAA,GAAa,cAAc,aAAA,EAAc;AAC/C,QAAA,MAAM,MAAM,MAAM,aAAA,CAAc,IAAA,EAAM,EAAA,EAAI,YAAY,MAAM,CAAA;AAC5D,QAAA,IAAI,SAAA,EAAW;AAEf,QAAA,aAAA,CAAc,GAAG,CAAA;AACjB,QAAA,eAAA,CAAgB,IAAI,GAAG,CAAA;AAAA,MAC3B,SAAS,GAAA,EAAK;AACV,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,IAAI,KAAA,EAAO;AACP,UAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,mCAAA,EAAsC,EAAE,CAAA,EAAA,CAAA,EAAM,GAAG,CAAA;AAAA,QACnE;AACA,QAAA,IAAI,QAAA,KAAa,WAAW,MAAM,GAAA;AAClC,QAAA,aAAA,CAAc,SAAS,CAAA;AAAA,MAC3B,CAAA,SAAE;AACE,QAAA,IAAI,CAAC,SAAA,EAAW,WAAA,CAAY,IAAI,CAAA;AAAA,MACpC;AAAA,IACJ,CAAA,GAAG;AAEH,IAAA,OAAO,MAAM;AACT,MAAA,SAAA,GAAY,IAAA;AAAA,IAChB,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,EAAA,EAAI,IAAI,CAAC,CAAA;AAEb,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,IAAI,SAAS,QAAA,EAAU;AACnB,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,EAAE,CAAA,cAAA,EAAiB,UAAU,CAAA,CAAA,CAAG,CAAA;AAAA,IACxE;AAAA,EACJ,GAAG,CAAC,KAAA,EAAO,EAAA,EAAI,UAAA,EAAY,QAAQ,CAAC,CAAA;AAEpC,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAClC;;;AC/GA,IAAM,MAAA,GAAS,cAAA;AACf,IAAM,SAAA,uBAAgB,GAAA,EAAY;AAE3B,SAAS,aAAA,CACZ,YAAA,EACA,UAAA,EACA,UAAA,EACA,OAAA,EACM;AACN,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,YAAY,IAAI,UAAU,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AAC1E;AAEO,SAAS,QAAQ,GAAA,EAAsB;AAC1C,EAAA,IAAI,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,IAAA;AAC/B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,IAAI;AACA,IAAA,OAAO,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA,KAAM,GAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,KAAA;AAAA,EACX;AACJ;AAEO,SAAS,SAAS,GAAA,EAAmB;AACxC,EAAA,SAAA,CAAU,IAAI,GAAG,CAAA;AACjB,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACA,IAAA,cAAA,CAAe,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,EACnC,CAAA,CAAA,MAAQ;AAAA,EAAC;AACb;ACjBA,IAAM,eAAA,GAAkB,kBAAA;AAIxB,SAAS,OAAA,GAAkB;AACvB,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,CAAC,CAAA;AAC9B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,eAAA,EAAiB;AACzD,IAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAAA,EAChC,CAAA,MAAO;AACH,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK,KAAA,CAAM,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,KAAW,GAAG,CAAA;AAAA,EACzE;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAC5E;AAKA,SAAS,gBAAgB,UAAA,EAA4B;AACjD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,IAAI;AACA,MAAA,MAAM,MAAA,GAAS,cAAA,CAAe,OAAA,CAAQ,UAAU,CAAA;AAChD,MAAA,IAAI,QAAQ,OAAO,MAAA;AAAA,IACvB,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACb;AACA,EAAA,MAAM,EAAA,GAAK,CAAA,KAAA,EAAQ,OAAA,EAAS,CAAA,CAAA;AAC5B,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,IAAI;AACA,MAAA,cAAA,CAAe,OAAA,CAAQ,YAAY,EAAE,CAAA;AAAA,IACzC,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACb;AACA,EAAA,OAAO,EAAA;AACX;AAOA,IAAM,YAAA,uBAAmB,GAAA,EAAoB;AAC7C,IAAI,cAAA,GAAiB,KAAA;AAErB,SAAS,UAAU,QAAA,EAA0B;AACzC,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,IAAK,CAAA;AAC1C,EAAA,YAAA,CAAa,GAAA,CAAI,QAAA,EAAU,GAAA,GAAM,CAAC,CAAA;AAClC,EAAA,IAAI,CAAC,cAAA,EAAgB;AACjB,IAAA,cAAA,GAAiB,IAAA;AACjB,IAAA,OAAA,CAAQ,OAAA,EAAQ,CAAE,IAAA,CAAK,MAAM;AACzB,MAAA,YAAA,CAAa,KAAA,EAAM;AACnB,MAAA,cAAA,GAAiB,KAAA;AAAA,IACrB,CAAC,CAAA;AAAA,EACL;AACA,EAAA,OAAO,GAAA;AACX;AAIA,SAAS,uBAAuB,YAAA,EAA8B;AAC1D,EAAA,MAAM,OAAA,GAAWA,OAAc,KAAA,EAAM;AACrC,EAAA,MAAM,GAAA,GAAM,OAAO,EAAE,CAAA;AACrB,EAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AACd,IAAA,MAAM,GAAA,GAAM,GAAG,eAAe,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,UAAA,EAAY,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AACxE,IAAA,GAAA,CAAI,OAAA,GAAU,gBAAgB,GAAG,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,GAAA,CAAI,OAAA;AACf;AAIA,SAAS,4BAA4B,YAAA,EAA8B;AAC/D,EAAA,MAAM,OAAA,GAAU,OAAO,EAAE,CAAA;AACzB,EAAA,MAAM,GAAA,GAAM,OAAO,EAAE,CAAA;AACrB,EAAA,IAAI,OAAA,CAAQ,YAAY,EAAA,EAAI;AACxB,IAAA,OAAA,CAAQ,UAAU,SAAA,CAAU,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,UAAA,EAAY,CAAA,CAAE,CAAA;AAAA,EACjE;AACA,EAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AACd,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,eAAe,CAAA,EAAG,YAAY,IAAI,UAAA,EAAY,CAAA,CAAA,EAAI,OAAA,CAAQ,OAAO,CAAA,CAAA;AAChF,IAAA,GAAA,CAAI,OAAA,GAAU,gBAAgB,GAAG,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,GAAA,CAAI,OAAA;AACf;AAMO,IAAM,mBAAA,GACT,OAAQA,MAAAA,CAAc,KAAA,KAAU,aAC1B,sBAAA,GACA,2BAAA;;;ACxCH,SAAS,SAAS,OAAA,EAA+D;AACpF,EAAA,MAAM;AAAA,IACF,YAAA;AAAA,IACA,mBAAA;AAAA,IACA,QAAA,GAAW,IAAA;AAAA,IACX,YAAY,eAAA,GAAkB,IAAA;AAAA,IAC9B,OAAO,UAAA,GAAa,IAAA;AAAA,IACpB,mBAAA,GAAsB,sBAAA;AAAA,IACtB,cAAA,GAAiB,mBAAA;AAAA,IACjB,KAAA,GAAQ;AAAA,GACZ,GAAI,OAAA;AAEJ,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,MAAA;AACzC,EAAA,MAAM,kBAAA,GAAqB,YAAA,IAAgB,OAAA,GAAU,OAAA,CAAQ,UAAA,GAAa,MAAA;AAE1E,EAAA,MAAM,EAAE,IAAA,EAAM,UAAA,EAAY,kBAAA,EAAoB,MAAA,KAAW,gBAAA,EAAiB;AAG1E,EAAA,MAAM,qBAAqB,kBAAA,IAAsB,kBAAA;AACjD,EAAA,MAAM,iBAAiB,CAAC,UAAA;AAExB,EAAA,MAAM,cAAA,GAAiB,oBAAoB,YAAY,CAAA;AACvD,EAAA,MAAM,aAAa,mBAAA,IAAuB,cAAA;AAE1C,EAAA,MAAM,YAAA,GAAeC,OAA2B,IAAI,CAAA;AACpD,EAAA,MAAM,cAAA,GAAiBA,OAAO,KAAK,CAAA;AAGnC,EAAAC,UAAU,MAAM;AACZ,IAAA,IAAI,cAAA,IAAkB,CAAC,kBAAA,IAAsB,KAAA,EAAO;AAChD,MAAA,OAAA,CAAQ,IAAA;AAAA,QACJ,gFACkB,YAAY,CAAA,2CAAA;AAAA,OAClC;AAAA,IACJ;AAAA,EACJ,GAAG,CAAC,cAAA,EAAgB,kBAAA,EAAoB,YAAA,EAAc,KAAK,CAAC,CAAA;AAE5D,EAAA,MAAM,UAAA,GAAaC,OAAAA;AAAA,IACf,OAAO;AAAA,MACH,aAAA,EAAe,YAAA;AAAA,MACf,GAAI,UAAA,GAAa,EAAE,WAAA,EAAa,UAAA,KAAe,EAAC;AAAA,MAChD,qBAAA,EAAuB,UAAA;AAAA,MACvB,GAAI,kBAAA,GAAqB,EAAE,WAAA,EAAa,kBAAA,KAAuB;AAAC,KACpE,CAAA;AAAA,IACA,CAAC,YAAA,EAAc,UAAA,EAAY,UAAA,EAAY,kBAAkB;AAAA,GAC7D;AAKA,EAAA,MAAM,aAAA,GAAgB,cAAc,kBAAA,IAAsB,UAAA;AAE1D,EAAAD,UAAU,MAAM;AACZ,IAAA,IAAI,CAAC,eAAA,IAAmB,CAAC,QAAA,EAAU;AAEnC,IAAA,cAAA,CAAe,OAAA,GAAU,KAAA;AAEzB,IAAA,MAAM,UAAU,UAAA,EAAW;AAC3B,IAAA,MAAM,SAAA,GAAY,aAAA,CAAc,YAAA,EAAc,aAAA,EAAe,YAAY,OAAO,CAAA;AAEhF,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACpB,MAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,KAAK,YAAA,CAAa,OAAA;AACxB,IAAA,IAAI,CAAC,EAAA,EAAI;AAGT,IAAA,IAAI,OAAO,yBAAyB,WAAA,EAAa;AAC7C,MAAA,IAAI,CAAC,eAAe,OAAA,EAAS;AACzB,QAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,QAAA,QAAA,CAAS,SAAS,CAAA;AAClB,QAAA,UAAA,CAAW,IAAA,EAAM,mBAAA,EAAqB,UAAA,EAAY,MAAM,CAAA;AACxD,QAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,sCAAA,EAAyC,YAAY,CAAA,CAAA,CAAG,CAAA;AAAA,MACnF;AACA,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,KAAA,GAA8C,IAAA;AAElD,IAAA,MAAM,WAAW,IAAI,oBAAA;AAAA,MACjB,CAAC,CAAC,KAAK,CAAA,KAAM;AACT,QAAA,IAAI,CAAC,KAAA,IAAS,cAAA,CAAe,OAAA,EAAS;AAEtC,QAAA,IAAI,MAAM,cAAA,EAAgB;AACtB,UAAA,KAAA,GAAQ,WAAW,MAAM;AACrB,YAAA,IAAI,eAAe,OAAA,EAAS;AAC5B,YAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,YAAA,QAAA,CAAS,SAAS,CAAA;AAClB,YAAA,UAAA,CAAW,IAAA,EAAM,mBAAA,EAAqB,UAAA,EAAY,MAAM,CAAA;AACxD,YAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,8BAAA,EAAiC,YAAY,CAAA,CAAA,CAAG,CAAA;AACvE,YAAA,QAAA,CAAS,UAAA,EAAW;AAAA,UACxB,GAAG,GAAG,CAAA;AAAA,QACV,WAAW,KAAA,EAAO;AACd,UAAA,YAAA,CAAa,KAAK,CAAA;AAClB,UAAA,KAAA,GAAQ,IAAA;AAAA,QACZ;AAAA,MACJ,CAAA;AAAA,MACA,EAAE,WAAW,GAAA;AAAI,KACrB;AAEA,IAAA,QAAA,CAAS,QAAQ,EAAE,CAAA;AAEnB,IAAA,OAAO,MAAM;AACT,MAAA,QAAA,CAAS,UAAA,EAAW;AACpB,MAAA,IAAI,KAAA,eAAoB,KAAK,CAAA;AAAA,IACjC,CAAA;AAAA,EACJ,CAAA,EAAG;AAAA,IACC,eAAA;AAAA,IACA,QAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAA;AAAA,IACA,IAAA;AAAA,IACA,mBAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACH,CAAA;AAID,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAChB,CAAC,CAAA,KAAa;AACV,MAAA,IAAI,CAAC,UAAA,IAAc,CAAC,QAAA,EAAU;AAE9B,MAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,CAAA,CAAE,MAAqB,CAAA;AACrD,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,UAAA,CAAW,MAAM,cAAA,EAAgB;AAAA,QAC7B,GAAG,UAAA;AAAA,QACH,GAAG;AAAA,SACJ,MAAM,CAAA;AACT,MAAA,IAAI,KAAA,EAAO;AACP,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,4BAAA,EAA+B,YAAY,CAAA,CAAA,CAAA,EAAK,IAAI,CAAA;AAAA,MACpE;AAAA,IACJ,CAAA;AAAA,IACA,CAAC,UAAA,EAAY,QAAA,EAAU,MAAM,cAAA,EAAgB,UAAA,EAAY,cAAc,KAAK;AAAA,GAChF;AAEA,EAAAA,UAAU,MAAM;AACZ,IAAA,MAAM,KAAK,YAAA,CAAa,OAAA;AACxB,IAAA,IAAI,CAAC,EAAA,IAAM,CAAC,UAAA,EAAY;AAExB,IAAA,EAAA,CAAG,gBAAA,CAAiB,SAAS,WAAW,CAAA;AACxC,IAAA,OAAO,MAAM;AACT,MAAA,EAAA,CAAG,mBAAA,CAAoB,SAAS,WAAW,CAAA;AAAA,IAC/C,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,WAAA,EAAa,UAAU,CAAC,CAAA;AAE5B,EAAA,OAAO,YAAA;AACX;;;ACpLO,SAAS,UAAA,CAAW;AAAA,EACvB,EAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,QAAA,GAAW,SAAA;AAAA,EACX,KAAA,GAAQ;AACZ,CAAA,EAAoB;AAGhB,EAAA,MAAM,EAAE,UAAA,EAAY,MAAA,EAAQ,QAAA,EAAS,GAAI,cAAc,EAAA,EAAI,EAAE,QAAA,EAAU,KAAA,EAAO,CAAA;AAG9E,EAAA,MAAM,UAAA,GACF,MAAA,KAAW,SAAA,IAAa,MAAA,IAAU,WAAW,MAAA,GAAS,SAAA;AAE1D,EAAA,IAAI,KAAA,IAAS,WAAW,UAAA,EAAY;AAChC,IAAA,OAAA,CAAQ,IAAA;AAAA,MACJ,CAAA,0BAAA,EAA6B,MAAM,CAAA,kBAAA,EAAqB,EAAE,CAAA,0BAAA;AAAA,KAC9D;AAAA,EACJ;AAIA,EAAA,MAAM,WAAW,QAAA,CAAS;AAAA,IACtB,YAAA,EAAc,EAAA;AAAA,IACd,UAAA;AAAA,IACA,mBAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA,EAAY,OAAO,UAAA,KAAe,KAAA;AAAA,IAClC,KAAA,EAAO,OAAO,YAAA,KAAiB,KAAA;AAAA,IAC/B,qBAAqB,KAAA,EAAO,mBAAA;AAAA,IAC5B,gBAAgB,KAAA,EAAO,cAAA;AAAA,IACvB;AAAA,GACH,CAAA;AAID,EAAA,MAAM,OAAA,GACF,eAAe,SAAA,IAAa,EAAE,cAAc,QAAA,CAAA,GACtC,OAAA,GACA,SAAS,UAAU,CAAA;AAE7B,EAAA,uBACIF,MAAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACG,GAAA,EAAK,QAAA;AAAA,MACL,wBAAA,EAAwB,EAAA;AAAA,MACxB,qBAAA,EAAqB,UAAA;AAAA,MACrB,KAAA,EAAO;AAAA,QACH,OAAA,EAAS,OAAA;AAAA,QACT,MAAA,EAAQ,CAAA;AAAA,QACR,OAAA,EAAS,CAAA;AAAA,QACT,OAAA,EAAS,WAAW,CAAA,GAAI,CAAA;AAAA,QACxB,UAAA,EAAY,WAAW,uBAAA,GAA0B;AAAA;AACrD,KAAA;AAAA,IAEC;AAAA,GACL;AAER;AC5EO,SAAS,KAAA,CAAM,EAAE,QAAA,EAAU,GAAG,cAAa,EAAe;AAC7D,EAAA,MAAM,QAAA,GAAW,SAAS,YAAY,CAAA;AAEtC,EAAA,uBACIA,MAAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACG,GAAA,EAAK,QAAA;AAAA,MACL,qBAAmB,YAAA,CAAa,YAAA;AAAA,MAChC,qBAAA,EAAqB,aAAa,UAAA,IAAc,iBAAA;AAAA,MAChD,KAAA,EAAO,EAAE,OAAA,EAAS,UAAA;AAAW,KAAA;AAAA,IAE5B;AAAA,GACL;AAER;ACTO,SAAS,gBAAA,GAA2C;AACvD,EAAA,MAAM,EAAE,IAAA,EAAM,UAAA,EAAY,MAAA,KAAW,gBAAA,EAAiB;AAEtD,EAAA,MAAM,OAAA,GAAUI,WAAAA;AAAA,IACZ,CAAC,KAAA,EAAe,UAAA,GAAsC,EAAC,KAAM;AACzD,MAAA,UAAA,CAAW,MAAM,KAAA,EAAO;AAAA,QACpB,GAAI,UAAA,GAAa,EAAE,WAAA,EAAa,UAAA,KAAe,EAAC;AAAA,QAChD,GAAG;AAAA,SACJ,MAAM,CAAA;AAAA,IACb,CAAA;AAAA,IACA,CAAC,IAAA,EAAM,UAAA,EAAY,MAAM;AAAA,GAC7B;AAEA,EAAA,OAAO,EAAE,OAAA,EAAQ;AACrB;AChBO,SAAS,wBAAwB,YAAA,EAAsB;AAC1D,EAAA,MAAM,GAAA,GAAMC,cAA6B,IAAI,CAAA;AAE7C,EAAA,SAAS,kBAAA,CAAmB;AAAA,IACxB,KAAA;AAAA,IACA;AAAA,GACJ,EAGG;AACC,IAAA,OAAOL,OAAM,aAAA,CAAc,GAAA,CAAI,UAAU,EAAE,KAAA,IAAS,QAAQ,CAAA;AAAA,EAChE;AAEA,EAAA,SAAS,aAAA,GAAwB;AAC7B,IAAA,MAAM,CAAA,GAAIM,WAAW,GAAG,CAAA;AACxB,IAAA,IAAI,MAAM,IAAA,EAAM;AACZ,MAAA,MAAM,IAAI,KAAA;AAAA,QACN,iEAAiE,YAAY,CAAA,CAAA;AAAA,OACjF;AAAA,IACJ;AACA,IAAA,OAAO,CAAA;AAAA,EACX;AAEA,EAAA,OAAO,EAAE,oBAAoB,aAAA,EAAc;AAC/C","file":"index.mjs","sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext, useMemo } from \"react\";\n\nexport interface ProbatContextValue {\n host: string;\n apiKey?: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://api.probat.app\";\n\nexport interface ProbatProviderProps {\n /** Your end-user's ID. When provided, used as the distinct_id for variant\n * assignment (consistent across devices) and attached to all events. */\n customerId?: string;\n /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */\n host?: string;\n /** API key for authenticating SDK requests (probat_sk_...) */\n apiKey?: string;\n /**\n * Bootstrap assignments to avoid flash on first render.\n * Map of experiment id → variant key.\n * e.g. { \"cta-copy-test\": \"ai_v1\" }\n */\n bootstrap?: Record<string, string>;\n children: React.ReactNode;\n}\n\nexport function ProbatProvider({\n customerId,\n host = DEFAULT_HOST,\n apiKey,\n bootstrap,\n children,\n}: ProbatProviderProps) {\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: host.replace(/\\/$/, \"\"),\n apiKey,\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, host, apiKey, bootstrap]\n );\n\n return (\n <ProbatContext.Provider value={value}>\n {children}\n </ProbatContext.Provider>\n );\n}\n\nexport function useProbatContext(): ProbatContextValue {\n const ctx = useContext(ProbatContext);\n if (!ctx) {\n throw new Error(\n \"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { ProbatProvider } from \"../context/ProbatContext\";\nimport type { ProbatProviderProps } from \"../context/ProbatContext\";\n\n/**\n * Client-only provider for Next.js App Router.\n * Import this in your layout/providers file.\n *\n * @example\n * ```tsx\n * // app/providers.tsx\n * \"use client\";\n * import { ProbatProviderClient } from \"@probat/react\";\n *\n * export function Providers({ children }) {\n * return (\n * <ProbatProviderClient customerId={user.id}>\n * {children}\n * </ProbatProviderClient>\n * );\n * }\n * ```\n */\nexport function ProbatProviderClient(props: ProbatProviderProps) {\n return React.createElement(ProbatProvider, props);\n}\n\nexport type { ProbatProviderProps };\n","/**\n * Detect if the code is running on localhost (development environment).\n * Returns \"dev\" for localhost, \"prod\" for production.\n */\nexport function detectEnvironment(): \"dev\" | \"prod\" {\n if (typeof window === \"undefined\") {\n return \"prod\"; // Server-side, default to prod\n }\n\n const hostname = window.location.hostname;\n\n // Check for localhost, 127.0.0.1, or local IP addresses\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname.startsWith(\"192.168.\") ||\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"172.16.\") ||\n hostname.startsWith(\"172.17.\") ||\n hostname.startsWith(\"172.18.\") ||\n hostname.startsWith(\"172.19.\") ||\n hostname.startsWith(\"172.20.\") ||\n hostname.startsWith(\"172.21.\") ||\n hostname.startsWith(\"172.22.\") ||\n hostname.startsWith(\"172.23.\") ||\n hostname.startsWith(\"172.24.\") ||\n hostname.startsWith(\"172.25.\") ||\n hostname.startsWith(\"172.26.\") ||\n hostname.startsWith(\"172.27.\") ||\n hostname.startsWith(\"172.28.\") ||\n hostname.startsWith(\"172.29.\") ||\n hostname.startsWith(\"172.30.\") ||\n hostname.startsWith(\"172.31.\")\n ) {\n return \"dev\";\n }\n\n return \"prod\";\n}\n\n","/**\n * Event context helpers: distinct_id, session_id, page info.\n * All browser-safe — no-ops when window is unavailable.\n */\n\nconst DISTINCT_ID_KEY = \"probat:distinct_id\";\nconst SESSION_ID_KEY = \"probat:session_id\";\n\nlet cachedDistinctId: string | null = null;\nlet cachedSessionId: string | null = null;\n\nfunction generateId(): string {\n // crypto.randomUUID where available, else fallback\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // fallback: random hex\n const bytes = new Uint8Array(16);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function getDistinctId(): string {\n if (cachedDistinctId) return cachedDistinctId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = localStorage.getItem(DISTINCT_ID_KEY);\n if (stored) {\n cachedDistinctId = stored;\n return stored;\n }\n } catch {}\n const id = `anon_${generateId()}`;\n cachedDistinctId = id;\n try {\n localStorage.setItem(DISTINCT_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getSessionId(): string {\n if (cachedSessionId) return cachedSessionId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = sessionStorage.getItem(SESSION_ID_KEY);\n if (stored) {\n cachedSessionId = stored;\n return stored;\n }\n } catch {}\n const id = `sess_${generateId()}`;\n cachedSessionId = id;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getPageKey(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.pathname + window.location.search;\n}\n\nexport function getPageUrl(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.href;\n}\n\nexport function getReferrer(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.referrer;\n}\n\nexport interface EventContext {\n distinct_id: string;\n session_id: string;\n $page_url: string;\n $pathname: string;\n $referrer: string;\n}\n\nexport function buildEventContext(): EventContext {\n return {\n distinct_id: getDistinctId(),\n session_id: getSessionId(),\n $page_url: getPageUrl(),\n $pathname: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n $referrer: getReferrer(),\n };\n}\n","import { detectEnvironment } from \"./environment\";\nimport { buildEventContext } from \"./eventContext\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface DecisionResponse {\n variant_key: string;\n}\n\nexport interface MetricPayload {\n event: string;\n environment: \"dev\" | \"prod\";\n properties: Record<string, unknown>;\n}\n\n// ── Assignment fetching ────────────────────────────────────────────────────\n\nconst pendingDecisions = new Map<string, Promise<string>>();\n\n/**\n * Fetch the variant assignment for an experiment.\n * Returns the variant key string (e.g. \"control\", \"ai_v1\").\n * Deduplicates concurrent calls for the same experiment.\n */\nexport async function fetchDecision(\n host: string,\n experimentId: string,\n distinctId: string,\n apiKey?: string\n): Promise<string> {\n const existing = pendingDecisions.get(experimentId);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/decide`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n };\n if (apiKey) headers[\"Authorization\"] = `Bearer ${apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify({\n experiment_id: experimentId,\n distinct_id: distinctId,\n }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const data = (await res.json()) as DecisionResponse;\n return data.variant_key || \"control\";\n } finally {\n pendingDecisions.delete(experimentId);\n }\n })();\n\n pendingDecisions.set(experimentId, promise);\n return promise;\n}\n\n// ── Metric sending ─────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget metric send. Never throws.\n */\nexport function sendMetric(\n host: string,\n event: string,\n properties: Record<string, unknown>,\n apiKey?: string\n): void {\n if (typeof window === \"undefined\") return;\n\n const ctx = buildEventContext();\n const payload: MetricPayload = {\n event,\n environment: detectEnvironment(),\n properties: {\n ...ctx,\n source: \"react-sdk\",\n captured_at: new Date().toISOString(),\n ...properties,\n },\n };\n\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/metrics`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (apiKey) headers[\"Authorization\"] = `Bearer ${apiKey}`;\n fetch(url, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify(payload),\n }).catch(() => {});\n } catch {\n // silently drop\n }\n}\n\n// ── Click metadata extraction ──────────────────────────────────────────────\n\nexport interface ClickMeta {\n click_target_tag: string;\n click_target_text?: string;\n click_target_id?: string;\n click_is_primary: boolean;\n}\n\n/**\n * Given a click event inside an experiment boundary, extract metadata.\n * Prioritizes elements with data-probat-click=\"primary\",\n * then falls back to button/a/role=button.\n */\nexport function extractClickMeta(target: EventTarget | null): ClickMeta | null {\n if (!target || !(target instanceof HTMLElement)) return null;\n\n // Priority 1: explicit primary marker\n const primary = target.closest('[data-probat-click=\"primary\"]');\n if (primary) return buildMeta(primary as HTMLElement, true);\n\n // Priority 2: interactive elements\n const interactive = target.closest('button, a, [role=\"button\"]');\n if (interactive) return buildMeta(interactive as HTMLElement, false);\n\n return null;\n}\n\nfunction buildMeta(el: HTMLElement, isPrimary: boolean): ClickMeta {\n const meta: ClickMeta = {\n click_target_tag: el.tagName,\n click_is_primary: isPrimary,\n };\n if (el.id) meta.click_target_id = el.id;\n const text = el.textContent?.trim();\n if (text) meta.click_target_text = text.slice(0, 120);\n return meta;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { fetchDecision } from \"../utils/api\";\nimport { getDistinctId } from \"../utils/eventContext\";\n\n// ── localStorage assignment cache ──────────────────────────────────────────\n\nconst ASSIGNMENT_PREFIX = \"probat:assignment:\";\n\ninterface StoredAssignment {\n variantKey: string;\n ts: number;\n}\n\nexport function readAssignment(id: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(ASSIGNMENT_PREFIX + id);\n if (!raw) return null;\n const parsed: StoredAssignment = JSON.parse(raw);\n return parsed.variantKey ?? null;\n } catch {\n return null;\n }\n}\n\nexport function writeAssignment(id: string, variantKey: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const entry: StoredAssignment = { variantKey, ts: Date.now() };\n localStorage.setItem(ASSIGNMENT_PREFIX + id, JSON.stringify(entry));\n } catch {}\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseExperimentOptions {\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions to console */\n debug?: boolean;\n}\n\nexport interface UseExperimentReturn {\n /** The resolved variant key (e.g. \"control\", \"ai_v1\") */\n variantKey: string;\n /** Whether the assignment has been resolved (bootstrap, cache, or fetch) */\n resolved: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Resolves the variant assignment for an experiment.\n * No tracking — use `useTrack` or `<Track>` to observe impressions/clicks.\n *\n * Priority: bootstrap > localStorage cache > fetchDecision.\n */\nexport function useExperiment(\n id: string,\n options: UseExperimentOptions = {}\n): UseExperimentReturn {\n const { fallback = \"control\", debug = false } = options;\n const { host, bootstrap, customerId, apiKey } = useProbatContext();\n\n const [variantKey, setVariantKey] = useState<string>(() => {\n if (bootstrap[id]) return bootstrap[id];\n return \"control\";\n });\n const [resolved, setResolved] = useState<boolean>(() => {\n return !!bootstrap[id];\n });\n\n useEffect(() => {\n if (bootstrap[id] || readAssignment(id)) {\n const key = bootstrap[id] ?? readAssignment(id) ?? \"control\";\n setVariantKey(key);\n setResolved(true);\n return;\n }\n\n let cancelled = false;\n\n (async () => {\n try {\n const distinctId = customerId ?? getDistinctId();\n const key = await fetchDecision(host, id, distinctId, apiKey);\n if (cancelled) return;\n\n setVariantKey(key);\n writeAssignment(id, key);\n } catch (err) {\n if (cancelled) return;\n if (debug) {\n console.error(`[probat] fetchDecision failed for \"${id}\":`, err);\n }\n if (fallback === \"suspend\") throw err;\n setVariantKey(\"control\");\n } finally {\n if (!cancelled) setResolved(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [id, host]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (debug && resolved) {\n console.log(`[probat] Experiment \"${id}\" -> variant \"${variantKey}\"`);\n }\n }, [debug, id, variantKey, resolved]);\n\n return { variantKey, resolved };\n}\n","/**\n * Dedupe storage for experiment exposures.\n * Uses sessionStorage + in-memory Set fallback.\n * Key format: probat:seen:{id}:{variantKey}:{instanceId}:{pageKey}\n */\n\nconst PREFIX = \"probat:seen:\";\nconst memorySet = new Set<string>();\n\nexport function makeDedupeKey(\n experimentId: string,\n variantKey: string,\n instanceId: string,\n pageKey: string\n): string {\n return `${PREFIX}${experimentId}:${variantKey}:${instanceId}:${pageKey}`;\n}\n\nexport function hasSeen(key: string): boolean {\n if (memorySet.has(key)) return true;\n if (typeof window === \"undefined\") return false;\n try {\n return sessionStorage.getItem(key) === \"1\";\n } catch {\n return false;\n }\n}\n\nexport function markSeen(key: string): void {\n memorySet.add(key);\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(key, \"1\");\n } catch {}\n}\n\n/** Reset all dedupe state — useful for testing. */\nexport function resetDedupe(): void {\n memorySet.clear();\n}\n","/**\n * Stable auto-generated instance IDs for <Experiment />.\n *\n * Problem: a naïve useRef + module counter gives a different ID on every mount,\n * so StrictMode double-mount or unmount/remount changes the dedupe key.\n *\n * Solution:\n * 1. React 18+ → useId() is stable per fiber position.\n * 2. Fallback → sessionStorage-backed slot counter per (experimentId, pageKey).\n * 3. Both paths persist a mapping in sessionStorage:\n * probat:instance:{experimentId}:{pageKey}:{positionKey} → stableId\n * so the same position resolves to the same ID across mounts.\n */\n\nimport React, { useRef } from \"react\";\nimport { getPageKey } from \"./eventContext\";\n\nconst INSTANCE_PREFIX = \"probat:instance:\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction shortId(): string {\n const bytes = new Uint8Array(4);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 4; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Look up or create a stable instance ID in sessionStorage.\n */\nfunction resolveStableId(storageKey: string): string {\n if (typeof window !== \"undefined\") {\n try {\n const stored = sessionStorage.getItem(storageKey);\n if (stored) return stored;\n } catch {}\n }\n const id = `inst_${shortId()}`;\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(storageKey, id);\n } catch {}\n }\n return id;\n}\n\n// ── Fallback: render-wave slot counter ─────────────────────────────────────\n// Each synchronous render batch claims sequential slots per (experimentId,\n// pageKey). A microtask resets the counters so the next batch starts at 0,\n// giving the same component position the same slot across mounts.\n\nconst slotCounters = new Map<string, number>();\nlet resetScheduled = false;\n\nfunction claimSlot(groupKey: string): number {\n const idx = slotCounters.get(groupKey) ?? 0;\n slotCounters.set(groupKey, idx + 1);\n if (!resetScheduled) {\n resetScheduled = true;\n Promise.resolve().then(() => {\n slotCounters.clear();\n resetScheduled = false;\n });\n }\n return idx;\n}\n\n// ── Hook: React 18+ path (useId available) ─────────────────────────────────\n\nfunction useStableInstanceIdV18(experimentId: string): string {\n const reactId = (React as any).useId() as string;\n const ref = useRef(\"\");\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${reactId}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Hook: fallback path (no useId) ─────────────────────────────────────────\n\nfunction useStableInstanceIdFallback(experimentId: string): string {\n const slotRef = useRef(-1);\n const ref = useRef(\"\");\n if (slotRef.current === -1) {\n slotRef.current = claimSlot(`${experimentId}:${getPageKey()}`);\n }\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${slotRef.current}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Exported hook ──────────────────────────────────────────────────────────\n// Selection is a module-level constant so the hook-call count never changes\n// between renders — safe for the rules of hooks.\n\nexport const useStableInstanceId: (experimentId: string) => string =\n typeof (React as any).useId === \"function\"\n ? useStableInstanceIdV18\n : useStableInstanceIdFallback;\n\n// ── Test utility ───────────────────────────────────────────────────────────\n\nexport function resetInstanceIdState(): void {\n slotCounters.clear();\n resetScheduled = false;\n}\n","\"use client\";\n\nimport { useEffect, useRef, useMemo, useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric, extractClickMeta } from \"../utils/api\";\nimport { getPageKey } from \"../utils/eventContext\";\nimport { makeDedupeKey, hasSeen, markSeen } from \"../utils/dedupeStorage\";\nimport { useStableInstanceId } from \"../utils/stableInstanceId\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\ninterface UseTrackBaseOptions {\n /** Experiment identifier */\n experimentId: string;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Whether the variant assignment has been resolved. When false, all tracking is suppressed. */\n resolved?: boolean;\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n click?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n /** Log events to console */\n debug?: boolean;\n}\n\n/** Explicit mode: pass the variant key directly */\nexport interface UseTrackExplicitOptions extends UseTrackBaseOptions {\n /** The variant key to attach to events */\n variantKey: string;\n customerId?: undefined;\n}\n\n/** Customer mode: backend resolves variant from assignment table */\nexport interface UseTrackCustomerOptions extends UseTrackBaseOptions {\n variantKey?: undefined;\n /** Customer ID to resolve variant server-side. Falls back to provider's customerId. */\n customerId?: string;\n}\n\nexport type UseTrackOptions = UseTrackExplicitOptions | UseTrackCustomerOptions;\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Attaches impression and click tracking to a DOM element via a ref.\n *\n * Two modes:\n * - **Explicit**: pass `variantKey` directly — stamped on every event.\n * - **Customer**: omit `variantKey` — backend resolves it from the assignment table\n * using `(experimentId, customerId)`.\n *\n * @example\n * ```tsx\n * // Explicit mode\n * const trackRef = useTrack({ experimentId: \"pricing\", variantKey: \"ai_v1\" });\n *\n * // Customer mode (backend resolves variant)\n * const trackRef = useTrack({ experimentId: \"pricing\", customerId: \"user_123\" });\n * ```\n */\nexport function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null> {\n const {\n experimentId,\n componentInstanceId,\n resolved = true,\n impression: trackImpression = true,\n click: trackClick = true,\n impressionEventName = \"$experiment_exposure\",\n clickEventName = \"$experiment_click\",\n debug = false,\n } = options;\n\n const variantKey = options.variantKey ?? undefined;\n const explicitCustomerId = \"customerId\" in options ? options.customerId : undefined;\n\n const { host, customerId: providerCustomerId, apiKey } = useProbatContext();\n\n // In customer mode, use explicit customerId or fall back to provider's\n const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;\n const isCustomerMode = !variantKey;\n\n const autoInstanceId = useStableInstanceId(experimentId);\n const instanceId = componentInstanceId ?? autoInstanceId;\n\n const containerRef = useRef<HTMLElement | null>(null);\n const impressionSent = useRef(false);\n\n // Runtime warning\n useEffect(() => {\n if (isCustomerMode && !resolvedCustomerId && debug) {\n console.warn(\n `[probat] useTrack called without variantKey and no customerId ` +\n `available for \"${experimentId}\". Events will have no variant attribution.`\n );\n }\n }, [isCustomerMode, resolvedCustomerId, experimentId, debug]);\n\n const eventProps = useMemo(\n () => ({\n experiment_id: experimentId,\n ...(variantKey ? { variant_key: variantKey } : {}),\n component_instance_id: instanceId,\n ...(resolvedCustomerId ? { distinct_id: resolvedCustomerId } : {}),\n }),\n [experimentId, variantKey, instanceId, resolvedCustomerId]\n );\n\n // ── Impression tracking via IntersectionObserver ────────────────────────\n\n // In customer mode, use customerId for dedupe instead of variantKey\n const dedupeVariant = variantKey ?? resolvedCustomerId ?? \"__anon__\";\n\n useEffect(() => {\n if (!trackImpression || !resolved) return;\n\n impressionSent.current = false;\n\n const pageKey = getPageKey();\n const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);\n\n if (hasSeen(dedupeKey)) {\n impressionSent.current = true;\n return;\n }\n\n const el = containerRef.current;\n if (!el) return;\n\n // Fallback: no IntersectionObserver (SSR, old browser)\n if (typeof IntersectionObserver === \"undefined\") {\n if (!impressionSent.current) {\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps, apiKey);\n if (debug) console.log(`[probat] Impression sent (no IO) for \"${experimentId}\"`);\n }\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry || impressionSent.current) return;\n\n if (entry.isIntersecting) {\n timer = setTimeout(() => {\n if (impressionSent.current) return;\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps, apiKey);\n if (debug) console.log(`[probat] Impression sent for \"${experimentId}\"`);\n observer.disconnect();\n }, 250);\n } else if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n },\n { threshold: 0.5 }\n );\n\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n if (timer) clearTimeout(timer);\n };\n }, [\n trackImpression,\n resolved,\n experimentId,\n dedupeVariant,\n instanceId,\n host,\n impressionEventName,\n eventProps,\n debug,\n ]);\n\n // ── Click tracking ─────────────────────────────────────────────────────\n\n const handleClick = useCallback(\n (e: Event) => {\n if (!trackClick || !resolved) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n }, apiKey);\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, resolved, host, clickEventName, eventProps, experimentId, debug]\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el || !trackClick) return;\n\n el.addEventListener(\"click\", handleClick);\n return () => {\n el.removeEventListener(\"click\", handleClick);\n };\n }, [handleClick, trackClick]);\n\n return containerRef;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useExperiment } from \"../hooks/useExperiment\";\nimport { useTrack } from \"../hooks/useTrack\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ExperimentTrackOptions {\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n primaryClick?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n}\n\nexport interface ExperimentProps {\n /** Experiment key / identifier */\n id: string;\n /** Control variant ReactNode */\n control: React.ReactNode;\n /** Named variant ReactNodes, keyed by variant key (e.g. { ai_v1: <MyVariant /> }) */\n variants: Record<string, React.ReactNode>;\n /** Tracking configuration */\n track?: ExperimentTrackOptions;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions + events to console */\n debug?: boolean;\n}\n\nexport function Experiment({\n id,\n control,\n variants,\n track,\n componentInstanceId,\n fallback = \"control\",\n debug = false,\n}: ExperimentProps) {\n // ── Assignment (decoupled) ──────────────────────────────────────────────\n\n const { variantKey: rawKey, resolved } = useExperiment(id, { fallback, debug });\n\n // Validate variant key against the ReactNode map\n const variantKey =\n rawKey === \"control\" || rawKey in variants ? rawKey : \"control\";\n\n if (debug && rawKey !== variantKey) {\n console.warn(\n `[probat] Unknown variant \"${rawKey}\" for experiment \"${id}\", falling back to control`\n );\n }\n\n // ── Tracking (decoupled) ────────────────────────────────────────────────\n\n const trackRef = useTrack({\n experimentId: id,\n variantKey,\n componentInstanceId,\n resolved,\n impression: track?.impression !== false,\n click: track?.primaryClick !== false,\n impressionEventName: track?.impressionEventName,\n clickEventName: track?.clickEventName,\n debug,\n });\n\n // ── Render ─────────────────────────────────────────────────────────────\n\n const content =\n variantKey === \"control\" || !(variantKey in variants)\n ? control\n : variants[variantKey];\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-experiment={id}\n data-probat-variant={variantKey}\n style={{\n display: \"block\",\n margin: 0,\n padding: 0,\n opacity: resolved ? 1 : 0,\n transition: resolved ? \"opacity 0.15s ease-in\" : \"none\",\n }}\n >\n {content}\n </div>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useTrack, type UseTrackOptions } from \"../hooks/useTrack\";\n\nexport type TrackProps = UseTrackOptions & {\n children: React.ReactNode;\n};\n\n/**\n * Wrapper component that attaches impression and click tracking to its children.\n * Alternative to the `useTrack` hook when you prefer a component over a ref.\n *\n * @example\n * ```tsx\n * <Track experimentId=\"pricing\" variantKey=\"ai_v1\">\n * <PricingCard />\n * </Track>\n * ```\n */\nexport function Track({ children, ...trackOptions }: TrackProps) {\n const trackRef = useTrack(trackOptions);\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-track={trackOptions.experimentId}\n data-probat-variant={trackOptions.variantKey ?? \"server-resolved\"}\n style={{ display: \"contents\" }}\n >\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric } from \"../utils/api\";\n\nexport interface UseProbatMetricsReturn {\n /**\n * Send a custom event with arbitrary properties.\n * Never throws — failures are silently dropped.\n *\n * @example\n * ```tsx\n * const { capture } = useProbatMetrics();\n * capture(\"purchase\", { revenue: 42, currency: \"USD\" });\n * ```\n */\n capture: (event: string, properties?: Record<string, unknown>) => void;\n}\n\n/**\n * Minimal metrics hook. Provides a single `capture(event, props)` function\n * that sends events to the Probat backend using the provider's host config.\n */\nexport function useProbatMetrics(): UseProbatMetricsReturn {\n const { host, customerId, apiKey } = useProbatContext();\n\n const capture = useCallback(\n (event: string, properties: Record<string, unknown> = {}) => {\n sendMetric(host, event, {\n ...(customerId ? { distinct_id: customerId } : {}),\n ...properties,\n }, apiKey);\n },\n [host, customerId, apiKey]\n );\n\n return { capture };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, type ReactNode } from \"react\";\n\n/**\n * Factory that creates a typed context + hook pair for passing a variantKey\n * between components in different files without prop-drilling.\n *\n * @example\n * ```tsx\n * // experiment-context.ts\n * export const { ExperimentProvider, useVariantKey } = createExperimentContext(\"pricing-test\");\n *\n * // Parent.tsx\n * const { variantKey } = useExperiment(\"pricing-test\");\n * <ExperimentProvider value={variantKey}><Child /></ExperimentProvider>\n *\n * // Child.tsx (different file)\n * const variantKey = useVariantKey();\n * const trackRef = useTrack({ experimentId: \"pricing-test\", variantKey });\n * ```\n */\nexport function createExperimentContext(experimentId: string) {\n const Ctx = createContext<string | null>(null);\n\n function ExperimentProvider({\n value,\n children,\n }: {\n value: string;\n children: ReactNode;\n }) {\n return React.createElement(Ctx.Provider, { value }, children);\n }\n\n function useVariantKey(): string {\n const v = useContext(Ctx);\n if (v === null) {\n throw new Error(\n `useVariantKey() must be used inside <ExperimentProvider> for \"${experimentId}\"`\n );\n }\n return v;\n }\n\n return { ExperimentProvider, useVariantKey };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types/events.ts","../src/utils/environment.ts","../src/utils/eventContext.ts","../src/utils/eventQueue.ts","../src/utils/api.ts","../src/context/ProbatContext.tsx","../src/components/ProbatProviderClient.tsx","../src/hooks/useExperiment.ts","../src/utils/dedupeStorage.ts","../src/utils/stableInstanceId.ts","../src/hooks/useTrack.ts","../src/components/Experiment.tsx","../src/components/Track.tsx","../src/hooks/useProbatMetrics.ts","../src/utils/createExperimentContext.ts"],"names":["React","useEffect","useRef","useMemo","useCallback","createContext","useContext"],"mappings":"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","file":"index.mjs","sourcesContent":["export const PROBAT_ENV_DEV = \"dev\" as const;\nexport const PROBAT_ENV_PROD = \"prod\" as const;\nexport type ProbatEnvironment = typeof PROBAT_ENV_DEV | typeof PROBAT_ENV_PROD;\n\nexport type ProbatEventType =\n | \"$experiment_exposure\"\n | \"$experiment_click\"\n | \"$pageview\"\n | \"$pageleave\"\n | \"$session_start\"\n | \"$session_end\"\n | \"$goal_reached\"\n | \"$feature_interaction\"\n | string;\n\nexport interface StructuredEventProperties {\n // Always present (auto-injected by SDK)\n distinct_id: string;\n session_id: string;\n $page_url: string;\n $pathname: string;\n $referrer: string;\n captured_at: string;\n environment: ProbatEnvironment;\n source: \"react-sdk\" | \"react-native-sdk\";\n\n // Session context\n $session_sequence: number;\n $session_start_at: string;\n\n // Optional context\n experiment_id?: string;\n variant_key?: string;\n component_instance_id?: string;\n\n // Funnel context\n $funnel_id?: string;\n $funnel_step?: number;\n\n // Custom\n [key: string]: unknown;\n}\n\nexport interface StructuredEvent {\n event: ProbatEventType;\n environment: ProbatEnvironment;\n properties: StructuredEventProperties;\n}\n","import { PROBAT_ENV_DEV, PROBAT_ENV_PROD, type ProbatEnvironment } from \"../types/events\";\n\n/**\n * Detect if the code is running on localhost (development environment).\n * Returns \"dev\" for localhost, \"prod\" for production.\n */\nexport function detectEnvironment(): ProbatEnvironment {\n if (typeof window === \"undefined\") {\n return PROBAT_ENV_PROD; // Server-side, default to prod\n }\n\n const hostname = window.location.hostname;\n\n // Check for localhost, 127.0.0.1, or local IP addresses\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname.startsWith(\"192.168.\") ||\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"172.16.\") ||\n hostname.startsWith(\"172.17.\") ||\n hostname.startsWith(\"172.18.\") ||\n hostname.startsWith(\"172.19.\") ||\n hostname.startsWith(\"172.20.\") ||\n hostname.startsWith(\"172.21.\") ||\n hostname.startsWith(\"172.22.\") ||\n hostname.startsWith(\"172.23.\") ||\n hostname.startsWith(\"172.24.\") ||\n hostname.startsWith(\"172.25.\") ||\n hostname.startsWith(\"172.26.\") ||\n hostname.startsWith(\"172.27.\") ||\n hostname.startsWith(\"172.28.\") ||\n hostname.startsWith(\"172.29.\") ||\n hostname.startsWith(\"172.30.\") ||\n hostname.startsWith(\"172.31.\")\n ) {\n return PROBAT_ENV_DEV;\n }\n\n return PROBAT_ENV_PROD;\n}\n","/**\n * Event context helpers: identity/session/page metadata.\n * Browser-safe — returns static values on the server.\n */\n\nconst DISTINCT_ID_KEY = \"probat:distinct_id\";\nconst SESSION_ID_KEY = \"probat:session_id\";\nconst SESSION_START_AT_KEY = \"probat:session_start_at\";\nconst SESSION_SEQUENCE_KEY = \"probat:session_sequence\";\nconst SESSION_LAST_ACTIVITY_AT_KEY = \"probat:session_last_activity_at\";\n\nconst SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;\n\nlet cachedDistinctId: string | null = null;\nlet cachedSessionId: string | null = null;\nlet cachedSessionStartAt: string | null = null;\nlet cachedSessionSequence = 0;\nlet cachedLastActivityAt = 0;\n\nfunction generateId(): string {\n // crypto.randomUUID where available, else fallback\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n const bytes = new Uint8Array(16);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction nowMs(): number {\n return Date.now();\n}\n\nfunction startNewSession(tsMs: number): void {\n if (typeof window === \"undefined\") return;\n const sid = `sess_${generateId()}`;\n const startedAt = new Date(tsMs).toISOString();\n cachedSessionId = sid;\n cachedSessionStartAt = startedAt;\n cachedSessionSequence = 0;\n cachedLastActivityAt = tsMs;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, sid);\n sessionStorage.setItem(SESSION_START_AT_KEY, startedAt);\n sessionStorage.setItem(SESSION_SEQUENCE_KEY, \"0\");\n sessionStorage.setItem(SESSION_LAST_ACTIVITY_AT_KEY, String(tsMs));\n } catch {}\n}\n\nfunction hydrateSessionFromStorage(tsMs: number): void {\n if (typeof window === \"undefined\") return;\n\n if (cachedSessionId && cachedSessionStartAt) {\n const expiredInMemory =\n !Number.isFinite(cachedLastActivityAt) ||\n cachedLastActivityAt <= 0 ||\n tsMs - cachedLastActivityAt > SESSION_IDLE_TIMEOUT_MS;\n if (expiredInMemory) {\n startNewSession(tsMs);\n }\n return;\n }\n\n try {\n const sid = sessionStorage.getItem(SESSION_ID_KEY);\n const startedAt = sessionStorage.getItem(SESSION_START_AT_KEY);\n const sequenceRaw = sessionStorage.getItem(SESSION_SEQUENCE_KEY);\n const lastActivityRaw = sessionStorage.getItem(SESSION_LAST_ACTIVITY_AT_KEY);\n const sequence = Number(sequenceRaw || \"0\");\n const lastActivity = Number(lastActivityRaw || \"0\");\n\n if (!sid || !startedAt) {\n startNewSession(tsMs);\n return;\n }\n\n const expired =\n !Number.isFinite(lastActivity) ||\n lastActivity <= 0 ||\n tsMs - lastActivity > SESSION_IDLE_TIMEOUT_MS;\n\n if (expired) {\n startNewSession(tsMs);\n return;\n }\n\n cachedSessionId = sid;\n cachedSessionStartAt = startedAt;\n cachedSessionSequence = Number.isFinite(sequence) && sequence >= 0 ? sequence : 0;\n cachedLastActivityAt = lastActivity > 0 ? lastActivity : tsMs;\n } catch {\n startNewSession(tsMs);\n }\n}\n\nfunction ensureSession(tsMs: number): void {\n if (typeof window === \"undefined\") return;\n hydrateSessionFromStorage(tsMs);\n if (!cachedSessionId || !cachedSessionStartAt) {\n startNewSession(tsMs);\n }\n}\n\nfunction bumpSequence(tsMs: number): number {\n if (typeof window === \"undefined\") return 0;\n ensureSession(tsMs);\n cachedSessionSequence += 1;\n cachedLastActivityAt = tsMs;\n try {\n sessionStorage.setItem(SESSION_SEQUENCE_KEY, String(cachedSessionSequence));\n sessionStorage.setItem(SESSION_LAST_ACTIVITY_AT_KEY, String(tsMs));\n } catch {}\n return cachedSessionSequence;\n}\n\nexport function getDistinctId(): string {\n if (cachedDistinctId) return cachedDistinctId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = localStorage.getItem(DISTINCT_ID_KEY);\n if (stored) {\n cachedDistinctId = stored;\n return stored;\n }\n } catch {}\n const id = `anon_${generateId()}`;\n cachedDistinctId = id;\n try {\n localStorage.setItem(DISTINCT_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getSessionId(): string {\n if (typeof window === \"undefined\") return \"server\";\n ensureSession(nowMs());\n return cachedSessionId || \"server\";\n}\n\nexport function getSessionStartAt(): string {\n if (typeof window === \"undefined\") return new Date(0).toISOString();\n ensureSession(nowMs());\n return cachedSessionStartAt || new Date(0).toISOString();\n}\n\nexport function getPageKey(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.pathname + window.location.search;\n}\n\nexport function getPageUrl(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.href;\n}\n\nexport function getReferrer(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.referrer;\n}\n\nexport interface EventContext {\n distinct_id: string;\n session_id: string;\n $page_url: string;\n $pathname: string;\n $referrer: string;\n $session_sequence: number;\n $session_start_at: string;\n}\n\nexport function buildEventContext(): EventContext {\n const ts = nowMs();\n return {\n distinct_id: getDistinctId(),\n session_id: getSessionId(),\n $page_url: getPageUrl(),\n $pathname: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n $referrer: getReferrer(),\n $session_sequence: bumpSequence(ts),\n $session_start_at: getSessionStartAt(),\n };\n}\n\nexport function resetEventContextStateForTests(): void {\n cachedDistinctId = null;\n cachedSessionId = null;\n cachedSessionStartAt = null;\n cachedSessionSequence = 0;\n cachedLastActivityAt = 0;\n}\n","import type { StructuredEvent } from \"../types/events\";\n\ninterface BatchBody {\n events: StructuredEvent[];\n}\n\nexport class EventQueue {\n private queue: StructuredEvent[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly maxBatchSize = 20;\n private readonly flushIntervalMs = 5000;\n private readonly endpointBatch: string;\n private readonly endpointSingle: string;\n private readonly apiKey?: string;\n\n constructor(host: string, apiKey?: string) {\n const normalized = host.replace(/\\/$/, \"\");\n this.endpointBatch = `${normalized}/experiment/metrics/batch`;\n this.endpointSingle = `${normalized}/experiment/metrics`;\n this.apiKey = apiKey;\n }\n\n enqueue(event: StructuredEvent): void {\n this.queue.push(event);\n if (this.queue.length >= this.maxBatchSize) {\n this.flush();\n return;\n }\n if (!this.flushTimer) {\n this.flushTimer = setTimeout(() => {\n this.flush();\n }, this.flushIntervalMs);\n }\n }\n\n flush(forceBeacon = false): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, this.maxBatchSize);\n this.send(batch, forceBeacon);\n\n if (this.queue.length > 0) {\n this.flushTimer = setTimeout(() => {\n this.flush();\n }, this.flushIntervalMs);\n }\n }\n\n private send(batch: StructuredEvent[], forceBeacon = false): void {\n const body: BatchBody = { events: batch };\n const encodedBody = JSON.stringify(body);\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n const canBeacon =\n typeof navigator !== \"undefined\" &&\n typeof navigator.sendBeacon === \"function\" &&\n !this.apiKey &&\n (\n forceBeacon ||\n (typeof document !== \"undefined\" && document.visibilityState === \"hidden\")\n );\n\n if (canBeacon) {\n navigator.sendBeacon(this.endpointBatch, encodedBody);\n return;\n }\n\n try {\n if (batch.length === 1) {\n fetch(this.endpointSingle, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify(batch[0]),\n keepalive: true,\n }).catch(() => {});\n return;\n }\n fetch(this.endpointBatch, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: encodedBody,\n keepalive: true,\n }).catch(() => {});\n } catch {\n // Deliberately drop failed batches (current behavior parity).\n }\n }\n}\n\nconst queuesByHost = new Map<string, EventQueue>();\nlet lifecycleListenersInstalled = false;\n\nfunction installLifecycleListeners(): void {\n if (lifecycleListenersInstalled || typeof window === \"undefined\") return;\n lifecycleListenersInstalled = true;\n\n const flushWithBeacon = () => {\n for (const queue of queuesByHost.values()) {\n queue.flush(true);\n }\n };\n\n const flushNormally = () => {\n for (const queue of queuesByHost.values()) {\n queue.flush(false);\n }\n };\n\n window.addEventListener(\"pagehide\", flushWithBeacon);\n window.addEventListener(\"beforeunload\", flushWithBeacon);\n document.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n flushWithBeacon();\n } else {\n flushNormally();\n }\n });\n}\n\nfunction queueKey(host: string, apiKey?: string): string {\n const normalized = host.replace(/\\/$/, \"\");\n return apiKey ? `${normalized}::${apiKey}` : `${normalized}::__no_key__`;\n}\n\nexport function getEventQueue(host: string, apiKey?: string): EventQueue {\n const normalized = host.replace(/\\/$/, \"\");\n const key = queueKey(normalized, apiKey);\n let queue = queuesByHost.get(key);\n if (!queue) {\n queue = new EventQueue(normalized, apiKey);\n queuesByHost.set(key, queue);\n }\n installLifecycleListeners();\n return queue;\n}\n\nexport function flushEventQueue(host: string, forceBeacon = false, apiKey?: string): void {\n const queue = queuesByHost.get(queueKey(host, apiKey));\n if (!queue) return;\n queue.flush(forceBeacon);\n}\n\nexport function flushAllEventQueues(forceBeacon = false): void {\n for (const queue of queuesByHost.values()) {\n queue.flush(forceBeacon);\n }\n}\n\nexport function resetEventQueuesForTests(): void {\n queuesByHost.clear();\n}\n","import { detectEnvironment } from \"./environment\";\nimport { buildEventContext } from \"./eventContext\";\nimport { getEventQueue, flushEventQueue } from \"./eventQueue\";\nimport type {\n ProbatEnvironment,\n ProbatEventType,\n StructuredEvent,\n StructuredEventProperties,\n} from \"../types/events\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface DecisionResponse {\n variant_key: string;\n}\n\nexport interface MetricPayload {\n event: ProbatEventType;\n environment: ProbatEnvironment;\n properties: StructuredEventProperties;\n}\n\n// ── Assignment fetching ────────────────────────────────────────────────────\n\nconst pendingDecisions = new Map<string, Promise<string>>();\n\n/**\n * Fetch the variant assignment for an experiment.\n * Returns the variant key string (e.g. \"control\", \"ai_v1\").\n * Deduplicates concurrent calls for the same experiment.\n */\nexport async function fetchDecision(\n host: string,\n experimentId: string,\n distinctId: string,\n apiKey?: string\n): Promise<string> {\n const existing = pendingDecisions.get(experimentId);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/decide`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n };\n if (apiKey) headers[\"Authorization\"] = `Bearer ${apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify({\n experiment_id: experimentId,\n distinct_id: distinctId,\n }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const data = (await res.json()) as DecisionResponse;\n return data.variant_key || \"control\";\n } finally {\n pendingDecisions.delete(experimentId);\n }\n })();\n\n pendingDecisions.set(experimentId, promise);\n return promise;\n}\n\n// ── Metric sending ─────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget metric send. Never throws.\n */\nexport function sendMetric(\n host: string,\n event: ProbatEventType,\n properties: Record<string, unknown>,\n apiKey?: string\n): void {\n if (typeof window === \"undefined\") return;\n\n const environment = detectEnvironment();\n const ctx = buildEventContext();\n const payload: MetricPayload = {\n event,\n environment,\n properties: {\n ...ctx,\n environment,\n source: \"react-sdk\",\n captured_at: new Date().toISOString(),\n ...properties,\n } as StructuredEventProperties,\n };\n\n const queuePayload: StructuredEvent = {\n event: payload.event,\n environment: payload.environment,\n properties: payload.properties,\n };\n const queue = getEventQueue(host, apiKey);\n queue.enqueue(queuePayload);\n\n // Preserve immediate delivery semantics for core experiment events.\n if (event === \"$experiment_exposure\" || event === \"$experiment_click\") {\n queue.flush(false);\n }\n}\n\nexport function flushMetrics(host: string, forceBeacon = false, apiKey?: string): void {\n flushEventQueue(host, forceBeacon, apiKey);\n}\n\n// ── Click metadata extraction ──────────────────────────────────────────────\n\nexport interface ClickMeta {\n click_target_tag: string;\n click_target_text?: string;\n click_target_id?: string;\n click_is_primary: boolean;\n}\n\n/**\n * Given a click event inside an experiment boundary, extract metadata.\n * Prioritizes elements with data-probat-click=\"primary\",\n * then falls back to button/a/role=button.\n */\nexport function extractClickMeta(target: EventTarget | null): ClickMeta | null {\n if (!target || !(target instanceof HTMLElement)) return null;\n\n // Priority 1: explicit primary marker\n const primary = target.closest('[data-probat-click=\"primary\"]');\n if (primary) return buildMeta(primary as HTMLElement, true);\n\n // Priority 2: interactive elements\n const interactive = target.closest('button, a, [role=\"button\"]');\n if (interactive) return buildMeta(interactive as HTMLElement, false);\n\n return null;\n}\n\nfunction buildMeta(el: HTMLElement, isPrimary: boolean): ClickMeta {\n const meta: ClickMeta = {\n click_target_tag: el.tagName,\n click_is_primary: isPrimary,\n };\n if (el.id) meta.click_target_id = el.id;\n const text = el.textContent?.trim();\n if (text) meta.click_target_text = text.slice(0, 120);\n return meta;\n}\n","\"use client\";\n\nimport React, { createContext, useContext, useEffect, useMemo } from \"react\";\nimport { flushMetrics, sendMetric } from \"../utils/api\";\n\nexport interface ProbatContextValue {\n host: string;\n apiKey?: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://api.probat.app\";\n\nexport interface ProbatProviderProps {\n /** Your end-user's ID. When provided, used as the distinct_id for variant\n * assignment (consistent across devices) and attached to all events. */\n customerId?: string;\n /** Base URL for the Probat API. Defaults to https://api.probat.app */\n host?: string;\n /** Publishable API key for authenticating SDK requests (probat_pk_...) */\n apiKey?: string;\n /**\n * Bootstrap assignments to avoid flash on first render.\n * Map of experiment id → variant key.\n * e.g. { \"cta-copy-test\": \"ai_v1\" }\n */\n bootstrap?: Record<string, string>;\n /** Automatically send $session_start/$session_end lifecycle events. */\n trackSessionLifecycle?: boolean;\n children: React.ReactNode;\n}\n\nexport function ProbatProvider({\n customerId,\n host = DEFAULT_HOST,\n apiKey,\n bootstrap,\n trackSessionLifecycle = true,\n children,\n}: ProbatProviderProps) {\n const normalizedHost = host.replace(/\\/$/, \"\");\n\n useEffect(() => {\n if (!trackSessionLifecycle) return;\n if (typeof window === \"undefined\") return;\n\n sendMetric(normalizedHost, \"$session_start\", {\n ...(customerId ? { distinct_id: customerId } : {}),\n }, apiKey);\n\n return () => {\n sendMetric(normalizedHost, \"$session_end\", {\n ...(customerId ? { distinct_id: customerId } : {}),\n }, apiKey);\n flushMetrics(normalizedHost, true, apiKey);\n };\n }, [normalizedHost, customerId, trackSessionLifecycle, apiKey]);\n\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: normalizedHost,\n apiKey,\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, normalizedHost, apiKey, bootstrap]\n );\n\n return (\n <ProbatContext.Provider value={value}>\n {children}\n </ProbatContext.Provider>\n );\n}\n\nexport function useProbatContext(): ProbatContextValue {\n const ctx = useContext(ProbatContext);\n if (!ctx) {\n throw new Error(\n \"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { ProbatProvider } from \"../context/ProbatContext\";\nimport type { ProbatProviderProps } from \"../context/ProbatContext\";\n\n/**\n * Client-only provider for Next.js App Router.\n * Import this in your layout/providers file.\n *\n * @example\n * ```tsx\n * // app/providers.tsx\n * \"use client\";\n * import { ProbatProviderClient } from \"@probat/react\";\n *\n * export function Providers({ children }) {\n * return (\n * <ProbatProviderClient customerId={user.id}>\n * {children}\n * </ProbatProviderClient>\n * );\n * }\n * ```\n */\nexport function ProbatProviderClient(props: ProbatProviderProps) {\n return React.createElement(ProbatProvider, props);\n}\n\nexport type { ProbatProviderProps };\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { fetchDecision } from \"../utils/api\";\nimport { getDistinctId } from \"../utils/eventContext\";\n\n// ── localStorage assignment cache ──────────────────────────────────────────\n\nconst ASSIGNMENT_PREFIX = \"probat:assignment:\";\n\ninterface StoredAssignment {\n variantKey: string;\n ts: number;\n}\n\nexport function readAssignment(id: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(ASSIGNMENT_PREFIX + id);\n if (!raw) return null;\n const parsed: StoredAssignment = JSON.parse(raw);\n return parsed.variantKey ?? null;\n } catch {\n return null;\n }\n}\n\nexport function writeAssignment(id: string, variantKey: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const entry: StoredAssignment = { variantKey, ts: Date.now() };\n localStorage.setItem(ASSIGNMENT_PREFIX + id, JSON.stringify(entry));\n } catch {}\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseExperimentOptions {\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions to console */\n debug?: boolean;\n}\n\nexport interface UseExperimentReturn {\n /** The resolved variant key (e.g. \"control\", \"ai_v1\") */\n variantKey: string;\n /** Whether the assignment has been resolved (bootstrap, cache, or fetch) */\n resolved: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Reads the __probat_force query param for a specific experiment ID.\n * Format: ?__probat_force=<experimentId>:<variantKey>\n * Used in preview sandboxes to pin a specific variant without an API call.\n */\nfunction readForceParam(id: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = new URLSearchParams(window.location.search).get(\"__probat_force\");\n if (!raw) return null;\n const [forceId, forceKey] = raw.split(\":\");\n return forceId === id ? (forceKey ?? null) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Resolves the variant assignment for an experiment.\n * No tracking — use `useTrack` or `<Track>` to observe impressions/clicks.\n *\n * Priority: __probat_force URL param > bootstrap > localStorage cache > fetchDecision.\n */\nexport function useExperiment(\n id: string,\n options: UseExperimentOptions = {}\n): UseExperimentReturn {\n const { fallback = \"control\", debug = false } = options;\n const { host, bootstrap, customerId, apiKey } = useProbatContext();\n\n const [variantKey, setVariantKey] = useState<string>(() => {\n const forced = readForceParam(id);\n if (forced) return forced;\n if (bootstrap[id]) return bootstrap[id];\n return \"control\";\n });\n const [resolved, setResolved] = useState<boolean>(() => {\n return !!(readForceParam(id) || bootstrap[id]);\n });\n\n useEffect(() => {\n const forced = readForceParam(id);\n if (forced) {\n setVariantKey(forced);\n setResolved(true);\n return;\n }\n\n if (bootstrap[id] || readAssignment(id)) {\n const key = bootstrap[id] ?? readAssignment(id) ?? \"control\";\n setVariantKey(key);\n setResolved(true);\n return;\n }\n\n let cancelled = false;\n\n (async () => {\n try {\n const distinctId = customerId ?? getDistinctId();\n const key = await fetchDecision(host, id, distinctId, apiKey);\n if (cancelled) return;\n\n setVariantKey(key);\n writeAssignment(id, key);\n } catch (err) {\n if (cancelled) return;\n if (debug) {\n console.error(`[probat] fetchDecision failed for \"${id}\":`, err);\n }\n if (fallback === \"suspend\") throw err;\n setVariantKey(\"control\");\n } finally {\n if (!cancelled) setResolved(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [id, host]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (debug && resolved) {\n console.log(`[probat] Experiment \"${id}\" -> variant \"${variantKey}\"`);\n }\n }, [debug, id, variantKey, resolved]);\n\n return { variantKey, resolved };\n}\n","/**\n * Dedupe storage for experiment exposures.\n * Uses sessionStorage + in-memory Set fallback.\n * Key format: probat:seen:{id}:{variantKey}:{instanceId}:{pageKey}\n */\n\nconst PREFIX = \"probat:seen:\";\nconst memorySet = new Set<string>();\n\nexport function makeDedupeKey(\n experimentId: string,\n variantKey: string,\n instanceId: string,\n pageKey: string\n): string {\n return `${PREFIX}${experimentId}:${variantKey}:${instanceId}:${pageKey}`;\n}\n\nexport function hasSeen(key: string): boolean {\n if (memorySet.has(key)) return true;\n if (typeof window === \"undefined\") return false;\n try {\n return sessionStorage.getItem(key) === \"1\";\n } catch {\n return false;\n }\n}\n\nexport function markSeen(key: string): void {\n memorySet.add(key);\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(key, \"1\");\n } catch {}\n}\n\n/** Reset all dedupe state — useful for testing. */\nexport function resetDedupe(): void {\n memorySet.clear();\n}\n","/**\n * Stable auto-generated instance IDs for <Experiment />.\n *\n * Problem: a naïve useRef + module counter gives a different ID on every mount,\n * so StrictMode double-mount or unmount/remount changes the dedupe key.\n *\n * Solution:\n * 1. React 18+ → useId() is stable per fiber position.\n * 2. Fallback → sessionStorage-backed slot counter per (experimentId, pageKey).\n * 3. Both paths persist a mapping in sessionStorage:\n * probat:instance:{experimentId}:{pageKey}:{positionKey} → stableId\n * so the same position resolves to the same ID across mounts.\n */\n\nimport React, { useRef } from \"react\";\nimport { getPageKey } from \"./eventContext\";\n\nconst INSTANCE_PREFIX = \"probat:instance:\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction shortId(): string {\n const bytes = new Uint8Array(4);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 4; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Look up or create a stable instance ID in sessionStorage.\n */\nfunction resolveStableId(storageKey: string): string {\n if (typeof window !== \"undefined\") {\n try {\n const stored = sessionStorage.getItem(storageKey);\n if (stored) return stored;\n } catch {}\n }\n const id = `inst_${shortId()}`;\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(storageKey, id);\n } catch {}\n }\n return id;\n}\n\n// ── Fallback: render-wave slot counter ─────────────────────────────────────\n// Each synchronous render batch claims sequential slots per (experimentId,\n// pageKey). A microtask resets the counters so the next batch starts at 0,\n// giving the same component position the same slot across mounts.\n\nconst slotCounters = new Map<string, number>();\nlet resetScheduled = false;\n\nfunction claimSlot(groupKey: string): number {\n const idx = slotCounters.get(groupKey) ?? 0;\n slotCounters.set(groupKey, idx + 1);\n if (!resetScheduled) {\n resetScheduled = true;\n Promise.resolve().then(() => {\n slotCounters.clear();\n resetScheduled = false;\n });\n }\n return idx;\n}\n\n// ── Hook: React 18+ path (useId available) ─────────────────────────────────\n\nfunction useStableInstanceIdV18(experimentId: string): string {\n const reactId = (React as any).useId() as string;\n const ref = useRef(\"\");\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${reactId}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Hook: fallback path (no useId) ─────────────────────────────────────────\n\nfunction useStableInstanceIdFallback(experimentId: string): string {\n const slotRef = useRef(-1);\n const ref = useRef(\"\");\n if (slotRef.current === -1) {\n slotRef.current = claimSlot(`${experimentId}:${getPageKey()}`);\n }\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${slotRef.current}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Exported hook ──────────────────────────────────────────────────────────\n// Selection is a module-level constant so the hook-call count never changes\n// between renders — safe for the rules of hooks.\n\nexport const useStableInstanceId: (experimentId: string) => string =\n typeof (React as any).useId === \"function\"\n ? useStableInstanceIdV18\n : useStableInstanceIdFallback;\n\n// ── Test utility ───────────────────────────────────────────────────────────\n\nexport function resetInstanceIdState(): void {\n slotCounters.clear();\n resetScheduled = false;\n}\n","\"use client\";\n\nimport { useEffect, useRef, useMemo, useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric, extractClickMeta } from \"../utils/api\";\nimport { getPageKey } from \"../utils/eventContext\";\nimport { makeDedupeKey, hasSeen, markSeen } from \"../utils/dedupeStorage\";\nimport { useStableInstanceId } from \"../utils/stableInstanceId\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\ninterface UseTrackBaseOptions {\n /** Experiment identifier */\n experimentId: string;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Whether the variant assignment has been resolved. When false, all tracking is suppressed. */\n resolved?: boolean;\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n click?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n /** Log events to console */\n debug?: boolean;\n}\n\n/** Explicit mode: pass the variant key directly */\nexport interface UseTrackExplicitOptions extends UseTrackBaseOptions {\n /** The variant key to attach to events */\n variantKey: string;\n customerId?: undefined;\n}\n\n/** Customer mode: backend resolves variant from assignment table */\nexport interface UseTrackCustomerOptions extends UseTrackBaseOptions {\n variantKey?: undefined;\n /** Customer ID to resolve variant server-side. Falls back to provider's customerId. */\n customerId?: string;\n}\n\nexport type UseTrackOptions = UseTrackExplicitOptions | UseTrackCustomerOptions;\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Attaches impression and click tracking to a DOM element via a ref.\n *\n * Two modes:\n * - **Explicit**: pass `variantKey` directly — stamped on every event.\n * - **Customer**: omit `variantKey` — backend resolves it from the assignment table\n * using `(experimentId, customerId)`.\n *\n * @example\n * ```tsx\n * // Explicit mode\n * const trackRef = useTrack({ experimentId: \"pricing\", variantKey: \"ai_v1\" });\n *\n * // Customer mode (backend resolves variant)\n * const trackRef = useTrack({ experimentId: \"pricing\", customerId: \"user_123\" });\n * ```\n */\nexport function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null> {\n const {\n experimentId,\n componentInstanceId,\n resolved = true,\n impression: trackImpression = true,\n click: trackClick = true,\n impressionEventName = \"$experiment_exposure\",\n clickEventName = \"$experiment_click\",\n debug = false,\n } = options;\n\n const variantKey = options.variantKey ?? undefined;\n const explicitCustomerId = \"customerId\" in options ? options.customerId : undefined;\n\n const { host, customerId: providerCustomerId, apiKey } = useProbatContext();\n\n // In customer mode, use explicit customerId or fall back to provider's\n const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;\n const isCustomerMode = !variantKey;\n\n const autoInstanceId = useStableInstanceId(experimentId);\n const instanceId = componentInstanceId ?? autoInstanceId;\n\n const containerRef = useRef<HTMLElement | null>(null);\n const impressionSent = useRef(false);\n\n // Runtime warning\n useEffect(() => {\n if (isCustomerMode && !resolvedCustomerId && debug) {\n console.warn(\n `[probat] useTrack called without variantKey and no customerId ` +\n `available for \"${experimentId}\". Events will have no variant attribution.`\n );\n }\n }, [isCustomerMode, resolvedCustomerId, experimentId, debug]);\n\n const eventProps = useMemo(\n () => ({\n experiment_id: experimentId,\n ...(variantKey ? { variant_key: variantKey } : {}),\n component_instance_id: instanceId,\n ...(resolvedCustomerId ? { distinct_id: resolvedCustomerId } : {}),\n }),\n [experimentId, variantKey, instanceId, resolvedCustomerId]\n );\n\n // ── Impression tracking via IntersectionObserver ────────────────────────\n\n // In customer mode, use customerId for dedupe instead of variantKey\n const dedupeVariant = variantKey ?? resolvedCustomerId ?? \"__anon__\";\n\n useEffect(() => {\n if (!trackImpression || !resolved) return;\n\n impressionSent.current = false;\n\n const pageKey = getPageKey();\n const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);\n\n if (hasSeen(dedupeKey)) {\n impressionSent.current = true;\n return;\n }\n\n const el = containerRef.current;\n if (!el) return;\n\n // Fallback: no IntersectionObserver (SSR, old browser)\n if (typeof IntersectionObserver === \"undefined\") {\n if (!impressionSent.current) {\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps, apiKey);\n if (debug) console.log(`[probat] Impression sent (no IO) for \"${experimentId}\"`);\n }\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry || impressionSent.current) return;\n\n if (entry.isIntersecting) {\n timer = setTimeout(() => {\n if (impressionSent.current) return;\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps, apiKey);\n if (debug) console.log(`[probat] Impression sent for \"${experimentId}\"`);\n observer.disconnect();\n }, 250);\n } else if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n },\n { threshold: 0.5 }\n );\n\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n if (timer) clearTimeout(timer);\n };\n }, [\n trackImpression,\n resolved,\n experimentId,\n dedupeVariant,\n instanceId,\n host,\n impressionEventName,\n eventProps,\n debug,\n ]);\n\n // ── Click tracking ─────────────────────────────────────────────────────\n\n const handleClick = useCallback(\n (e: Event) => {\n if (!trackClick || !resolved) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n }, apiKey);\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, resolved, host, clickEventName, eventProps, experimentId, debug]\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el || !trackClick) return;\n\n el.addEventListener(\"click\", handleClick);\n return () => {\n el.removeEventListener(\"click\", handleClick);\n };\n }, [handleClick, trackClick]);\n\n return containerRef;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useExperiment } from \"../hooks/useExperiment\";\nimport { useTrack } from \"../hooks/useTrack\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ExperimentTrackOptions {\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n primaryClick?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n}\n\nexport interface ExperimentProps {\n /** Experiment key / identifier */\n id: string;\n /** Control variant ReactNode */\n control: React.ReactNode;\n /** Named variant ReactNodes, keyed by variant key (e.g. { ai_v1: <MyVariant /> }) */\n variants: Record<string, React.ReactNode>;\n /** Tracking configuration */\n track?: ExperimentTrackOptions;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions + events to console */\n debug?: boolean;\n}\n\nexport function Experiment({\n id,\n control,\n variants,\n track,\n componentInstanceId,\n fallback = \"control\",\n debug = false,\n}: ExperimentProps) {\n // ── Assignment (decoupled) ──────────────────────────────────────────────\n\n const { variantKey: rawKey, resolved } = useExperiment(id, { fallback, debug });\n\n // Validate variant key against the ReactNode map\n const variantKey =\n rawKey === \"control\" || rawKey in variants ? rawKey : \"control\";\n\n if (debug && rawKey !== variantKey) {\n console.warn(\n `[probat] Unknown variant \"${rawKey}\" for experiment \"${id}\", falling back to control`\n );\n }\n\n // ── Tracking (decoupled) ────────────────────────────────────────────────\n\n const trackRef = useTrack({\n experimentId: id,\n variantKey,\n componentInstanceId,\n resolved,\n impression: track?.impression !== false,\n click: track?.primaryClick !== false,\n impressionEventName: track?.impressionEventName,\n clickEventName: track?.clickEventName,\n debug,\n });\n\n // ── Render ─────────────────────────────────────────────────────────────\n\n const content =\n variantKey === \"control\" || !(variantKey in variants)\n ? control\n : variants[variantKey];\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-experiment={id}\n data-probat-variant={variantKey}\n style={{\n display: \"block\",\n margin: 0,\n padding: 0,\n opacity: resolved ? 1 : 0,\n transition: resolved ? \"opacity 0.15s ease-in\" : \"none\",\n }}\n >\n {content}\n </div>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useTrack, type UseTrackOptions } from \"../hooks/useTrack\";\n\nexport type TrackProps = UseTrackOptions & {\n children: React.ReactNode;\n};\n\n/**\n * Wrapper component that attaches impression and click tracking to its children.\n * Alternative to the `useTrack` hook when you prefer a component over a ref.\n *\n * @example\n * ```tsx\n * <Track experimentId=\"pricing\" variantKey=\"ai_v1\">\n * <PricingCard />\n * </Track>\n * ```\n */\nexport function Track({ children, ...trackOptions }: TrackProps) {\n const trackRef = useTrack(trackOptions);\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-track={trackOptions.experimentId}\n data-probat-variant={trackOptions.variantKey ?? \"server-resolved\"}\n style={{ display: \"contents\" }}\n >\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric } from \"../utils/api\";\nimport type { ProbatEventType } from \"../types/events\";\n\nexport interface UseProbatMetricsReturn {\n /**\n * Send a custom event with arbitrary properties.\n * Never throws — failures are silently dropped.\n *\n * @example\n * ```tsx\n * const { capture } = useProbatMetrics();\n * capture(\"purchase\", { revenue: 42, currency: \"USD\" });\n * ```\n */\n capture: (event: ProbatEventType, properties?: Record<string, unknown>) => void;\n captureGoal: (\n funnelId: string,\n funnelStep: number,\n properties?: Record<string, unknown>,\n ) => void;\n captureFeatureInteraction: (\n interactionName: string,\n properties?: Record<string, unknown>,\n ) => void;\n}\n\n/**\n * Minimal metrics hook. Provides a single `capture(event, props)` function\n * that sends events to the Probat backend using the provider's host config.\n */\nexport function useProbatMetrics(): UseProbatMetricsReturn {\n const { host, customerId, apiKey } = useProbatContext();\n\n const capture = useCallback(\n (event: ProbatEventType, properties: Record<string, unknown> = {}) => {\n sendMetric(host, event, {\n ...(customerId ? { distinct_id: customerId } : {}),\n ...properties,\n }, apiKey);\n },\n [host, customerId, apiKey]\n );\n\n const captureGoal = useCallback(\n (\n funnelId: string,\n funnelStep: number,\n properties: Record<string, unknown> = {},\n ) => {\n sendMetric(host, \"$goal_reached\", {\n ...(customerId ? { distinct_id: customerId } : {}),\n $funnel_id: funnelId,\n $funnel_step: funnelStep,\n ...properties,\n });\n },\n [host, customerId],\n );\n\n const captureFeatureInteraction = useCallback(\n (\n interactionName: string,\n properties: Record<string, unknown> = {},\n ) => {\n sendMetric(host, \"$feature_interaction\", {\n ...(customerId ? { distinct_id: customerId } : {}),\n interaction_name: interactionName,\n ...properties,\n });\n },\n [host, customerId],\n );\n\n return {\n capture,\n captureGoal,\n captureFeatureInteraction,\n };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, type ReactNode } from \"react\";\n\n/**\n * Factory that creates a typed context + hook pair for passing a variantKey\n * between components in different files without prop-drilling.\n *\n * @example\n * ```tsx\n * // experiment-context.ts\n * export const { ExperimentProvider, useVariantKey } = createExperimentContext(\"pricing-test\");\n *\n * // Parent.tsx\n * const { variantKey } = useExperiment(\"pricing-test\");\n * <ExperimentProvider value={variantKey}><Child /></ExperimentProvider>\n *\n * // Child.tsx (different file)\n * const variantKey = useVariantKey();\n * const trackRef = useTrack({ experimentId: \"pricing-test\", variantKey });\n * ```\n */\nexport function createExperimentContext(experimentId: string) {\n const Ctx = createContext<string | null>(null);\n\n function ExperimentProvider({\n value,\n children,\n }: {\n value: string;\n children: ReactNode;\n }) {\n return React.createElement(Ctx.Provider, { value }, children);\n }\n\n function useVariantKey(): string {\n const v = useContext(Ctx);\n if (v === null) {\n throw new Error(\n `useVariantKey() must be used inside <ExperimentProvider> for \"${experimentId}\"`\n );\n }\n return v;\n }\n\n return { ExperimentProvider, useVariantKey };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probat/react",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "React SDK for Probat experimentation — CTR tracking, variant assignment, and more",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@testing-library/jest-dom": "^6.6.3",
|
|
49
49
|
"@testing-library/react": "^16.1.0",
|
|
50
|
+
"@types/node": "^25.5.0",
|
|
50
51
|
"@types/react": "^18.2.0",
|
|
51
52
|
"@types/react-dom": "^18.2.0",
|
|
52
53
|
"jsdom": "^25.0.1",
|
|
@@ -11,7 +11,7 @@ import { MockIntersectionObserver } from "./setup";
|
|
|
11
11
|
|
|
12
12
|
function wrapper({ children }: { children: React.ReactNode }) {
|
|
13
13
|
return (
|
|
14
|
-
<ProbatProviderClient customerId="test-customer-id" host="https://api.test.com">
|
|
14
|
+
<ProbatProviderClient trackSessionLifecycle={false} customerId="test-customer-id" host="https://api.test.com">
|
|
15
15
|
{children}
|
|
16
16
|
</ProbatProviderClient>
|
|
17
17
|
);
|
|
@@ -21,6 +21,7 @@ function wrapperWithBootstrap(bootstrap: Record<string, string>) {
|
|
|
21
21
|
return function BootstrapWrapper({ children }: { children: React.ReactNode }) {
|
|
22
22
|
return (
|
|
23
23
|
<ProbatProviderClient
|
|
24
|
+
trackSessionLifecycle={false}
|
|
24
25
|
customerId="test-customer-id"
|
|
25
26
|
host="https://api.test.com"
|
|
26
27
|
bootstrap={bootstrap}
|
|
@@ -544,7 +545,7 @@ describe("StrictMode safety", () => {
|
|
|
544
545
|
|
|
545
546
|
render(
|
|
546
547
|
<StrictMode>
|
|
547
|
-
<ProbatProviderClient customerId="test-customer-id" host="https://api.test.com">
|
|
548
|
+
<ProbatProviderClient trackSessionLifecycle={false} customerId="test-customer-id" host="https://api.test.com">
|
|
548
549
|
<Experiment
|
|
549
550
|
id="strict-exp"
|
|
550
551
|
control={<div>Strict Control</div>}
|
|
@@ -592,7 +593,7 @@ describe("StrictMode safety", () => {
|
|
|
592
593
|
|
|
593
594
|
render(
|
|
594
595
|
<StrictMode>
|
|
595
|
-
<ProbatProviderClient customerId="test-customer-id" host="https://api.test.com">
|
|
596
|
+
<ProbatProviderClient trackSessionLifecycle={false} customerId="test-customer-id" host="https://api.test.com">
|
|
596
597
|
<Experiment
|
|
597
598
|
id="stable-id-exp"
|
|
598
599
|
control={<button>Buy</button>}
|
|
@@ -816,7 +817,7 @@ describe("event payload", () => {
|
|
|
816
817
|
);
|
|
817
818
|
|
|
818
819
|
render(
|
|
819
|
-
<ProbatProviderClient host="https://api.test.com">
|
|
820
|
+
<ProbatProviderClient trackSessionLifecycle={false} host="https://api.test.com">
|
|
820
821
|
<Experiment
|
|
821
822
|
id="no-cid-exp"
|
|
822
823
|
control={<div>Control</div>}
|
|
@@ -887,7 +888,7 @@ describe("customerId assignment bucketing", () => {
|
|
|
887
888
|
});
|
|
888
889
|
|
|
889
890
|
render(
|
|
890
|
-
<ProbatProviderClient host="https://api.test.com">
|
|
891
|
+
<ProbatProviderClient trackSessionLifecycle={false} host="https://api.test.com">
|
|
891
892
|
<Experiment
|
|
892
893
|
id="anon-bucket-exp"
|
|
893
894
|
control={<div>Control</div>}
|
|
@@ -11,7 +11,7 @@ import { MockIntersectionObserver } from "./setup";
|
|
|
11
11
|
|
|
12
12
|
function wrapper({ children }: { children: React.ReactNode }) {
|
|
13
13
|
return (
|
|
14
|
-
<ProbatProviderClient customerId="test-customer-id" host="https://api.test.com">
|
|
14
|
+
<ProbatProviderClient trackSessionLifecycle={false} customerId="test-customer-id" host="https://api.test.com">
|
|
15
15
|
{children}
|
|
16
16
|
</ProbatProviderClient>
|
|
17
17
|
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { sendMetric, flushMetrics } from "../utils/api";
|
|
3
|
+
import { resetEventContextStateForTests } from "../utils/eventContext";
|
|
4
|
+
|
|
5
|
+
describe("event queue", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.useFakeTimers();
|
|
8
|
+
localStorage.clear();
|
|
9
|
+
sessionStorage.clear();
|
|
10
|
+
resetEventContextStateForTests();
|
|
11
|
+
vi.restoreAllMocks();
|
|
12
|
+
global.fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.useRealTimers();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("flushes a single queued event to /experiment/metrics", async () => {
|
|
20
|
+
const fetchMock = global.fetch as ReturnType<typeof vi.fn>;
|
|
21
|
+
|
|
22
|
+
sendMetric("https://api.test.com", "$pageview", {
|
|
23
|
+
distinct_id: "u1",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
27
|
+
|
|
28
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
29
|
+
const [url, init] = fetchMock.mock.calls[0]!;
|
|
30
|
+
expect(String(url)).toContain("/experiment/metrics");
|
|
31
|
+
|
|
32
|
+
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
33
|
+
expect(body.event).toBe("$pageview");
|
|
34
|
+
expect(body.properties.distinct_id).toBe("u1");
|
|
35
|
+
expect(body.properties.$session_sequence).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("flushes multiple queued events as a batch", async () => {
|
|
39
|
+
const fetchMock = global.fetch as ReturnType<typeof vi.fn>;
|
|
40
|
+
|
|
41
|
+
sendMetric("https://api.test.com", "$pageview", { distinct_id: "u1" });
|
|
42
|
+
sendMetric("https://api.test.com", "$feature_interaction", {
|
|
43
|
+
distinct_id: "u1",
|
|
44
|
+
interaction_name: "cta",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
48
|
+
|
|
49
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
50
|
+
const [url, init] = fetchMock.mock.calls[0]!;
|
|
51
|
+
expect(String(url)).toContain("/experiment/metrics/batch");
|
|
52
|
+
|
|
53
|
+
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
54
|
+
expect(Array.isArray(body.events)).toBe(true);
|
|
55
|
+
expect(body.events).toHaveLength(2);
|
|
56
|
+
expect(body.events[0].event).toBe("$pageview");
|
|
57
|
+
expect(body.events[1].event).toBe("$feature_interaction");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("supports manual flush before timer", () => {
|
|
61
|
+
const fetchMock = global.fetch as ReturnType<typeof vi.fn>;
|
|
62
|
+
|
|
63
|
+
sendMetric("https://api.test.com", "$pageview", { distinct_id: "u2" });
|
|
64
|
+
flushMetrics("https://api.test.com");
|
|
65
|
+
|
|
66
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
67
|
+
});
|
|
68
|
+
});
|
package/src/__tests__/setup.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import "@testing-library/jest-dom/vitest";
|
|
2
|
+
import { beforeEach } from "vitest";
|
|
3
|
+
import { resetEventQueuesForTests } from "../utils/eventQueue";
|
|
4
|
+
import { resetEventContextStateForTests } from "../utils/eventContext";
|
|
2
5
|
|
|
3
6
|
// Mock IntersectionObserver
|
|
4
7
|
class MockIntersectionObserver implements IntersectionObserver {
|
|
@@ -59,5 +62,57 @@ Object.defineProperty(globalThis, "IntersectionObserver", {
|
|
|
59
62
|
writable: true,
|
|
60
63
|
});
|
|
61
64
|
|
|
65
|
+
function createMemoryStorage(): Storage {
|
|
66
|
+
class MemoryStorage implements Storage {
|
|
67
|
+
[key: string]: any;
|
|
68
|
+
private store = new Map<string, string>();
|
|
69
|
+
|
|
70
|
+
get length() {
|
|
71
|
+
return this.store.size;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
clear() {
|
|
75
|
+
for (const key of this.store.keys()) {
|
|
76
|
+
delete this[key];
|
|
77
|
+
}
|
|
78
|
+
this.store.clear();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getItem(key: string) {
|
|
82
|
+
return this.store.has(key) ? this.store.get(key)! : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
key(index: number) {
|
|
86
|
+
return Array.from(this.store.keys())[index] ?? null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
removeItem(key: string) {
|
|
90
|
+
this.store.delete(key);
|
|
91
|
+
delete this[key];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setItem(key: string, value: string) {
|
|
95
|
+
const normalized = String(value);
|
|
96
|
+
this.store.set(key, normalized);
|
|
97
|
+
this[key] = normalized;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return new MemoryStorage();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const localStorageShim = createMemoryStorage();
|
|
105
|
+
const sessionStorageShim = createMemoryStorage();
|
|
106
|
+
|
|
107
|
+
Object.defineProperty(globalThis, "localStorage", { value: localStorageShim, writable: true });
|
|
108
|
+
Object.defineProperty(globalThis, "sessionStorage", { value: sessionStorageShim, writable: true });
|
|
109
|
+
Object.defineProperty(window, "localStorage", { value: localStorageShim, writable: true });
|
|
110
|
+
Object.defineProperty(window, "sessionStorage", { value: sessionStorageShim, writable: true });
|
|
111
|
+
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
resetEventQueuesForTests();
|
|
114
|
+
resetEventContextStateForTests();
|
|
115
|
+
});
|
|
116
|
+
|
|
62
117
|
// Export for test use
|
|
63
118
|
export { MockIntersectionObserver };
|
|
@@ -9,7 +9,7 @@ import { resetInstanceIdState } from "../utils/stableInstanceId";
|
|
|
9
9
|
|
|
10
10
|
function wrapper({ children }: { children: React.ReactNode }) {
|
|
11
11
|
return (
|
|
12
|
-
<ProbatProviderClient customerId="test-customer-id" host="https://api.test.com">
|
|
12
|
+
<ProbatProviderClient trackSessionLifecycle={false} customerId="test-customer-id" host="https://api.test.com">
|
|
13
13
|
{children}
|
|
14
14
|
</ProbatProviderClient>
|
|
15
15
|
);
|
|
@@ -19,6 +19,7 @@ function wrapperWithBootstrap(bootstrap: Record<string, string>) {
|
|
|
19
19
|
return function BootstrapWrapper({ children }: { children: React.ReactNode }) {
|
|
20
20
|
return (
|
|
21
21
|
<ProbatProviderClient
|
|
22
|
+
trackSessionLifecycle={false}
|
|
22
23
|
customerId="test-customer-id"
|
|
23
24
|
host="https://api.test.com"
|
|
24
25
|
bootstrap={bootstrap}
|
|
@@ -31,7 +32,7 @@ function wrapperWithBootstrap(bootstrap: Record<string, string>) {
|
|
|
31
32
|
|
|
32
33
|
function wrapperNoCustomerId({ children }: { children: React.ReactNode }) {
|
|
33
34
|
return (
|
|
34
|
-
<ProbatProviderClient host="https://api.test.com">
|
|
35
|
+
<ProbatProviderClient trackSessionLifecycle={false} host="https://api.test.com">
|
|
35
36
|
{children}
|
|
36
37
|
</ProbatProviderClient>
|
|
37
38
|
);
|
|
@@ -37,7 +37,7 @@ function TrackedContent(props: {
|
|
|
37
37
|
|
|
38
38
|
function wrapper({ children }: { children: React.ReactNode }) {
|
|
39
39
|
return (
|
|
40
|
-
<ProbatProviderClient customerId="test-customer-id" host="https://api.test.com">
|
|
40
|
+
<ProbatProviderClient trackSessionLifecycle={false} customerId="test-customer-id" host="https://api.test.com">
|
|
41
41
|
{children}
|
|
42
42
|
</ProbatProviderClient>
|
|
43
43
|
);
|
|
@@ -578,7 +578,7 @@ describe("useTrack customer mode", () => {
|
|
|
578
578
|
|
|
579
579
|
function NoCustomerProvider({ children }: { children: React.ReactNode }) {
|
|
580
580
|
return (
|
|
581
|
-
<ProbatProviderClient host="https://api.test.com">
|
|
581
|
+
<ProbatProviderClient trackSessionLifecycle={false} host="https://api.test.com">
|
|
582
582
|
{children}
|
|
583
583
|
</ProbatProviderClient>
|
|
584
584
|
);
|
|
@@ -607,7 +607,7 @@ describe("useTrack customer mode", () => {
|
|
|
607
607
|
|
|
608
608
|
function NoCustomerProvider({ children }: { children: React.ReactNode }) {
|
|
609
609
|
return (
|
|
610
|
-
<ProbatProviderClient host="https://api.test.com">
|
|
610
|
+
<ProbatProviderClient trackSessionLifecycle={false} host="https://api.test.com">
|
|
611
611
|
{children}
|
|
612
612
|
</ProbatProviderClient>
|
|
613
613
|
);
|
|
@@ -651,7 +651,7 @@ describe("useTrack StrictMode safety", () => {
|
|
|
651
651
|
|
|
652
652
|
render(
|
|
653
653
|
<StrictMode>
|
|
654
|
-
<ProbatProviderClient customerId="test-customer-id" host="https://api.test.com">
|
|
654
|
+
<ProbatProviderClient trackSessionLifecycle={false} customerId="test-customer-id" host="https://api.test.com">
|
|
655
655
|
<TrackedContent experimentId="strict-exp" variantKey="control">
|
|
656
656
|
<div>Strict Content</div>
|
|
657
657
|
</TrackedContent>
|