@interfere/react 1.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/package.json +24 -25
  2. package/dist/error-boundary.d.mts +0 -27
  3. package/dist/error-boundary.d.mts.map +0 -1
  4. package/dist/error-boundary.mjs +0 -44
  5. package/dist/error-boundary.mjs.map +0 -1
  6. package/dist/internal/client.d.mts +0 -32
  7. package/dist/internal/client.d.mts.map +0 -1
  8. package/dist/internal/client.mjs +0 -100
  9. package/dist/internal/client.mjs.map +0 -1
  10. package/dist/internal/config.d.mts +0 -9
  11. package/dist/internal/config.d.mts.map +0 -1
  12. package/dist/internal/config.mjs +0 -27
  13. package/dist/internal/config.mjs.map +0 -1
  14. package/dist/internal/consent.d.mts +0 -15
  15. package/dist/internal/consent.d.mts.map +0 -1
  16. package/dist/internal/consent.mjs +0 -25
  17. package/dist/internal/consent.mjs.map +0 -1
  18. package/dist/internal/context.d.mts +0 -6
  19. package/dist/internal/context.d.mts.map +0 -1
  20. package/dist/internal/context.mjs +0 -32
  21. package/dist/internal/context.mjs.map +0 -1
  22. package/dist/internal/envelope.d.mts +0 -14
  23. package/dist/internal/envelope.d.mts.map +0 -1
  24. package/dist/internal/envelope.mjs +0 -20
  25. package/dist/internal/envelope.mjs.map +0 -1
  26. package/dist/internal/errors.d.mts +0 -4
  27. package/dist/internal/errors.d.mts.map +0 -1
  28. package/dist/internal/errors.mjs +0 -4
  29. package/dist/internal/errors.mjs.map +0 -1
  30. package/dist/internal/plugin-runtime.d.mts +0 -26
  31. package/dist/internal/plugin-runtime.d.mts.map +0 -1
  32. package/dist/internal/plugin-runtime.mjs +0 -85
  33. package/dist/internal/plugin-runtime.mjs.map +0 -1
  34. package/dist/internal/sw.d.mts +0 -4
  35. package/dist/internal/sw.d.mts.map +0 -1
  36. package/dist/internal/sw.mjs +0 -10
  37. package/dist/internal/sw.mjs.map +0 -1
  38. package/dist/plugins/errors.d.mts +0 -6
  39. package/dist/plugins/errors.d.mts.map +0 -1
  40. package/dist/plugins/errors.mjs +0 -59
  41. package/dist/plugins/errors.mjs.map +0 -1
  42. package/dist/plugins/fingerprint.d.mts +0 -6
  43. package/dist/plugins/fingerprint.d.mts.map +0 -1
  44. package/dist/plugins/fingerprint.mjs +0 -13
  45. package/dist/plugins/fingerprint.mjs.map +0 -1
  46. package/dist/plugins/lib/loader.d.mts +0 -10
  47. package/dist/plugins/lib/loader.d.mts.map +0 -1
  48. package/dist/plugins/lib/loader.mjs +0 -43
  49. package/dist/plugins/lib/loader.mjs.map +0 -1
  50. package/dist/plugins/lib/types.d.mts +0 -14
  51. package/dist/plugins/lib/types.d.mts.map +0 -1
  52. package/dist/plugins/lib/types.mjs +0 -1
  53. package/dist/plugins/pages.d.mts +0 -6
  54. package/dist/plugins/pages.d.mts.map +0 -1
  55. package/dist/plugins/pages.mjs +0 -102
  56. package/dist/plugins/pages.mjs.map +0 -1
  57. package/dist/plugins/rage-clicks.d.mts +0 -6
  58. package/dist/plugins/rage-clicks.d.mts.map +0 -1
  59. package/dist/plugins/rage-clicks.mjs +0 -53
  60. package/dist/plugins/rage-clicks.mjs.map +0 -1
  61. package/dist/plugins/replay.d.mts +0 -6
  62. package/dist/plugins/replay.d.mts.map +0 -1
  63. package/dist/plugins/replay.mjs +0 -62
  64. package/dist/plugins/replay.mjs.map +0 -1
  65. package/dist/provider.d.mts +0 -30
  66. package/dist/provider.d.mts.map +0 -1
  67. package/dist/provider.mjs +0 -30
  68. package/dist/provider.mjs.map +0 -1
  69. package/dist/tracking/api.d.mts +0 -17
  70. package/dist/tracking/api.d.mts.map +0 -1
  71. package/dist/tracking/api.mjs +0 -76
  72. package/dist/tracking/api.mjs.map +0 -1
  73. package/dist/tracking/session.d.mts +0 -19
  74. package/dist/tracking/session.d.mts.map +0 -1
  75. package/dist/tracking/session.mjs +0 -76
  76. package/dist/tracking/session.mjs.map +0 -1
  77. package/dist/tracking/visitor.d.mts +0 -7
  78. package/dist/tracking/visitor.d.mts.map +0 -1
  79. package/dist/tracking/visitor.mjs +0 -40
  80. package/dist/tracking/visitor.mjs.map +0 -1
  81. package/dist/transport/http.d.mts +0 -16
  82. package/dist/transport/http.d.mts.map +0 -1
  83. package/dist/transport/http.mjs +0 -56
  84. package/dist/transport/http.mjs.map +0 -1
  85. package/dist/transport/queue.d.mts +0 -25
  86. package/dist/transport/queue.d.mts.map +0 -1
  87. package/dist/transport/queue.mjs +0 -60
  88. package/dist/transport/queue.mjs.map +0 -1
  89. package/dist/util/log.d.mts +0 -13
  90. package/dist/util/log.d.mts.map +0 -1
  91. package/dist/util/log.mjs +0 -37
  92. package/dist/util/log.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin-runtime.mjs","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nimport errorsPlugin from \"../plugins/errors.js\";\nimport {\n loadPlugin,\n type PluginOverrides,\n resolveFeatures,\n} from \"../plugins/lib/loader.js\";\nimport type { PluginCleanup, PluginContext } from \"../plugins/lib/types.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getPluginConsentCategory,\n hasConsentChanged,\n isConsentAllowed,\n resolveGrantedConsent,\n shouldCaptureEvent,\n} from \"./consent.js\";\n\nconst log = createLogger(\"plugin-runtime\");\n\nexport class PluginRuntime {\n private readonly activeCleanups = new Map<PluginKey, PluginCleanup>();\n private readonly context: PluginContext;\n private readonly features: Record<PluginKey, boolean>;\n private consentState: ConsentState | null;\n private syncVersion = 0;\n\n constructor(\n context: PluginContext,\n overrides: PluginOverrides | undefined,\n initialConsent: ConsentState | undefined\n ) {\n this.context = context;\n this.features = resolveFeatures(overrides);\n this.consentState = initialConsent ?? null;\n }\n\n getConsent(): ConsentState | null {\n return this.consentState;\n }\n\n setConsent(nextConsent?: ConsentState): void {\n const nextState = resolveGrantedConsent(nextConsent);\n if (!hasConsentChanged(this.consentState, nextState)) {\n return;\n }\n\n this.consentState = nextState;\n this.sync();\n }\n\n resetConsent(): void {\n if (!hasConsentChanged(this.consentState, null)) {\n return;\n }\n\n this.consentState = null;\n this.sync();\n }\n\n canCapture(type: EventType): boolean {\n return shouldCaptureEvent(type, this.consentState);\n }\n\n start(): void {\n if (this.features.errors) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n\n this.sync();\n }\n\n dispose(): void {\n for (const key of this.activeCleanups.keys()) {\n this.deactivate(key);\n }\n }\n\n private shouldEnablePlugin(key: PluginKey): boolean {\n return (\n this.features[key] &&\n isConsentAllowed(getPluginConsentCategory(key), this.consentState)\n );\n }\n\n private deactivate(key: PluginKey): void {\n const cleanup = this.activeCleanups.get(key);\n if (!cleanup) {\n return;\n }\n\n try {\n cleanup();\n } catch {\n log.warn(\"cleanup failed for %s\", key);\n }\n\n this.activeCleanups.delete(key);\n }\n\n private async activate(key: PluginKey): Promise<void> {\n if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) {\n return;\n }\n\n const version = this.syncVersion;\n const cleanup = await loadPlugin(key, this.context);\n if (!cleanup) {\n return;\n }\n\n const staleSync = version !== this.syncVersion;\n if (staleSync || !this.shouldEnablePlugin(key)) {\n cleanup();\n return;\n }\n\n this.activeCleanups.set(key, cleanup);\n }\n\n private sync(): void {\n this.syncVersion += 1;\n\n for (const plugin of PLUGIN_MANIFEST) {\n if (plugin.name === \"errors\") {\n continue;\n }\n\n if (this.shouldEnablePlugin(plugin.name)) {\n this.activate(plugin.name).catch(() => {\n log.warn(\"non-critical plugin loading failed\");\n });\n continue;\n }\n\n this.deactivate(plugin.name);\n }\n }\n}\n"],"mappings":";;;;;;AAuBA,MAAM,MAAM,aAAa,iBAAiB;AAE1C,IAAa,gBAAb,MAA2B;CACzB,iCAAkC,IAAI,KAA+B;CACrE;CACA;CACA;CACA,cAAsB;CAEtB,YACE,SACA,WACA,gBACA;AACA,OAAK,UAAU;AACf,OAAK,WAAW,gBAAgB,UAAU;AAC1C,OAAK,eAAe,kBAAkB;;CAGxC,aAAkC;AAChC,SAAO,KAAK;;CAGd,WAAW,aAAkC;EAC3C,MAAM,YAAY,sBAAsB,YAAY;AACpD,MAAI,CAAC,kBAAkB,KAAK,cAAc,UAAU,CAClD;AAGF,OAAK,eAAe;AACpB,OAAK,MAAM;;CAGb,eAAqB;AACnB,MAAI,CAAC,kBAAkB,KAAK,cAAc,KAAK,CAC7C;AAGF,OAAK,eAAe;AACpB,OAAK,MAAM;;CAGb,WAAW,MAA0B;AACnC,SAAO,mBAAmB,MAAM,KAAK,aAAa;;CAGpD,QAAc;AACZ,MAAI,KAAK,SAAS,QAAQ;GACxB,MAAM,UAAU,aAAa,MAAM,KAAK,QAAQ;AAChD,OAAI,QACF,MAAK,eAAe,IAAI,UAAU,QAAQ;;AAI9C,OAAK,MAAM;;CAGb,UAAgB;AACd,OAAK,MAAM,OAAO,KAAK,eAAe,MAAM,CAC1C,MAAK,WAAW,IAAI;;CAIxB,mBAA2B,KAAyB;AAClD,SACE,KAAK,SAAS,QACd,iBAAiB,yBAAyB,IAAI,EAAE,KAAK,aAAa;;CAItE,WAAmB,KAAsB;EACvC,MAAM,UAAU,KAAK,eAAe,IAAI,IAAI;AAC5C,MAAI,CAAC,QACH;AAGF,MAAI;AACF,YAAS;UACH;AACN,OAAI,KAAK,yBAAyB,IAAI;;AAGxC,OAAK,eAAe,OAAO,IAAI;;CAGjC,MAAc,SAAS,KAA+B;AACpD,MAAI,KAAK,eAAe,IAAI,IAAI,IAAI,CAAC,KAAK,mBAAmB,IAAI,CAC/D;EAGF,MAAM,UAAU,KAAK;EACrB,MAAM,UAAU,MAAM,WAAW,KAAK,KAAK,QAAQ;AACnD,MAAI,CAAC,QACH;AAIF,MADkB,YAAY,KAAK,eAClB,CAAC,KAAK,mBAAmB,IAAI,EAAE;AAC9C,YAAS;AACT;;AAGF,OAAK,eAAe,IAAI,KAAK,QAAQ;;CAGvC,OAAqB;AACnB,OAAK,eAAe;AAEpB,OAAK,MAAM,UAAU,iBAAiB;AACpC,OAAI,OAAO,SAAS,SAClB;AAGF,OAAI,KAAK,mBAAmB,OAAO,KAAK,EAAE;AACxC,SAAK,SAAS,OAAO,KAAK,CAAC,YAAY;AACrC,SAAI,KAAK,qCAAqC;MAC9C;AACF;;AAGF,QAAK,WAAW,OAAO,KAAK"}
@@ -1,4 +0,0 @@
1
- //#region src/internal/sw.d.ts
2
- declare function registerServiceWorker(): void;
3
- //#endregion
4
- export { registerServiceWorker };
@@ -1 +0,0 @@
1
- {"version":3,"file":"sw.d.mts","names":[],"sources":["../../src/internal/sw.ts"],"mappings":";iBAMgB,qBAAA,CAAA"}
@@ -1,10 +0,0 @@
1
- import { createLogger } from "../util/log.mjs";
2
- //#region src/internal/sw.ts
3
- const log = createLogger("sw");
4
- const SW_PATH = "/api/interfere/sw";
5
- function registerServiceWorker() {
6
- if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
7
- navigator.serviceWorker.register(SW_PATH, { scope: "/" }).then(() => log.debug("registered")).catch(() => log.warn("registration failed, using direct fetch"));
8
- }
9
- //#endregion
10
- export { registerServiceWorker };
@@ -1 +0,0 @@
1
- {"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nconst SW_PATH = \"/api/interfere/sw\";\n\nexport function registerServiceWorker(): void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n navigator.serviceWorker\n .register(SW_PATH, { scope: \"/\" })\n .then(() => log.debug(\"registered\"))\n .catch(() => log.warn(\"registration failed, using direct fetch\"));\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,KAAK;AAE9B,MAAM,UAAU;AAEhB,SAAgB,wBAA8B;AAC5C,KAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,WAC3D;AAGF,WAAU,cACP,SAAS,SAAS,EAAE,OAAO,KAAK,CAAC,CACjC,WAAW,IAAI,MAAM,aAAa,CAAC,CACnC,YAAY,IAAI,KAAK,0CAA0C,CAAC"}
@@ -1,6 +0,0 @@
1
- import { Plugin } from "./lib/types.mjs";
2
-
3
- //#region src/plugins/errors.d.ts
4
- declare const errorsPlugin: Plugin;
5
- //#endregion
6
- export { errorsPlugin as default, errorsPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/plugins/errors.ts"],"mappings":";;;cA2Ba,YAAA,EAAc,MAAA"}
@@ -1,59 +0,0 @@
1
- import { seen } from "../internal/errors.mjs";
2
- import { toExceptions } from "@interfere/types/sdk/errors";
3
- //#region src/plugins/errors.ts
4
- let capturing = false;
5
- function capture(ctx, opts) {
6
- if (capturing || seen.has(opts.error)) return;
7
- seen.add(opts.error);
8
- capturing = true;
9
- try {
10
- ctx.capture("error", { exceptions: toExceptions(opts.error, opts.mechanism) });
11
- } finally {
12
- capturing = false;
13
- }
14
- }
15
- const errorsPlugin = {
16
- name: "errors",
17
- setup(ctx) {
18
- const originalOnError = globalThis.onerror;
19
- const originalConsoleError = globalThis.console.error;
20
- globalThis.onerror = (msg, source, line, col, error) => {
21
- if (error instanceof Error) capture(ctx, {
22
- error,
23
- mechanism: {
24
- type: "onerror",
25
- handled: false
26
- }
27
- });
28
- if (typeof originalOnError === "function") return originalOnError.call(globalThis, msg, source, line, col, error);
29
- return false;
30
- };
31
- const onUnhandledRejection = (event) => {
32
- if (event.reason instanceof Error) capture(ctx, {
33
- error: event.reason,
34
- mechanism: {
35
- type: "unhandledrejection",
36
- handled: false
37
- }
38
- });
39
- };
40
- globalThis.addEventListener("unhandledrejection", onUnhandledRejection);
41
- globalThis.console.error = (...args) => {
42
- originalConsoleError.apply(globalThis.console, args);
43
- if (args[0] instanceof Error) capture(ctx, {
44
- error: args[0],
45
- mechanism: {
46
- type: "console.error",
47
- handled: true
48
- }
49
- });
50
- };
51
- return () => {
52
- globalThis.onerror = originalOnError;
53
- globalThis.removeEventListener("unhandledrejection", onUnhandledRejection);
54
- globalThis.console.error = originalConsoleError;
55
- };
56
- }
57
- };
58
- //#endregion
59
- export { errorsPlugin as default, errorsPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/plugins/errors.ts"],"sourcesContent":["import { toExceptions } from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { seen } from \"../internal/errors.js\";\nimport type { Plugin, PluginContext } from \"./lib/types.js\";\n\nlet capturing = false;\n\nfunction capture(\n ctx: PluginContext,\n opts: { error: Error; mechanism: ErrorMechanism }\n) {\n if (capturing || seen.has(opts.error)) {\n return;\n }\n\n seen.add(opts.error);\n capturing = true;\n try {\n ctx.capture(\"error\", {\n exceptions: toExceptions(opts.error, opts.mechanism),\n });\n } finally {\n capturing = false;\n }\n}\n\nexport const errorsPlugin: Plugin = {\n name: \"errors\",\n\n setup(ctx) {\n const originalOnError = globalThis.onerror;\n const originalConsoleError = globalThis.console.error;\n\n globalThis.onerror = (msg, source, line, col, error) => {\n if (error instanceof Error) {\n capture(ctx, {\n error,\n mechanism: { type: \"onerror\", handled: false },\n });\n }\n if (typeof originalOnError === \"function\") {\n return originalOnError.call(globalThis, msg, source, line, col, error);\n }\n return false;\n };\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n if (event.reason instanceof Error) {\n capture(ctx, {\n error: event.reason,\n mechanism: { type: \"unhandledrejection\", handled: false },\n });\n }\n };\n globalThis.addEventListener(\"unhandledrejection\", onUnhandledRejection);\n\n globalThis.console.error = (...args: unknown[]) => {\n originalConsoleError.apply(globalThis.console, args);\n if (args[0] instanceof Error) {\n capture(ctx, {\n error: args[0],\n mechanism: { type: \"console.error\", handled: true },\n });\n }\n };\n\n return () => {\n globalThis.onerror = originalOnError;\n globalThis.removeEventListener(\n \"unhandledrejection\",\n onUnhandledRejection\n );\n globalThis.console.error = originalConsoleError;\n };\n },\n};\n\nexport default errorsPlugin;\n"],"mappings":";;;AAMA,IAAI,YAAY;AAEhB,SAAS,QACP,KACA,MACA;AACA,KAAI,aAAa,KAAK,IAAI,KAAK,MAAM,CACnC;AAGF,MAAK,IAAI,KAAK,MAAM;AACpB,aAAY;AACZ,KAAI;AACF,MAAI,QAAQ,SAAS,EACnB,YAAY,aAAa,KAAK,OAAO,KAAK,UAAU,EACrD,CAAC;WACM;AACR,cAAY;;;AAIhB,MAAa,eAAuB;CAClC,MAAM;CAEN,MAAM,KAAK;EACT,MAAM,kBAAkB,WAAW;EACnC,MAAM,uBAAuB,WAAW,QAAQ;AAEhD,aAAW,WAAW,KAAK,QAAQ,MAAM,KAAK,UAAU;AACtD,OAAI,iBAAiB,MACnB,SAAQ,KAAK;IACX;IACA,WAAW;KAAE,MAAM;KAAW,SAAS;KAAO;IAC/C,CAAC;AAEJ,OAAI,OAAO,oBAAoB,WAC7B,QAAO,gBAAgB,KAAK,YAAY,KAAK,QAAQ,MAAM,KAAK,MAAM;AAExE,UAAO;;EAGT,MAAM,wBAAwB,UAAiC;AAC7D,OAAI,MAAM,kBAAkB,MAC1B,SAAQ,KAAK;IACX,OAAO,MAAM;IACb,WAAW;KAAE,MAAM;KAAsB,SAAS;KAAO;IAC1D,CAAC;;AAGN,aAAW,iBAAiB,sBAAsB,qBAAqB;AAEvE,aAAW,QAAQ,SAAS,GAAG,SAAoB;AACjD,wBAAqB,MAAM,WAAW,SAAS,KAAK;AACpD,OAAI,KAAK,cAAc,MACrB,SAAQ,KAAK;IACX,OAAO,KAAK;IACZ,WAAW;KAAE,MAAM;KAAiB,SAAS;KAAM;IACpD,CAAC;;AAIN,eAAa;AACX,cAAW,UAAU;AACrB,cAAW,oBACT,sBACA,qBACD;AACD,cAAW,QAAQ,QAAQ;;;CAGhC"}
@@ -1,6 +0,0 @@
1
- import { Plugin } from "./lib/types.mjs";
2
-
3
- //#region src/plugins/fingerprint.d.ts
4
- declare const fingerprintPlugin: Plugin;
5
- //#endregion
6
- export { fingerprintPlugin as default, fingerprintPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"fingerprint.d.mts","names":[],"sources":["../../src/plugins/fingerprint.ts"],"mappings":";;;cAGa,iBAAA,EAAmB,MAAA"}
@@ -1,13 +0,0 @@
1
- import { initVisitor, resetVisitor } from "../tracking/visitor.mjs";
2
- //#region src/plugins/fingerprint.ts
3
- const fingerprintPlugin = {
4
- name: "fingerprint",
5
- setup() {
6
- initVisitor();
7
- return () => {
8
- resetVisitor();
9
- };
10
- }
11
- };
12
- //#endregion
13
- export { fingerprintPlugin as default, fingerprintPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"fingerprint.mjs","names":[],"sources":["../../src/plugins/fingerprint.ts"],"sourcesContent":["import { initVisitor, resetVisitor } from \"../tracking/visitor.js\";\nimport type { Plugin } from \"./lib/types.js\";\n\nexport const fingerprintPlugin: Plugin = {\n name: \"fingerprint\",\n\n setup() {\n initVisitor();\n\n return () => {\n resetVisitor();\n };\n },\n};\n\nexport default fingerprintPlugin;\n"],"mappings":";;AAGA,MAAa,oBAA4B;CACvC,MAAM;CAEN,QAAQ;AACN,eAAa;AAEb,eAAa;AACX,iBAAc;;;CAGnB"}
@@ -1,10 +0,0 @@
1
- import { PluginCleanup, PluginContext } from "./types.mjs";
2
- import { PluginKey } from "@interfere/types/sdk/plugins/manifest";
3
-
4
- //#region src/plugins/lib/loader.d.ts
5
- type PluginOverrides = Partial<Record<PluginKey, boolean>>;
6
- declare function resolveFeatures(overrides?: PluginOverrides): Record<PluginKey, boolean>;
7
- declare function loadPlugin(key: PluginKey, context: PluginContext): Promise<PluginCleanup | null>;
8
- declare function loadPlugins(overrides: PluginOverrides | undefined, context: PluginContext): Promise<PluginCleanup[]>;
9
- //#endregion
10
- export { PluginOverrides, loadPlugin, loadPlugins, resolveFeatures };
@@ -1 +0,0 @@
1
- {"version":3,"file":"loader.d.mts","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"mappings":";;;;KAwBY,eAAA,GAAkB,OAAA,CAAQ,MAAA,CAAO,SAAA;AAAA,iBAE7B,eAAA,CACd,SAAA,GAAY,eAAA,GACX,MAAA,CAAO,SAAA;AAAA,iBAUY,UAAA,CACpB,GAAA,EAAK,SAAA,EACL,OAAA,EAAS,aAAA,GACR,OAAA,CAAQ,aAAA;AAAA,iBAkBW,WAAA,CACpB,SAAA,EAAW,eAAA,cACX,OAAA,EAAS,aAAA,GACR,OAAA,CAAQ,aAAA"}
@@ -1,43 +0,0 @@
1
- import { createLogger } from "../../util/log.mjs";
2
- import { PLUGIN_MANIFEST } from "@interfere/types/sdk/plugins/manifest";
3
- //#region src/plugins/lib/loader.ts
4
- const log = createLogger("plugins");
5
- const LOADERS = {
6
- errors: () => import("../errors.mjs"),
7
- fingerprint: () => import("../fingerprint.mjs"),
8
- pageEvents: () => import("../pages.mjs"),
9
- rageClick: () => import("../rage-clicks.mjs"),
10
- replay: () => import("../replay.mjs")
11
- };
12
- const DEFAULTS = Object.fromEntries(PLUGIN_MANIFEST.map((p) => [p.name, p.defaultEnabled]));
13
- function resolveFeatures(overrides) {
14
- return {
15
- ...DEFAULTS,
16
- ...overrides
17
- };
18
- }
19
- function resolvePlugin(mod) {
20
- return "default" in mod && typeof mod.default.setup === "function" ? mod.default : mod;
21
- }
22
- async function loadPlugin(key, context) {
23
- const loader = LOADERS[key];
24
- if (!loader) return null;
25
- try {
26
- const cleanup = resolvePlugin(await loader()).setup(context);
27
- log.debug("loaded %s", key);
28
- return typeof cleanup === "function" ? cleanup : null;
29
- } catch {
30
- log.error("failed to load plugin %s", key);
31
- return null;
32
- }
33
- }
34
- async function loadPlugins(overrides, context) {
35
- const resolved = {
36
- ...DEFAULTS,
37
- ...overrides
38
- };
39
- const keys = Object.entries(resolved).filter(([key, enabled]) => enabled && key in LOADERS).map(([key]) => key);
40
- return (await Promise.all(keys.map(async (key) => loadPlugin(key, context)))).filter((cleanup) => cleanup !== null);
41
- }
42
- //#endregion
43
- export { loadPlugin, loadPlugins, resolveFeatures };
@@ -1 +0,0 @@
1
- {"version":3,"file":"loader.mjs","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"sourcesContent":["import {\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createLogger } from \"../../util/log.js\";\nimport type { Plugin, PluginCleanup, PluginContext } from \"./types.js\";\n\nconst log = createLogger(\"plugins\");\n\ntype PluginLoader = () => Promise<{ default: Plugin } | Plugin>;\n\nconst LOADERS: Partial<Record<PluginKey, PluginLoader>> = {\n errors: () => import(\"../errors.js\"),\n fingerprint: () => import(\"../fingerprint.js\"),\n pageEvents: () => import(\"../pages.js\"),\n rageClick: () => import(\"../rage-clicks.js\"),\n replay: () => import(\"../replay.js\"),\n};\n\nconst DEFAULTS: Record<PluginKey, boolean> = Object.fromEntries(\n PLUGIN_MANIFEST.map((p) => [p.name, p.defaultEnabled])\n) as Record<PluginKey, boolean>;\n\nexport type PluginOverrides = Partial<Record<PluginKey, boolean>>;\n\nexport function resolveFeatures(\n overrides?: PluginOverrides\n): Record<PluginKey, boolean> {\n return { ...DEFAULTS, ...overrides };\n}\n\nfunction resolvePlugin(mod: { default: Plugin } | Plugin): Plugin {\n return \"default\" in mod && typeof (mod.default as Plugin).setup === \"function\"\n ? mod.default\n : (mod as Plugin);\n}\n\nexport async function loadPlugin(\n key: PluginKey,\n context: PluginContext\n): Promise<PluginCleanup | null> {\n const loader = LOADERS[key];\n if (!loader) {\n return null;\n }\n\n try {\n const mod = await loader();\n const plugin = resolvePlugin(mod);\n const cleanup = plugin.setup(context);\n log.debug(\"loaded %s\", key);\n return typeof cleanup === \"function\" ? cleanup : null;\n } catch {\n log.error(\"failed to load plugin %s\", key);\n return null;\n }\n}\n\nexport async function loadPlugins(\n overrides: PluginOverrides | undefined,\n context: PluginContext\n): Promise<PluginCleanup[]> {\n const resolved = { ...DEFAULTS, ...overrides };\n const keys = (Object.entries(resolved) as [PluginKey, boolean][])\n .filter(([key, enabled]) => enabled && key in LOADERS)\n .map(([key]) => key);\n\n const cleanups = await Promise.all(\n keys.map(async (key) => loadPlugin(key, context))\n );\n return cleanups.filter((cleanup) => cleanup !== null);\n}\n"],"mappings":";;;AAQA,MAAM,MAAM,aAAa,UAAU;AAInC,MAAM,UAAoD;CACxD,cAAc,OAAO;CACrB,mBAAmB,OAAO;CAC1B,kBAAkB,OAAO;CACzB,iBAAiB,OAAO;CACxB,cAAc,OAAO;CACtB;AAED,MAAM,WAAuC,OAAO,YAClD,gBAAgB,KAAK,MAAM,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,CACvD;AAID,SAAgB,gBACd,WAC4B;AAC5B,QAAO;EAAE,GAAG;EAAU,GAAG;EAAW;;AAGtC,SAAS,cAAc,KAA2C;AAChE,QAAO,aAAa,OAAO,OAAQ,IAAI,QAAmB,UAAU,aAChE,IAAI,UACH;;AAGP,eAAsB,WACpB,KACA,SAC+B;CAC/B,MAAM,SAAS,QAAQ;AACvB,KAAI,CAAC,OACH,QAAO;AAGT,KAAI;EAGF,MAAM,UADS,cADH,MAAM,QAAQ,CACO,CACV,MAAM,QAAQ;AACrC,MAAI,MAAM,aAAa,IAAI;AAC3B,SAAO,OAAO,YAAY,aAAa,UAAU;SAC3C;AACN,MAAI,MAAM,4BAA4B,IAAI;AAC1C,SAAO;;;AAIX,eAAsB,YACpB,WACA,SAC0B;CAC1B,MAAM,WAAW;EAAE,GAAG;EAAU,GAAG;EAAW;CAC9C,MAAM,OAAQ,OAAO,QAAQ,SAAS,CACnC,QAAQ,CAAC,KAAK,aAAa,WAAW,OAAO,QAAQ,CACrD,KAAK,CAAC,SAAS,IAAI;AAKtB,SAHiB,MAAM,QAAQ,IAC7B,KAAK,IAAI,OAAO,QAAQ,WAAW,KAAK,QAAQ,CAAC,CAClD,EACe,QAAQ,YAAY,YAAY,KAAK"}
@@ -1,14 +0,0 @@
1
- import { EnvelopePayload, EventType } from "@interfere/types/sdk/envelope";
2
-
3
- //#region src/plugins/lib/types.d.ts
4
- type PluginCleanup = () => void;
5
- interface PluginContext {
6
- capture<T extends EventType>(type: T, payload: EnvelopePayload<T>): void;
7
- getSessionId(): string;
8
- }
9
- interface Plugin {
10
- readonly name: string;
11
- setup(ctx: PluginContext): PluginCleanup | undefined;
12
- }
13
- //#endregion
14
- export { Plugin, PluginCleanup, PluginContext };
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/plugins/lib/types.ts"],"mappings":";;;KAEY,aAAA;AAAA,UAEK,aAAA;EACf,OAAA,WAAkB,SAAA,EAAW,IAAA,EAAM,CAAA,EAAG,OAAA,EAAS,eAAA,CAAgB,CAAA;EAC/D,YAAA;AAAA;AAAA,UAGe,MAAA;EAAA,SACN,IAAA;EACT,KAAA,CAAM,GAAA,EAAK,aAAA,GAAgB,aAAA;AAAA"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,6 +0,0 @@
1
- import { Plugin } from "./lib/types.mjs";
2
-
3
- //#region src/plugins/pages.d.ts
4
- declare const pagesPlugin: Plugin;
5
- //#endregion
6
- export { pagesPlugin as default, pagesPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"pages.d.mts","names":[],"sources":["../../src/plugins/pages.ts"],"mappings":";;;cAmDa,WAAA,EAAa,MAAA"}
@@ -1,102 +0,0 @@
1
- //#region src/plugins/pages.ts
2
- const INTERACTIVE_TAGS = new Set([
3
- "A",
4
- "BUTTON",
5
- "INPUT",
6
- "SELECT",
7
- "TEXTAREA"
8
- ]);
9
- const INTERACTIVE_ROLES = new Set([
10
- "button",
11
- "link",
12
- "tab",
13
- "menuitem",
14
- "checkbox",
15
- "radio",
16
- "switch"
17
- ]);
18
- function isInteractive(el) {
19
- if (INTERACTIVE_TAGS.has(el.tagName)) return true;
20
- const role = el.getAttribute("role");
21
- return role !== null && INTERACTIVE_ROLES.has(role);
22
- }
23
- function closestInteractive(target) {
24
- if (!(target instanceof Element)) return null;
25
- let el = target;
26
- while (el) {
27
- if (isInteractive(el)) return el;
28
- el = el.parentElement;
29
- }
30
- return null;
31
- }
32
- function elementDescriptor(el) {
33
- return {
34
- tag: el.tagName.toLowerCase(),
35
- text: el.textContent?.trim().slice(0, 120) ?? null,
36
- ...el.id && { id: el.id },
37
- ...el.getAttribute("role") && { role: el.getAttribute("role") },
38
- ...el instanceof HTMLAnchorElement && { href: el.href }
39
- };
40
- }
41
- const pagesPlugin = {
42
- name: "pages",
43
- setup(ctx) {
44
- let pageEnteredAt = Date.now();
45
- let currentUrl = location.href;
46
- const emitPageview = () => {
47
- currentUrl = location.href;
48
- pageEnteredAt = Date.now();
49
- ctx.capture("pageview", {
50
- url: currentUrl,
51
- title: document.title || void 0
52
- });
53
- };
54
- const emitPageleave = () => {
55
- ctx.capture("pageleave", {
56
- url: currentUrl,
57
- durationMs: Date.now() - pageEnteredAt
58
- });
59
- };
60
- const originalPushState = history.pushState;
61
- const originalReplaceState = history.replaceState;
62
- history.pushState = function(...args) {
63
- emitPageleave();
64
- originalPushState.apply(this, args);
65
- emitPageview();
66
- };
67
- history.replaceState = function(...args) {
68
- originalReplaceState.apply(this, args);
69
- currentUrl = location.href;
70
- };
71
- const onPopState = () => {
72
- emitPageleave();
73
- emitPageview();
74
- };
75
- globalThis.addEventListener("popstate", onPopState);
76
- const onBeforeUnload = () => {
77
- emitPageleave();
78
- };
79
- globalThis.addEventListener("beforeunload", onBeforeUnload);
80
- const onVisibilityChange = () => {
81
- if (document.visibilityState === "hidden") emitPageleave();
82
- };
83
- globalThis.addEventListener("visibilitychange", onVisibilityChange);
84
- const onClick = (event) => {
85
- const el = closestInteractive(event.target);
86
- if (!el) return;
87
- ctx.capture("ui_event", { event: elementDescriptor(el) });
88
- };
89
- document.addEventListener("click", onClick, { capture: true });
90
- emitPageview();
91
- return () => {
92
- history.pushState = originalPushState;
93
- history.replaceState = originalReplaceState;
94
- globalThis.removeEventListener("popstate", onPopState);
95
- globalThis.removeEventListener("beforeunload", onBeforeUnload);
96
- globalThis.removeEventListener("visibilitychange", onVisibilityChange);
97
- document.removeEventListener("click", onClick, { capture: true });
98
- };
99
- }
100
- };
101
- //#endregion
102
- export { pagesPlugin as default, pagesPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"pages.mjs","names":[],"sources":["../../src/plugins/pages.ts"],"sourcesContent":["import type { Plugin } from \"./lib/types.js\";\n\nconst INTERACTIVE_TAGS = new Set([\n \"A\",\n \"BUTTON\",\n \"INPUT\",\n \"SELECT\",\n \"TEXTAREA\",\n]);\nconst INTERACTIVE_ROLES = new Set([\n \"button\",\n \"link\",\n \"tab\",\n \"menuitem\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n]);\n\nfunction isInteractive(el: Element): boolean {\n if (INTERACTIVE_TAGS.has(el.tagName)) {\n return true;\n }\n const role = el.getAttribute(\"role\");\n return role !== null && INTERACTIVE_ROLES.has(role);\n}\n\nfunction closestInteractive(target: EventTarget | null): Element | null {\n if (!(target instanceof Element)) {\n return null;\n }\n let el: Element | null = target;\n while (el) {\n if (isInteractive(el)) {\n return el;\n }\n el = el.parentElement;\n }\n return null;\n}\n\nfunction elementDescriptor(el: Element): Record<string, unknown> {\n return {\n tag: el.tagName.toLowerCase(),\n text: el.textContent?.trim().slice(0, 120) ?? null,\n ...(el.id && { id: el.id }),\n ...(el.getAttribute(\"role\") && { role: el.getAttribute(\"role\") }),\n ...(el instanceof HTMLAnchorElement && { href: el.href }),\n };\n}\n\nexport const pagesPlugin: Plugin = {\n name: \"pages\",\n\n setup(ctx) {\n let pageEnteredAt = Date.now();\n let currentUrl = location.href;\n\n const emitPageview = () => {\n currentUrl = location.href;\n pageEnteredAt = Date.now();\n ctx.capture(\"pageview\", {\n url: currentUrl,\n title: document.title || undefined,\n });\n };\n\n const emitPageleave = () => {\n ctx.capture(\"pageleave\", {\n url: currentUrl,\n durationMs: Date.now() - pageEnteredAt,\n });\n };\n\n // Patch history API for SPA navigation\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n\n history.pushState = function (...args) {\n emitPageleave();\n originalPushState.apply(this, args);\n emitPageview();\n };\n\n history.replaceState = function (...args) {\n originalReplaceState.apply(this, args);\n // replaceState doesn't create a new entry — update URL tracking but skip pageview\n currentUrl = location.href;\n };\n\n const onPopState = () => {\n emitPageleave();\n emitPageview();\n };\n globalThis.addEventListener(\"popstate\", onPopState);\n\n const onBeforeUnload = () => {\n emitPageleave();\n };\n globalThis.addEventListener(\"beforeunload\", onBeforeUnload);\n\n const onVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") {\n emitPageleave();\n }\n };\n globalThis.addEventListener(\"visibilitychange\", onVisibilityChange);\n\n const onClick = (event: MouseEvent) => {\n const el = closestInteractive(event.target);\n if (!el) {\n return;\n }\n ctx.capture(\"ui_event\", { event: elementDescriptor(el) });\n };\n document.addEventListener(\"click\", onClick, { capture: true });\n\n // Initial pageview\n emitPageview();\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n globalThis.removeEventListener(\"popstate\", onPopState);\n globalThis.removeEventListener(\"beforeunload\", onBeforeUnload);\n globalThis.removeEventListener(\"visibilitychange\", onVisibilityChange);\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default pagesPlugin;\n"],"mappings":";AAEA,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACD,CAAC;AACF,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,cAAc,IAAsB;AAC3C,KAAI,iBAAiB,IAAI,GAAG,QAAQ,CAClC,QAAO;CAET,MAAM,OAAO,GAAG,aAAa,OAAO;AACpC,QAAO,SAAS,QAAQ,kBAAkB,IAAI,KAAK;;AAGrD,SAAS,mBAAmB,QAA4C;AACtE,KAAI,EAAE,kBAAkB,SACtB,QAAO;CAET,IAAI,KAAqB;AACzB,QAAO,IAAI;AACT,MAAI,cAAc,GAAG,CACnB,QAAO;AAET,OAAK,GAAG;;AAEV,QAAO;;AAGT,SAAS,kBAAkB,IAAsC;AAC/D,QAAO;EACL,KAAK,GAAG,QAAQ,aAAa;EAC7B,MAAM,GAAG,aAAa,MAAM,CAAC,MAAM,GAAG,IAAI,IAAI;EAC9C,GAAI,GAAG,MAAM,EAAE,IAAI,GAAG,IAAI;EAC1B,GAAI,GAAG,aAAa,OAAO,IAAI,EAAE,MAAM,GAAG,aAAa,OAAO,EAAE;EAChE,GAAI,cAAc,qBAAqB,EAAE,MAAM,GAAG,MAAM;EACzD;;AAGH,MAAa,cAAsB;CACjC,MAAM;CAEN,MAAM,KAAK;EACT,IAAI,gBAAgB,KAAK,KAAK;EAC9B,IAAI,aAAa,SAAS;EAE1B,MAAM,qBAAqB;AACzB,gBAAa,SAAS;AACtB,mBAAgB,KAAK,KAAK;AAC1B,OAAI,QAAQ,YAAY;IACtB,KAAK;IACL,OAAO,SAAS,SAAS,KAAA;IAC1B,CAAC;;EAGJ,MAAM,sBAAsB;AAC1B,OAAI,QAAQ,aAAa;IACvB,KAAK;IACL,YAAY,KAAK,KAAK,GAAG;IAC1B,CAAC;;EAIJ,MAAM,oBAAoB,QAAQ;EAClC,MAAM,uBAAuB,QAAQ;AAErC,UAAQ,YAAY,SAAU,GAAG,MAAM;AACrC,kBAAe;AACf,qBAAkB,MAAM,MAAM,KAAK;AACnC,iBAAc;;AAGhB,UAAQ,eAAe,SAAU,GAAG,MAAM;AACxC,wBAAqB,MAAM,MAAM,KAAK;AAEtC,gBAAa,SAAS;;EAGxB,MAAM,mBAAmB;AACvB,kBAAe;AACf,iBAAc;;AAEhB,aAAW,iBAAiB,YAAY,WAAW;EAEnD,MAAM,uBAAuB;AAC3B,kBAAe;;AAEjB,aAAW,iBAAiB,gBAAgB,eAAe;EAE3D,MAAM,2BAA2B;AAC/B,OAAI,SAAS,oBAAoB,SAC/B,gBAAe;;AAGnB,aAAW,iBAAiB,oBAAoB,mBAAmB;EAEnE,MAAM,WAAW,UAAsB;GACrC,MAAM,KAAK,mBAAmB,MAAM,OAAO;AAC3C,OAAI,CAAC,GACH;AAEF,OAAI,QAAQ,YAAY,EAAE,OAAO,kBAAkB,GAAG,EAAE,CAAC;;AAE3D,WAAS,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;AAG9D,gBAAc;AAEd,eAAa;AACX,WAAQ,YAAY;AACpB,WAAQ,eAAe;AACvB,cAAW,oBAAoB,YAAY,WAAW;AACtD,cAAW,oBAAoB,gBAAgB,eAAe;AAC9D,cAAW,oBAAoB,oBAAoB,mBAAmB;AACtE,YAAS,oBAAoB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;;;CAGtE"}
@@ -1,6 +0,0 @@
1
- import { Plugin } from "./lib/types.mjs";
2
-
3
- //#region src/plugins/rage-clicks.d.ts
4
- declare const rageClicksPlugin: Plugin;
5
- //#endregion
6
- export { rageClicksPlugin as default, rageClicksPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;cA6Ba,gBAAA,EAAkB,MAAA"}
@@ -1,53 +0,0 @@
1
- //#region src/plugins/rage-clicks.ts
2
- const CLICK_THRESHOLD = 3;
3
- const TIME_WINDOW_MS = 800;
4
- const PROXIMITY_PX = 30;
5
- function distance(a, b) {
6
- return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
7
- }
8
- function selectorFor(el) {
9
- if (!el) return "unknown";
10
- if (el.id) return `#${el.id}`;
11
- const classes = [...el.classList].slice(0, 3).join(".");
12
- const tag = el.tagName.toLowerCase();
13
- return classes ? `${tag}.${classes}` : tag;
14
- }
15
- const rageClicksPlugin = {
16
- name: "rage-clicks",
17
- setup(ctx) {
18
- const clicks = [];
19
- const onClick = (event) => {
20
- const now = Date.now();
21
- clicks.push({
22
- x: event.clientX,
23
- y: event.clientY,
24
- ts: now,
25
- target: event.target instanceof Element ? event.target : null
26
- });
27
- while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) clicks.shift();
28
- if (clicks.length < CLICK_THRESHOLD) return;
29
- const anchor = clicks[0];
30
- if (!anchor) return;
31
- const clustered = clicks.filter((c) => distance(anchor, c) <= PROXIMITY_PX);
32
- if (clustered.length < CLICK_THRESHOLD) return;
33
- const last = clustered.at(-1);
34
- if (!last) return;
35
- ctx.capture("rage_click", {
36
- count: clustered.length,
37
- timeWindow: last.ts - anchor.ts,
38
- selector: selectorFor(last.target),
39
- text: last.target?.textContent?.trim().slice(0, 120) ?? "",
40
- x: last.x,
41
- y: last.y,
42
- timestamp: now
43
- });
44
- clicks.length = 0;
45
- };
46
- document.addEventListener("click", onClick, { capture: true });
47
- return () => {
48
- document.removeEventListener("click", onClick, { capture: true });
49
- };
50
- }
51
- };
52
- //#endregion
53
- export { rageClicksPlugin as default, rageClicksPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"rage-clicks.mjs","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"sourcesContent":["import type { Plugin } from \"./lib/types.js\";\n\nconst CLICK_THRESHOLD = 3;\nconst TIME_WINDOW_MS = 800;\nconst PROXIMITY_PX = 30;\n\ninterface Click {\n target: Element | null;\n ts: number;\n x: number;\n y: number;\n}\n\nfunction distance(a: Click, b: Click): number {\n return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n}\n\nfunction selectorFor(el: Element | null): string {\n if (!el) {\n return \"unknown\";\n }\n if (el.id) {\n return `#${el.id}`;\n }\n const classes = [...el.classList].slice(0, 3).join(\".\");\n const tag = el.tagName.toLowerCase();\n return classes ? `${tag}.${classes}` : tag;\n}\n\nexport const rageClicksPlugin: Plugin = {\n name: \"rage-clicks\",\n\n setup(ctx) {\n const clicks: Click[] = [];\n\n const onClick = (event: MouseEvent) => {\n const now = Date.now();\n clicks.push({\n x: event.clientX,\n y: event.clientY,\n ts: now,\n target: event.target instanceof Element ? event.target : null,\n });\n\n // Prune stale clicks\n while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) {\n clicks.shift();\n }\n\n if (clicks.length < CLICK_THRESHOLD) {\n return;\n }\n\n // Check proximity — all clicks within PROXIMITY_PX of the first\n const anchor = clicks[0];\n if (!anchor) {\n return;\n }\n const clustered = clicks.filter(\n (c) => distance(anchor, c) <= PROXIMITY_PX\n );\n if (clustered.length < CLICK_THRESHOLD) {\n return;\n }\n\n const last = clustered.at(-1);\n if (!last) {\n return;\n }\n ctx.capture(\"rage_click\", {\n count: clustered.length,\n timeWindow: last.ts - anchor.ts,\n selector: selectorFor(last.target),\n text: last.target?.textContent?.trim().slice(0, 120) ?? \"\",\n x: last.x,\n y: last.y,\n timestamp: now,\n });\n\n clicks.length = 0;\n };\n\n document.addEventListener(\"click\", onClick, { capture: true });\n\n return () => {\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default rageClicksPlugin;\n"],"mappings":";AAEA,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,eAAe;AASrB,SAAS,SAAS,GAAU,GAAkB;AAC5C,QAAO,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE;;AAGvD,SAAS,YAAY,IAA4B;AAC/C,KAAI,CAAC,GACH,QAAO;AAET,KAAI,GAAG,GACL,QAAO,IAAI,GAAG;CAEhB,MAAM,UAAU,CAAC,GAAG,GAAG,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI;CACvD,MAAM,MAAM,GAAG,QAAQ,aAAa;AACpC,QAAO,UAAU,GAAG,IAAI,GAAG,YAAY;;AAGzC,MAAa,mBAA2B;CACtC,MAAM;CAEN,MAAM,KAAK;EACT,MAAM,SAAkB,EAAE;EAE1B,MAAM,WAAW,UAAsB;GACrC,MAAM,MAAM,KAAK,KAAK;AACtB,UAAO,KAAK;IACV,GAAG,MAAM;IACT,GAAG,MAAM;IACT,IAAI;IACJ,QAAQ,MAAM,kBAAkB,UAAU,MAAM,SAAS;IAC1D,CAAC;AAGF,UAAO,OAAO,SAAS,KAAK,OAAO,OAAO,IAAI,MAAM,KAAK,eACvD,QAAO,OAAO;AAGhB,OAAI,OAAO,SAAS,gBAClB;GAIF,MAAM,SAAS,OAAO;AACtB,OAAI,CAAC,OACH;GAEF,MAAM,YAAY,OAAO,QACtB,MAAM,SAAS,QAAQ,EAAE,IAAI,aAC/B;AACD,OAAI,UAAU,SAAS,gBACrB;GAGF,MAAM,OAAO,UAAU,GAAG,GAAG;AAC7B,OAAI,CAAC,KACH;AAEF,OAAI,QAAQ,cAAc;IACxB,OAAO,UAAU;IACjB,YAAY,KAAK,KAAK,OAAO;IAC7B,UAAU,YAAY,KAAK,OAAO;IAClC,MAAM,KAAK,QAAQ,aAAa,MAAM,CAAC,MAAM,GAAG,IAAI,IAAI;IACxD,GAAG,KAAK;IACR,GAAG,KAAK;IACR,WAAW;IACZ,CAAC;AAEF,UAAO,SAAS;;AAGlB,WAAS,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;AAE9D,eAAa;AACX,YAAS,oBAAoB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;;;CAGtE"}
@@ -1,6 +0,0 @@
1
- import { Plugin } from "./lib/types.mjs";
2
-
3
- //#region src/plugins/replay.d.ts
4
- declare const replayPlugin: Plugin;
5
- //#endregion
6
- export { replayPlugin as default, replayPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"replay.d.mts","names":[],"sources":["../../src/plugins/replay.ts"],"mappings":";;;cAOa,YAAA,EAAc,MAAA"}
@@ -1,62 +0,0 @@
1
- import { createLogger } from "../util/log.mjs";
2
- //#region src/plugins/replay.ts
3
- const log = createLogger("replay");
4
- const FLUSH_INTERVAL_MS = 1e4;
5
- const replayPlugin = {
6
- name: "replay",
7
- setup(ctx) {
8
- let stopFn = null;
9
- let events = [];
10
- let flushTimer = null;
11
- let firstTs = null;
12
- let lastTs = null;
13
- const flush = () => {
14
- if (events.length === 0) return;
15
- const chunk = events;
16
- events = [];
17
- const fts = firstTs;
18
- const lts = lastTs;
19
- firstTs = null;
20
- lastTs = null;
21
- ctx.capture("replay_chunk", {
22
- ts: Date.now(),
23
- count: chunk.length,
24
- events: chunk,
25
- ...fts !== null && { first_event_ts: fts },
26
- ...lts !== null && { last_event_ts: lts }
27
- });
28
- };
29
- const onVisibilityChange = () => {
30
- if (document.visibilityState === "hidden") flush();
31
- };
32
- const onBeforeUnload = () => {
33
- flush();
34
- };
35
- const init = async () => {
36
- try {
37
- stopFn = (await import("rrweb")).record({ emit(event) {
38
- const ts = Date.now();
39
- if (firstTs === null) firstTs = ts;
40
- lastTs = ts;
41
- events.push(JSON.stringify(event));
42
- } }) ?? null;
43
- flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);
44
- globalThis.addEventListener("visibilitychange", onVisibilityChange);
45
- globalThis.addEventListener("beforeunload", onBeforeUnload);
46
- log.debug("recording started");
47
- } catch {
48
- log.error("rrweb failed to load, replay disabled");
49
- }
50
- };
51
- init().catch(() => {});
52
- return () => {
53
- flush();
54
- stopFn?.();
55
- if (flushTimer) clearInterval(flushTimer);
56
- globalThis.removeEventListener("visibilitychange", onVisibilityChange);
57
- globalThis.removeEventListener("beforeunload", onBeforeUnload);
58
- };
59
- }
60
- };
61
- //#endregion
62
- export { replayPlugin as default, replayPlugin };
@@ -1 +0,0 @@
1
- {"version":3,"file":"replay.mjs","names":[],"sources":["../../src/plugins/replay.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\nimport type { Plugin } from \"./lib/types.js\";\n\nconst log = createLogger(\"replay\");\n\nconst FLUSH_INTERVAL_MS = 10_000;\n\nexport const replayPlugin: Plugin = {\n name: \"replay\",\n\n setup(ctx) {\n let stopFn: (() => void) | null = null;\n let events: string[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let firstTs: number | null = null;\n let lastTs: number | null = null;\n\n const flush = () => {\n if (events.length === 0) {\n return;\n }\n const chunk = events;\n events = [];\n const fts = firstTs;\n const lts = lastTs;\n firstTs = null;\n lastTs = null;\n\n ctx.capture(\"replay_chunk\", {\n ts: Date.now(),\n count: chunk.length,\n events: chunk,\n ...(fts !== null && { first_event_ts: fts }),\n ...(lts !== null && { last_event_ts: lts }),\n });\n };\n\n const onVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") {\n flush();\n }\n };\n const onBeforeUnload = () => {\n flush();\n };\n\n const init = async () => {\n try {\n const rrweb = await import(\"rrweb\");\n stopFn =\n rrweb.record({\n emit(event) {\n const ts = Date.now();\n if (firstTs === null) {\n firstTs = ts;\n }\n lastTs = ts;\n events.push(JSON.stringify(event));\n },\n }) ?? null;\n\n flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);\n globalThis.addEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.addEventListener(\"beforeunload\", onBeforeUnload);\n log.debug(\"recording started\");\n } catch {\n log.error(\"rrweb failed to load, replay disabled\");\n }\n };\n\n init().catch(() => {\n // rrweb load failure is non-fatal\n });\n\n return () => {\n flush();\n stopFn?.();\n if (flushTimer) {\n clearInterval(flushTimer);\n }\n globalThis.removeEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener(\"beforeunload\", onBeforeUnload);\n };\n },\n};\n\nexport default replayPlugin;\n"],"mappings":";;AAGA,MAAM,MAAM,aAAa,SAAS;AAElC,MAAM,oBAAoB;AAE1B,MAAa,eAAuB;CAClC,MAAM;CAEN,MAAM,KAAK;EACT,IAAI,SAA8B;EAClC,IAAI,SAAmB,EAAE;EACzB,IAAI,aAAoD;EACxD,IAAI,UAAyB;EAC7B,IAAI,SAAwB;EAE5B,MAAM,cAAc;AAClB,OAAI,OAAO,WAAW,EACpB;GAEF,MAAM,QAAQ;AACd,YAAS,EAAE;GACX,MAAM,MAAM;GACZ,MAAM,MAAM;AACZ,aAAU;AACV,YAAS;AAET,OAAI,QAAQ,gBAAgB;IAC1B,IAAI,KAAK,KAAK;IACd,OAAO,MAAM;IACb,QAAQ;IACR,GAAI,QAAQ,QAAQ,EAAE,gBAAgB,KAAK;IAC3C,GAAI,QAAQ,QAAQ,EAAE,eAAe,KAAK;IAC3C,CAAC;;EAGJ,MAAM,2BAA2B;AAC/B,OAAI,SAAS,oBAAoB,SAC/B,QAAO;;EAGX,MAAM,uBAAuB;AAC3B,UAAO;;EAGT,MAAM,OAAO,YAAY;AACvB,OAAI;AAEF,cADc,MAAM,OAAO,UAEnB,OAAO,EACX,KAAK,OAAO;KACV,MAAM,KAAK,KAAK,KAAK;AACrB,SAAI,YAAY,KACd,WAAU;AAEZ,cAAS;AACT,YAAO,KAAK,KAAK,UAAU,MAAM,CAAC;OAErC,CAAC,IAAI;AAER,iBAAa,YAAY,OAAO,kBAAkB;AAClD,eAAW,iBAAiB,oBAAoB,mBAAmB;AACnE,eAAW,iBAAiB,gBAAgB,eAAe;AAC3D,QAAI,MAAM,oBAAoB;WACxB;AACN,QAAI,MAAM,wCAAwC;;;AAItD,QAAM,CAAC,YAAY,GAEjB;AAEF,eAAa;AACX,UAAO;AACP,aAAU;AACV,OAAI,WACF,eAAc,WAAW;AAE3B,cAAW,oBAAoB,oBAAoB,mBAAmB;AACtE,cAAW,oBAAoB,gBAAgB,eAAe;;;CAGnE"}
@@ -1,30 +0,0 @@
1
- import { PropsWithChildren, ReactNode } from "react";
2
- import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
3
- import { IdentifyParams } from "@interfere/types/sdk/identify";
4
-
5
- //#region src/provider.d.ts
6
- interface InterfereContextValue {
7
- consent: {
8
- get(): ConsentState | null;
9
- set(state?: ConsentState): void;
10
- };
11
- identity: {
12
- get(): IdentifyParams | null;
13
- set(params: IdentifyParams): void;
14
- };
15
- session: {
16
- getId(): string | null;
17
- getWindowId(): string | null;
18
- };
19
- }
20
- interface InterfereProviderProps extends PropsWithChildren {
21
- consent?: ConsentState;
22
- }
23
- declare function InterfereProvider({
24
- children,
25
- consent
26
- }: InterfereProviderProps): ReactNode;
27
- declare function useInterfere(): InterfereContextValue;
28
- declare function useSession(): string | null;
29
- //#endregion
30
- export { InterfereProvider, useInterfere, useSession };
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider.d.mts","names":[],"sources":["../src/provider.tsx"],"mappings":";;;;;UAgBU,qBAAA;EACR,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA;EAAA;EAEd,OAAA;IACE,KAAA;IACA,WAAA;EAAA;AAAA;AAAA,UAMM,sBAAA,SAA+B,iBAAA;EACvC,OAAA,GAAU,YAAA;AAAA;AAAA,iBAGI,iBAAA,CAAA;EACd,QAAA;EACA;AAAA,GACC,sBAAA,GAAyB,SAAA;AAAA,iBAcZ,YAAA,CAAA,GAAgB,qBAAA;AAAA,iBAQhB,UAAA,CAAA"}
package/dist/provider.mjs DELETED
@@ -1,30 +0,0 @@
1
- "use client";
2
- import { identity, session } from "./tracking/api.mjs";
3
- import { consent, syncConsent } from "./internal/client.mjs";
4
- import { createContext, useContext, useEffect } from "react";
5
- import { jsx } from "react/jsx-runtime";
6
- //#region src/provider.tsx
7
- const InterfereContext = createContext(null);
8
- function InterfereProvider({ children, consent: consent$1 }) {
9
- useEffect(() => {
10
- syncConsent(consent$1);
11
- }, [consent$1]);
12
- return /* @__PURE__ */ jsx(InterfereContext, {
13
- value: {
14
- consent,
15
- identity,
16
- session
17
- },
18
- children
19
- });
20
- }
21
- function useInterfere() {
22
- const ctx = useContext(InterfereContext);
23
- if (!ctx) throw new Error("useInterfere must be used within <InterfereProvider>");
24
- return ctx;
25
- }
26
- function useSession() {
27
- return useInterfere().session.getId();
28
- }
29
- //#endregion
30
- export { InterfereProvider, useInterfere, useSession };
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider.mjs","names":["consent","sdkConsent"],"sources":["../src/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport {\n createContext,\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n} from \"react\";\n\nimport { consent as sdkConsent, syncConsent } from \"./internal/client.js\";\nimport { identity, session } from \"./tracking/api.js\";\n\ninterface InterfereContextValue {\n consent: {\n get(): ConsentState | null;\n set(state?: ConsentState): void;\n };\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): void;\n };\n session: {\n getId(): string | null;\n getWindowId(): string | null;\n };\n}\n\nconst InterfereContext = createContext<InterfereContextValue | null>(null);\n\ninterface InterfereProviderProps extends PropsWithChildren {\n consent?: ConsentState;\n}\n\nexport function InterfereProvider({\n children,\n consent,\n}: InterfereProviderProps): ReactNode {\n useEffect(() => {\n syncConsent(consent);\n }, [consent]);\n\n const value: InterfereContextValue = {\n consent: sdkConsent,\n identity,\n session,\n };\n\n return <InterfereContext value={value}>{children}</InterfereContext>;\n}\n\nexport function useInterfere(): InterfereContextValue {\n const ctx = useContext(InterfereContext);\n if (!ctx) {\n throw new Error(\"useInterfere must be used within <InterfereProvider>\");\n }\n return ctx;\n}\n\nexport function useSession(): string | null {\n return useInterfere().session.getId();\n}\n"],"mappings":";;;;;;AA+BA,MAAM,mBAAmB,cAA4C,KAAK;AAM1E,SAAgB,kBAAkB,EAChC,UACA,SAAA,aACoC;AACpC,iBAAgB;AACd,cAAYA,UAAQ;IACnB,CAACA,UAAQ,CAAC;AAQb,QAAO,oBAAC,kBAAD;EAAkB,OANY;GAC1BC;GACT;GACA;GACD;EAEuC;EAA4B,CAAA;;AAGtE,SAAgB,eAAsC;CACpD,MAAM,MAAM,WAAW,iBAAiB;AACxC,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,uDAAuD;AAEzE,QAAO;;AAGT,SAAgB,aAA4B;AAC1C,QAAO,cAAc,CAAC,QAAQ,OAAO"}
@@ -1,17 +0,0 @@
1
- import { IngestTarget } from "../transport/http.mjs";
2
- import { IdentifyParams } from "@interfere/types/sdk/identify";
3
-
4
- //#region src/tracking/api.d.ts
5
- declare function bootstrap(sessionTarget: IngestTarget): void;
6
- declare const session: {
7
- getId(): string | null;
8
- getWindowId(): string | null;
9
- };
10
- declare const identity: {
11
- get(): IdentifyParams | null;
12
- set(params: IdentifyParams): void;
13
- clear(): void;
14
- };
15
- declare function teardown(): void;
16
- //#endregion
17
- export { bootstrap, identity, session, teardown };
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.d.mts","names":[],"sources":["../../src/tracking/api.ts"],"mappings":";;;;iBAuCgB,SAAA,CAAU,aAAA,EAAe,YAAA;AAAA,cAU5B,OAAA;EAQZ,KAAA;EAAA,WAAA;AAAA;AAAA,cAEY,QAAA;SACJ,cAAA;cAIK,cAAA;;;iBA4BE,QAAA,CAAA"}