@interfere/react 0.2.0-alpha.3 → 0.2.0-alpha.5
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/error-boundary.d.mts +2 -2
- package/dist/error-boundary.d.mts.map +1 -1
- package/dist/error-boundary.mjs +9 -7
- package/dist/error-boundary.mjs.map +1 -1
- package/dist/internal/client.d.mts +22 -3
- package/dist/internal/client.d.mts.map +1 -1
- package/dist/internal/client.mjs +77 -57
- package/dist/internal/client.mjs.map +1 -1
- package/dist/internal/consent.d.mts +15 -0
- package/dist/internal/consent.d.mts.map +1 -0
- package/dist/internal/consent.mjs +25 -0
- package/dist/internal/consent.mjs.map +1 -0
- package/dist/internal/plugin-runtime.d.mts +26 -0
- package/dist/internal/plugin-runtime.d.mts.map +1 -0
- package/dist/internal/plugin-runtime.mjs +85 -0
- package/dist/internal/plugin-runtime.mjs.map +1 -0
- package/dist/plugins/fingerprint.d.mts +6 -0
- package/dist/plugins/fingerprint.d.mts.map +1 -0
- package/dist/plugins/fingerprint.mjs +13 -0
- package/dist/plugins/fingerprint.mjs.map +1 -0
- package/dist/plugins/lib/loader.d.mts +2 -1
- package/dist/plugins/lib/loader.d.mts.map +1 -1
- package/dist/plugins/lib/loader.mjs +16 -20
- package/dist/plugins/lib/loader.mjs.map +1 -1
- package/dist/provider.d.mts +11 -4
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +7 -4
- package/dist/provider.mjs.map +1 -1
- package/dist/tracking/api.d.mts +1 -6
- package/dist/tracking/api.d.mts.map +1 -1
- package/dist/tracking/api.mjs +4 -16
- package/dist/tracking/api.mjs.map +1 -1
- package/dist/tracking/session.d.mts +0 -1
- package/dist/tracking/session.d.mts.map +1 -1
- package/dist/tracking/session.mjs +0 -19
- package/dist/tracking/session.mjs.map +1 -1
- package/dist/tracking/visitor.d.mts +2 -1
- package/dist/tracking/visitor.d.mts.map +1 -1
- package/dist/tracking/visitor.mjs +6 -1
- package/dist/tracking/visitor.mjs.map +1 -1
- package/dist/transport/http.d.mts +2 -1
- package/dist/transport/http.d.mts.map +1 -1
- package/dist/transport/http.mjs +1 -1
- package/dist/transport/http.mjs.map +1 -1
- package/package.json +16 -11
|
@@ -11,8 +11,8 @@ interface ErrorBoundaryState {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Catches render-phase React errors, reports them to the SDK, and renders a
|
|
14
|
-
* fallback.
|
|
15
|
-
*
|
|
14
|
+
* fallback. Requires the SDK to be bootstrapped via `init()` before React
|
|
15
|
+
* renders so `capture` has an active runtime.
|
|
16
16
|
*
|
|
17
17
|
* Class component required — React has no hook-based error boundary API.
|
|
18
18
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary.d.mts","names":[],"sources":["../src/error-boundary.tsx"],"mappings":";;;UASiB,kBAAA;EACf,QAAA,EAAU,SAAA;EACV,QAAA,GAAW,SAAA,KAAc,KAAA,EAAO,KAAA,EAAO,KAAA,iBAAsB,SAAA;EAC7D,OAAA,IAAW,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;AAAA;AAAA,UAGvB,kBAAA;EACR,KAAA,EAAO,KAAA;AAAA;;;;;;;;cAUI,aAAA,SAAsB,SAAA,CACjC,kBAAA,EACA,kBAAA;EAES,KAAA,EAAO,kBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,kBAAA;EAItC,iBAAA,CAAkB,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"error-boundary.d.mts","names":[],"sources":["../src/error-boundary.tsx"],"mappings":";;;UASiB,kBAAA;EACf,QAAA,EAAU,SAAA;EACV,QAAA,GAAW,SAAA,KAAc,KAAA,EAAO,KAAA,EAAO,KAAA,iBAAsB,SAAA;EAC7D,OAAA,IAAW,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;AAAA;AAAA,UAGvB,kBAAA;EACR,KAAA,EAAO,KAAA;AAAA;;;;;;;;cAUI,aAAA,SAAsB,SAAA,CACjC,kBAAA,EACA,kBAAA;EAES,KAAA,EAAO,kBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,kBAAA;EAItC,iBAAA,CAAkB,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;EAAA,iBAiB9B,KAAA;EAIR,MAAA,CAAA,GAAM,SAAA;AAAA"}
|
package/dist/error-boundary.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { seen } from "./internal/errors.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { getClient } from "./internal/client.mjs";
|
|
4
4
|
import { toExceptions } from "@interfere/types/sdk/errors";
|
|
5
5
|
import { Component } from "react";
|
|
6
6
|
//#region src/error-boundary.tsx
|
|
7
7
|
/**
|
|
8
8
|
* Catches render-phase React errors, reports them to the SDK, and renders a
|
|
9
|
-
* fallback.
|
|
10
|
-
*
|
|
9
|
+
* fallback. Requires the SDK to be bootstrapped via `init()` before React
|
|
10
|
+
* renders so `capture` has an active runtime.
|
|
11
11
|
*
|
|
12
12
|
* Class component required — React has no hook-based error boundary API.
|
|
13
13
|
*/
|
|
@@ -19,10 +19,12 @@ var ErrorBoundary = class extends Component {
|
|
|
19
19
|
componentDidCatch(error, info) {
|
|
20
20
|
if (seen.has(error)) return;
|
|
21
21
|
seen.add(error);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
try {
|
|
23
|
+
getClient().capture("error", { exceptions: toExceptions(error, {
|
|
24
|
+
type: "react",
|
|
25
|
+
handled: true
|
|
26
|
+
}) });
|
|
27
|
+
} catch {}
|
|
26
28
|
this.props.onError?.(error, info);
|
|
27
29
|
}
|
|
28
30
|
reset = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary.mjs","names":[],"sources":["../src/error-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { toExceptions } from \"@interfere/types/sdk/errors\";\n\nimport { Component, type ErrorInfo, type ReactNode } from \"react\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"error-boundary.mjs","names":[],"sources":["../src/error-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { toExceptions } from \"@interfere/types/sdk/errors\";\n\nimport { Component, type ErrorInfo, type ReactNode } from \"react\";\n\nimport { getClient } from \"./internal/client.js\";\nimport { seen } from \"./internal/errors.js\";\n\nexport interface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render-phase React errors, reports them to the SDK, and renders a\n * fallback. Requires the SDK to be bootstrapped via `init()` before React\n * renders so `capture` has an active runtime.\n *\n * Class component required — React has no hook-based error boundary API.\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n override state: ErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { error };\n }\n\n override componentDidCatch(error: Error, info: ErrorInfo) {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n\n try {\n getClient().capture(\"error\", {\n exceptions: toExceptions(error, { type: \"react\", handled: true }),\n });\n } catch {\n // SDK not initialized — error boundary still renders fallback\n }\n\n this.props.onError?.(error, info);\n }\n\n private readonly reset = () => {\n this.setState({ error: null });\n };\n\n override render() {\n const { error } = this.state;\n\n if (error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") {\n return fallback(error, this.reset);\n }\n return fallback ?? null;\n }\n\n return this.props.children;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA0BA,IAAa,gBAAb,cAAmC,UAGjC;CACA,QAAqC,EAAE,OAAO,MAAM;CAEpD,OAAO,yBAAyB,OAAkC;AAChE,SAAO,EAAE,OAAO;;CAGlB,kBAA2B,OAAc,MAAiB;AACxD,MAAI,KAAK,IAAI,MAAM,CACjB;AAEF,OAAK,IAAI,MAAM;AAEf,MAAI;AACF,cAAW,CAAC,QAAQ,SAAS,EAC3B,YAAY,aAAa,OAAO;IAAE,MAAM;IAAS,SAAS;IAAM,CAAC,EAClE,CAAC;UACI;AAIR,OAAK,MAAM,UAAU,OAAO,KAAK;;CAGnC,cAA+B;AAC7B,OAAK,SAAS,EAAE,OAAO,MAAM,CAAC;;CAGhC,SAAkB;EAChB,MAAM,EAAE,UAAU,KAAK;AAEvB,MAAI,OAAO;GACT,MAAM,EAAE,aAAa,KAAK;AAC1B,OAAI,OAAO,aAAa,WACtB,QAAO,SAAS,OAAO,KAAK,MAAM;AAEpC,UAAO,YAAY;;AAGrB,SAAO,KAAK,MAAM"}
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
import { PluginOverrides } from "../plugins/lib/loader.mjs";
|
|
2
|
+
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
2
3
|
import { EnvelopePayload, EventType } from "@interfere/types/sdk/envelope";
|
|
3
4
|
|
|
4
5
|
//#region src/internal/client.d.ts
|
|
5
6
|
interface ClientOptions {
|
|
7
|
+
consent?: ConsentState;
|
|
6
8
|
plugins?: PluginOverrides;
|
|
7
9
|
}
|
|
10
|
+
declare class Client {
|
|
11
|
+
private readonly metadata;
|
|
12
|
+
private readonly queue;
|
|
13
|
+
private readonly runtime;
|
|
14
|
+
constructor(opts: ClientOptions, buildId: string, releaseId: string | null);
|
|
15
|
+
capture<T extends EventType>(type: T, payload: EnvelopePayload<T>): void;
|
|
16
|
+
flush(): void;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
getConsent(): ConsentState | null;
|
|
19
|
+
setConsent(value?: ConsentState): void;
|
|
20
|
+
resetConsent(): void;
|
|
21
|
+
}
|
|
22
|
+
declare function getClient(): Client;
|
|
8
23
|
declare function init(opts?: ClientOptions): void;
|
|
9
|
-
declare function capture<T extends EventType>(type: T, payload: EnvelopePayload<T>): void;
|
|
10
|
-
declare function flush(): void;
|
|
11
24
|
declare function close(): void;
|
|
25
|
+
declare const consent: {
|
|
26
|
+
get(): ConsentState | null;
|
|
27
|
+
set(value?: ConsentState): void;
|
|
28
|
+
};
|
|
29
|
+
declare function syncConsent(consentState: ConsentState | undefined): void;
|
|
30
|
+
declare function flush(): void;
|
|
12
31
|
//#endregion
|
|
13
|
-
export { ClientOptions,
|
|
32
|
+
export { ClientOptions, close, consent, flush, getClient, init, syncConsent };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../../src/internal/client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../../src/internal/client.ts"],"mappings":";;;;;UAiBiB,aAAA;EACf,OAAA,GAAU,YAAA;EACV,OAAA,GAAU,eAAA;AAAA;AAAA,cAGN,MAAA;EAAA,iBACa,QAAA;EAAA,iBACA,KAAA;EAAA,iBACA,OAAA;cAEL,IAAA,EAAM,aAAA,EAAe,OAAA,UAAiB,SAAA;EAkClD,OAAA,WAAkB,SAAA,CAAA,CAAW,IAAA,EAAM,CAAA,EAAG,OAAA,EAAS,eAAA,CAAgB,CAAA;EAS/D,KAAA,CAAA;EAIA,OAAA,CAAA;EAMA,UAAA,CAAA,GAAc,YAAA;EAId,UAAA,CAAW,KAAA,GAAQ,YAAA;EAInB,YAAA,CAAA;AAAA;AAAA,iBAOc,SAAA,CAAA,GAAa,MAAA;AAAA,iBASb,IAAA,CAAK,IAAA,GAAM,aAAA;AAAA,iBAsBX,KAAA,CAAA;AAAA,cASH,OAAA;SACJ,YAAA;cAIK,YAAA;AAAA;AAAA,iBAKE,WAAA,CAAY,YAAA,EAAc,YAAA;AAAA,iBAa1B,KAAA,CAAA"}
|
package/dist/internal/client.mjs
CHANGED
|
@@ -1,80 +1,100 @@
|
|
|
1
|
-
import errorsPlugin from "../plugins/errors.mjs";
|
|
2
1
|
import { createLogger } from "../util/log.mjs";
|
|
3
|
-
import { loadPlugins } from "../plugins/lib/loader.mjs";
|
|
4
|
-
import { bootstrap, session, teardown } from "../tracking/api.mjs";
|
|
5
2
|
import { HttpTransport } from "../transport/http.mjs";
|
|
3
|
+
import { bootstrap, session, teardown } from "../tracking/api.mjs";
|
|
6
4
|
import { BatchQueue } from "../transport/queue.mjs";
|
|
7
5
|
import { resolveTargets } from "./config.mjs";
|
|
8
6
|
import { collectContext } from "./context.mjs";
|
|
9
7
|
import { buildEnvelope } from "./envelope.mjs";
|
|
8
|
+
import { PluginRuntime } from "./plugin-runtime.mjs";
|
|
10
9
|
import { registerServiceWorker } from "./sw.mjs";
|
|
11
10
|
import { inferRuntime, normalizeEnv } from "@interfere/types/sdk/runtime";
|
|
12
11
|
//#region src/internal/client.ts
|
|
13
12
|
const log = createLogger("client");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
var Client = class {
|
|
14
|
+
metadata;
|
|
15
|
+
queue;
|
|
16
|
+
runtime;
|
|
17
|
+
constructor(opts, buildId, releaseId) {
|
|
18
|
+
const targets = resolveTargets();
|
|
19
|
+
bootstrap(targets.session);
|
|
20
|
+
log.info("target: %s", targets.ingest.url);
|
|
21
|
+
this.metadata = {
|
|
22
|
+
context: collectContext(),
|
|
23
|
+
environment: normalizeEnv(typeof process !== "undefined" ? process.env.NODE_ENV : void 0),
|
|
24
|
+
runtime: inferRuntime(),
|
|
25
|
+
buildId,
|
|
26
|
+
releaseId
|
|
27
|
+
};
|
|
28
|
+
registerServiceWorker();
|
|
29
|
+
this.queue = new BatchQueue({ transport: new HttpTransport(targets.ingest) });
|
|
30
|
+
this.queue.start();
|
|
31
|
+
this.runtime = new PluginRuntime({
|
|
32
|
+
capture: (type, payload) => this.capture(type, payload),
|
|
33
|
+
getSessionId: () => session.getId() ?? ""
|
|
34
|
+
}, opts.plugins, opts.consent);
|
|
35
|
+
this.runtime.start();
|
|
36
|
+
}
|
|
37
|
+
capture(type, payload) {
|
|
38
|
+
const sessionId = session.getId();
|
|
39
|
+
if (!(sessionId && this.runtime.canCapture(type))) return;
|
|
40
|
+
this.queue.enqueue(buildEnvelope(type, payload, sessionId, this.metadata));
|
|
41
|
+
}
|
|
42
|
+
flush() {
|
|
43
|
+
this.queue.flush();
|
|
44
|
+
}
|
|
45
|
+
dispose() {
|
|
46
|
+
this.runtime.dispose();
|
|
47
|
+
teardown();
|
|
48
|
+
this.queue.dispose();
|
|
49
|
+
}
|
|
50
|
+
getConsent() {
|
|
51
|
+
return this.runtime.getConsent();
|
|
52
|
+
}
|
|
53
|
+
setConsent(value) {
|
|
54
|
+
this.runtime.setConsent(value);
|
|
55
|
+
}
|
|
56
|
+
resetConsent() {
|
|
57
|
+
this.runtime.resetConsent();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
let instance = null;
|
|
61
|
+
function getClient() {
|
|
62
|
+
if (!instance) throw new Error("Interfere SDK not initialized. Call init() from your instrumentation-client entrypoint.");
|
|
63
|
+
return instance;
|
|
64
|
+
}
|
|
18
65
|
function init(opts = {}) {
|
|
19
|
-
if (
|
|
66
|
+
if (instance) return;
|
|
20
67
|
const buildId = globalThis.__INTERFERE_BUILD_ID__;
|
|
21
68
|
const releaseId = globalThis.__INTERFERE_RELEASE_ID__;
|
|
22
69
|
if (!buildId) {
|
|
23
70
|
log.error("buildId not found — ensure withInterfere() is configured in next.config and instrumentation-client.ts exists in your project root.");
|
|
24
71
|
return;
|
|
25
72
|
}
|
|
26
|
-
|
|
27
|
-
bootstrap(targets.session, opts.plugins);
|
|
28
|
-
log.info("target: %s", targets.ingest.url);
|
|
29
|
-
metadata = {
|
|
30
|
-
context: collectContext(),
|
|
31
|
-
environment: normalizeEnv(typeof process !== "undefined" ? process.env.NODE_ENV : void 0),
|
|
32
|
-
runtime: inferRuntime(),
|
|
33
|
-
buildId,
|
|
34
|
-
releaseId: releaseId ?? null
|
|
35
|
-
};
|
|
36
|
-
registerServiceWorker();
|
|
37
|
-
queue = new BatchQueue({ transport: new HttpTransport(targets.ingest) });
|
|
38
|
-
queue.start();
|
|
39
|
-
const pluginCtx = {
|
|
40
|
-
capture: (type, payload) => capture(type, payload),
|
|
41
|
-
getSessionId: () => session.getId() ?? ""
|
|
42
|
-
};
|
|
43
|
-
const errorCleanup = errorsPlugin.setup(pluginCtx);
|
|
44
|
-
cleanups = errorCleanup ? [errorCleanup] : [];
|
|
45
|
-
state = "ready";
|
|
46
|
-
loadPlugins({
|
|
47
|
-
...opts.plugins,
|
|
48
|
-
errors: false
|
|
49
|
-
}, pluginCtx).then((c) => {
|
|
50
|
-
cleanups.push(...c);
|
|
51
|
-
}).catch(() => {
|
|
52
|
-
log.warn("non-critical plugin loading failed");
|
|
53
|
-
});
|
|
73
|
+
instance = new Client(opts, buildId, releaseId ?? null);
|
|
54
74
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
function close() {
|
|
76
|
+
if (!instance) return;
|
|
77
|
+
instance.dispose();
|
|
78
|
+
instance = null;
|
|
79
|
+
}
|
|
80
|
+
const consent = {
|
|
81
|
+
get() {
|
|
82
|
+
return instance?.getConsent() ?? null;
|
|
83
|
+
},
|
|
84
|
+
set(value) {
|
|
85
|
+
instance?.setConsent(value);
|
|
62
86
|
}
|
|
87
|
+
};
|
|
88
|
+
function syncConsent(consentState) {
|
|
89
|
+
if (!instance) return;
|
|
90
|
+
if (consentState) {
|
|
91
|
+
instance.setConsent(consentState);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
instance.resetConsent();
|
|
63
95
|
}
|
|
64
96
|
function flush() {
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
function close() {
|
|
68
|
-
if (state === "closed") return;
|
|
69
|
-
state = "closed";
|
|
70
|
-
for (const cleanup of cleanups) try {
|
|
71
|
-
cleanup();
|
|
72
|
-
} catch {}
|
|
73
|
-
cleanups = [];
|
|
74
|
-
teardown();
|
|
75
|
-
queue?.dispose();
|
|
76
|
-
queue = null;
|
|
77
|
-
metadata = null;
|
|
97
|
+
instance?.flush();
|
|
78
98
|
}
|
|
79
99
|
//#endregion
|
|
80
|
-
export {
|
|
100
|
+
export { close, consent, flush, getClient, init, syncConsent };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/internal/client.ts"],"sourcesContent":["import type { EnvelopePayload, EventType } from \"@interfere/types/sdk/envelope\";\nimport {
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/internal/client.ts"],"sourcesContent":["import type { EnvelopePayload, EventType } from \"@interfere/types/sdk/envelope\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\nimport { inferRuntime, normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nimport type { PluginOverrides } from \"../plugins/lib/loader.js\";\nimport { bootstrap, session, teardown } from \"../tracking/api.js\";\nimport { HttpTransport } from \"../transport/http.js\";\nimport { BatchQueue } from \"../transport/queue.js\";\nimport { createLogger } from \"../util/log.js\";\nimport { resolveTargets } from \"./config.js\";\nimport { collectContext } from \"./context.js\";\nimport { buildEnvelope, type EnvelopeMetadata } from \"./envelope.js\";\nimport { PluginRuntime } from \"./plugin-runtime.js\";\nimport { registerServiceWorker } from \"./sw.js\";\n\nconst log = createLogger(\"client\");\n\nexport interface ClientOptions {\n consent?: ConsentState;\n plugins?: PluginOverrides;\n}\n\nclass Client {\n private readonly metadata: EnvelopeMetadata;\n private readonly queue: BatchQueue;\n private readonly runtime: PluginRuntime;\n\n constructor(opts: ClientOptions, buildId: string, releaseId: string | null) {\n const targets = resolveTargets();\n bootstrap(targets.session);\n\n log.info(\"target: %s\", targets.ingest.url);\n\n this.metadata = {\n context: collectContext(),\n environment: normalizeEnv(\n typeof process !== \"undefined\" ? process.env.NODE_ENV : undefined\n ),\n runtime: inferRuntime(),\n buildId,\n releaseId,\n };\n\n registerServiceWorker();\n\n const transport = new HttpTransport(targets.ingest);\n this.queue = new BatchQueue({ transport });\n this.queue.start();\n\n this.runtime = new PluginRuntime(\n {\n capture: (type, payload) => this.capture(type, payload),\n getSessionId: () => session.getId() ?? \"\",\n },\n opts.plugins,\n opts.consent\n );\n\n this.runtime.start();\n }\n\n capture<T extends EventType>(type: T, payload: EnvelopePayload<T>): void {\n const sessionId = session.getId();\n if (!(sessionId && this.runtime.canCapture(type))) {\n return;\n }\n\n this.queue.enqueue(buildEnvelope(type, payload, sessionId, this.metadata));\n }\n\n flush(): void {\n this.queue.flush();\n }\n\n dispose(): void {\n this.runtime.dispose();\n teardown();\n this.queue.dispose();\n }\n\n getConsent(): ConsentState | null {\n return this.runtime.getConsent();\n }\n\n setConsent(value?: ConsentState): void {\n this.runtime.setConsent(value);\n }\n\n resetConsent(): void {\n this.runtime.resetConsent();\n }\n}\n\nlet instance: Client | null = null;\n\nexport function getClient(): Client {\n if (!instance) {\n throw new Error(\n \"Interfere SDK not initialized. Call init() from your instrumentation-client entrypoint.\"\n );\n }\n return instance;\n}\n\nexport function init(opts: ClientOptions = {}): void {\n if (instance) {\n return;\n }\n\n const buildId = (globalThis as Record<string, unknown>)\n .__INTERFERE_BUILD_ID__ as string | undefined;\n\n const releaseId = (globalThis as Record<string, unknown>)\n .__INTERFERE_RELEASE_ID__ as string | null | undefined;\n\n if (!buildId) {\n log.error(\n \"buildId not found — ensure withInterfere() is configured in \" +\n \"next.config and instrumentation-client.ts exists in your project root.\"\n );\n return;\n }\n\n instance = new Client(opts, buildId, releaseId ?? null);\n}\n\nexport function close(): void {\n if (!instance) {\n return;\n }\n\n instance.dispose();\n instance = null;\n}\n\nexport const consent = {\n get(): ConsentState | null {\n return instance?.getConsent() ?? null;\n },\n\n set(value?: ConsentState): void {\n instance?.setConsent(value);\n },\n};\n\nexport function syncConsent(consentState: ConsentState | undefined): void {\n if (!instance) {\n return;\n }\n\n if (consentState) {\n instance.setConsent(consentState);\n return;\n }\n\n instance.resetConsent();\n}\n\nexport function flush(): void {\n instance?.flush();\n}\n"],"mappings":";;;;;;;;;;;AAeA,MAAM,MAAM,aAAa,SAAS;AAOlC,IAAM,SAAN,MAAa;CACX;CACA;CACA;CAEA,YAAY,MAAqB,SAAiB,WAA0B;EAC1E,MAAM,UAAU,gBAAgB;AAChC,YAAU,QAAQ,QAAQ;AAE1B,MAAI,KAAK,cAAc,QAAQ,OAAO,IAAI;AAE1C,OAAK,WAAW;GACd,SAAS,gBAAgB;GACzB,aAAa,aACX,OAAO,YAAY,cAAc,QAAQ,IAAI,WAAW,KAAA,EACzD;GACD,SAAS,cAAc;GACvB;GACA;GACD;AAED,yBAAuB;AAGvB,OAAK,QAAQ,IAAI,WAAW,EAAE,WADZ,IAAI,cAAc,QAAQ,OAAO,EACV,CAAC;AAC1C,OAAK,MAAM,OAAO;AAElB,OAAK,UAAU,IAAI,cACjB;GACE,UAAU,MAAM,YAAY,KAAK,QAAQ,MAAM,QAAQ;GACvD,oBAAoB,QAAQ,OAAO,IAAI;GACxC,EACD,KAAK,SACL,KAAK,QACN;AAED,OAAK,QAAQ,OAAO;;CAGtB,QAA6B,MAAS,SAAmC;EACvE,MAAM,YAAY,QAAQ,OAAO;AACjC,MAAI,EAAE,aAAa,KAAK,QAAQ,WAAW,KAAK,EAC9C;AAGF,OAAK,MAAM,QAAQ,cAAc,MAAM,SAAS,WAAW,KAAK,SAAS,CAAC;;CAG5E,QAAc;AACZ,OAAK,MAAM,OAAO;;CAGpB,UAAgB;AACd,OAAK,QAAQ,SAAS;AACtB,YAAU;AACV,OAAK,MAAM,SAAS;;CAGtB,aAAkC;AAChC,SAAO,KAAK,QAAQ,YAAY;;CAGlC,WAAW,OAA4B;AACrC,OAAK,QAAQ,WAAW,MAAM;;CAGhC,eAAqB;AACnB,OAAK,QAAQ,cAAc;;;AAI/B,IAAI,WAA0B;AAE9B,SAAgB,YAAoB;AAClC,KAAI,CAAC,SACH,OAAM,IAAI,MACR,0FACD;AAEH,QAAO;;AAGT,SAAgB,KAAK,OAAsB,EAAE,EAAQ;AACnD,KAAI,SACF;CAGF,MAAM,UAAW,WACd;CAEH,MAAM,YAAa,WAChB;AAEH,KAAI,CAAC,SAAS;AACZ,MAAI,MACF,qIAED;AACD;;AAGF,YAAW,IAAI,OAAO,MAAM,SAAS,aAAa,KAAK;;AAGzD,SAAgB,QAAc;AAC5B,KAAI,CAAC,SACH;AAGF,UAAS,SAAS;AAClB,YAAW;;AAGb,MAAa,UAAU;CACrB,MAA2B;AACzB,SAAO,UAAU,YAAY,IAAI;;CAGnC,IAAI,OAA4B;AAC9B,YAAU,WAAW,MAAM;;CAE9B;AAED,SAAgB,YAAY,cAA8C;AACxE,KAAI,CAAC,SACH;AAGF,KAAI,cAAc;AAChB,WAAS,WAAW,aAAa;AACjC;;AAGF,UAAS,cAAc;;AAGzB,SAAgB,QAAc;AAC5B,WAAU,OAAO"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ConsentCategory, ConsentState, PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
2
|
+
import { EventType } from "@interfere/types/sdk/envelope";
|
|
3
|
+
|
|
4
|
+
//#region src/internal/consent.d.ts
|
|
5
|
+
declare const DEFAULT_CONSENT: {
|
|
6
|
+
readonly analytics: true;
|
|
7
|
+
readonly replay: true;
|
|
8
|
+
};
|
|
9
|
+
declare function getPluginConsentCategory(key: PluginKey): ConsentCategory;
|
|
10
|
+
declare function isConsentAllowed(category: ConsentCategory, consentState: ConsentState | null): boolean;
|
|
11
|
+
declare function shouldCaptureEvent(type: EventType, consentState: ConsentState | null): boolean;
|
|
12
|
+
declare function resolveGrantedConsent(consent?: ConsentState): ConsentState;
|
|
13
|
+
declare function hasConsentChanged(current: ConsentState | null, next: ConsentState | null): boolean;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { DEFAULT_CONSENT, getPluginConsentCategory, hasConsentChanged, isConsentAllowed, resolveGrantedConsent, shouldCaptureEvent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.d.mts","names":[],"sources":["../../src/internal/consent.ts"],"mappings":";;;;cAQa,eAAA;EAAA,SAGoB,SAAA;EAAA,SAAA,MAAA;AAAA;AAAA,iBAYjB,wBAAA,CAAyB,GAAA,EAAK,SAAA,GAAY,eAAA;AAAA,iBAI1C,gBAAA,CACd,QAAA,EAAU,eAAA,EACV,YAAA,EAAc,YAAA;AAAA,iBASA,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,YAAA,EAAc,YAAA;AAAA,iBAKA,qBAAA,CAAsB,OAAA,GAAU,YAAA,GAAe,YAAA;AAAA,iBAI/C,iBAAA,CACd,OAAA,EAAS,YAAA,SACT,IAAA,EAAM,YAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PLUGIN_MANIFEST } from "@interfere/types/sdk/plugins/manifest";
|
|
2
|
+
//#region src/internal/consent.ts
|
|
3
|
+
const DEFAULT_CONSENT = {
|
|
4
|
+
analytics: true,
|
|
5
|
+
replay: true
|
|
6
|
+
};
|
|
7
|
+
const PLUGIN_CONSENT_BY_KEY = Object.fromEntries(PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory]));
|
|
8
|
+
const EVENT_CONSENT_BY_TYPE = Object.fromEntries(PLUGIN_MANIFEST.flatMap((plugin) => plugin.events.map((event) => [event.name, plugin.consentCategory])));
|
|
9
|
+
function getPluginConsentCategory(key) {
|
|
10
|
+
return PLUGIN_CONSENT_BY_KEY[key];
|
|
11
|
+
}
|
|
12
|
+
function isConsentAllowed(category, consentState) {
|
|
13
|
+
return category === "necessary" || consentState === null || consentState[category] === true;
|
|
14
|
+
}
|
|
15
|
+
function shouldCaptureEvent(type, consentState) {
|
|
16
|
+
return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);
|
|
17
|
+
}
|
|
18
|
+
function resolveGrantedConsent(consent) {
|
|
19
|
+
return consent ?? { ...DEFAULT_CONSENT };
|
|
20
|
+
}
|
|
21
|
+
function hasConsentChanged(current, next) {
|
|
22
|
+
return current?.analytics !== next?.analytics || current?.replay !== next?.replay;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { DEFAULT_CONSENT, getPluginConsentCategory, hasConsentChanged, isConsentAllowed, resolveGrantedConsent, shouldCaptureEvent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.mjs","names":[],"sources":["../../src/internal/consent.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentCategory,\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nexport const DEFAULT_CONSENT = {\n analytics: true,\n replay: true,\n} as const satisfies ConsentState;\n\nconst PLUGIN_CONSENT_BY_KEY = Object.fromEntries(\n PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory])\n) as Record<PluginKey, ConsentCategory>;\n\nconst EVENT_CONSENT_BY_TYPE = Object.fromEntries(\n PLUGIN_MANIFEST.flatMap((plugin) =>\n plugin.events.map((event) => [event.name, plugin.consentCategory] as const)\n )\n) as Record<EventType, ConsentCategory>;\n\nexport function getPluginConsentCategory(key: PluginKey): ConsentCategory {\n return PLUGIN_CONSENT_BY_KEY[key];\n}\n\nexport function isConsentAllowed(\n category: ConsentCategory,\n consentState: ConsentState | null\n): boolean {\n return (\n category === \"necessary\" ||\n consentState === null ||\n consentState[category] === true\n );\n}\n\nexport function shouldCaptureEvent(\n type: EventType,\n consentState: ConsentState | null\n): boolean {\n return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);\n}\n\nexport function resolveGrantedConsent(consent?: ConsentState): ConsentState {\n return consent ?? { ...DEFAULT_CONSENT };\n}\n\nexport function hasConsentChanged(\n current: ConsentState | null,\n next: ConsentState | null\n): boolean {\n return (\n current?.analytics !== next?.analytics || current?.replay !== next?.replay\n );\n}\n"],"mappings":";;AAQA,MAAa,kBAAkB;CAC7B,WAAW;CACX,QAAQ;CACT;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,KAAK,WAAW,CAAC,OAAO,MAAM,OAAO,gBAAgB,CAAC,CACvE;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,SAAS,WACvB,OAAO,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,OAAO,gBAAgB,CAAU,CAC5E,CACF;AAED,SAAgB,yBAAyB,KAAiC;AACxE,QAAO,sBAAsB;;AAG/B,SAAgB,iBACd,UACA,cACS;AACT,QACE,aAAa,eACb,iBAAiB,QACjB,aAAa,cAAc;;AAI/B,SAAgB,mBACd,MACA,cACS;AACT,QAAO,iBAAiB,sBAAsB,OAAO,aAAa;;AAGpE,SAAgB,sBAAsB,SAAsC;AAC1E,QAAO,WAAW,EAAE,GAAG,iBAAiB;;AAG1C,SAAgB,kBACd,SACA,MACS;AACT,QACE,SAAS,cAAc,MAAM,aAAa,SAAS,WAAW,MAAM"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PluginContext } from "../plugins/lib/types.mjs";
|
|
2
|
+
import { PluginOverrides } from "../plugins/lib/loader.mjs";
|
|
3
|
+
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
4
|
+
import { EventType } from "@interfere/types/sdk/envelope";
|
|
5
|
+
|
|
6
|
+
//#region src/internal/plugin-runtime.d.ts
|
|
7
|
+
declare class PluginRuntime {
|
|
8
|
+
private readonly activeCleanups;
|
|
9
|
+
private readonly context;
|
|
10
|
+
private readonly features;
|
|
11
|
+
private consentState;
|
|
12
|
+
private syncVersion;
|
|
13
|
+
constructor(context: PluginContext, overrides: PluginOverrides | undefined, initialConsent: ConsentState | undefined);
|
|
14
|
+
getConsent(): ConsentState | null;
|
|
15
|
+
setConsent(nextConsent?: ConsentState): void;
|
|
16
|
+
resetConsent(): void;
|
|
17
|
+
canCapture(type: EventType): boolean;
|
|
18
|
+
start(): void;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
private shouldEnablePlugin;
|
|
21
|
+
private deactivate;
|
|
22
|
+
private activate;
|
|
23
|
+
private sync;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { PluginRuntime };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-runtime.d.mts","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"mappings":";;;;;;cAyBa,aAAA;EAAA,iBACM,cAAA;EAAA,iBACA,OAAA;EAAA,iBACA,QAAA;EAAA,QACT,YAAA;EAAA,QACA,WAAA;cAGN,OAAA,EAAS,aAAA,EACT,SAAA,EAAW,eAAA,cACX,cAAA,EAAgB,YAAA;EAOlB,UAAA,CAAA,GAAc,YAAA;EAId,UAAA,CAAW,WAAA,GAAc,YAAA;EAUzB,YAAA,CAAA;EASA,UAAA,CAAW,IAAA,EAAM,SAAA;EAIjB,KAAA,CAAA;EAWA,OAAA,CAAA;EAAA,QAMQ,kBAAA;EAAA,QAOA,UAAA;EAAA,QAeM,QAAA;EAAA,QAoBN,IAAA;AAAA"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createLogger } from "../util/log.mjs";
|
|
2
|
+
import errorsPlugin from "../plugins/errors.mjs";
|
|
3
|
+
import { loadPlugin, resolveFeatures } from "../plugins/lib/loader.mjs";
|
|
4
|
+
import { getPluginConsentCategory, hasConsentChanged, isConsentAllowed, resolveGrantedConsent, shouldCaptureEvent } from "./consent.mjs";
|
|
5
|
+
import { PLUGIN_MANIFEST } from "@interfere/types/sdk/plugins/manifest";
|
|
6
|
+
//#region src/internal/plugin-runtime.ts
|
|
7
|
+
const log = createLogger("plugin-runtime");
|
|
8
|
+
var PluginRuntime = class {
|
|
9
|
+
activeCleanups = /* @__PURE__ */ new Map();
|
|
10
|
+
context;
|
|
11
|
+
features;
|
|
12
|
+
consentState;
|
|
13
|
+
syncVersion = 0;
|
|
14
|
+
constructor(context, overrides, initialConsent) {
|
|
15
|
+
this.context = context;
|
|
16
|
+
this.features = resolveFeatures(overrides);
|
|
17
|
+
this.consentState = initialConsent ?? null;
|
|
18
|
+
}
|
|
19
|
+
getConsent() {
|
|
20
|
+
return this.consentState;
|
|
21
|
+
}
|
|
22
|
+
setConsent(nextConsent) {
|
|
23
|
+
const nextState = resolveGrantedConsent(nextConsent);
|
|
24
|
+
if (!hasConsentChanged(this.consentState, nextState)) return;
|
|
25
|
+
this.consentState = nextState;
|
|
26
|
+
this.sync();
|
|
27
|
+
}
|
|
28
|
+
resetConsent() {
|
|
29
|
+
if (!hasConsentChanged(this.consentState, null)) return;
|
|
30
|
+
this.consentState = null;
|
|
31
|
+
this.sync();
|
|
32
|
+
}
|
|
33
|
+
canCapture(type) {
|
|
34
|
+
return shouldCaptureEvent(type, this.consentState);
|
|
35
|
+
}
|
|
36
|
+
start() {
|
|
37
|
+
if (this.features.errors) {
|
|
38
|
+
const cleanup = errorsPlugin.setup(this.context);
|
|
39
|
+
if (cleanup) this.activeCleanups.set("errors", cleanup);
|
|
40
|
+
}
|
|
41
|
+
this.sync();
|
|
42
|
+
}
|
|
43
|
+
dispose() {
|
|
44
|
+
for (const key of this.activeCleanups.keys()) this.deactivate(key);
|
|
45
|
+
}
|
|
46
|
+
shouldEnablePlugin(key) {
|
|
47
|
+
return this.features[key] && isConsentAllowed(getPluginConsentCategory(key), this.consentState);
|
|
48
|
+
}
|
|
49
|
+
deactivate(key) {
|
|
50
|
+
const cleanup = this.activeCleanups.get(key);
|
|
51
|
+
if (!cleanup) return;
|
|
52
|
+
try {
|
|
53
|
+
cleanup();
|
|
54
|
+
} catch {
|
|
55
|
+
log.warn("cleanup failed for %s", key);
|
|
56
|
+
}
|
|
57
|
+
this.activeCleanups.delete(key);
|
|
58
|
+
}
|
|
59
|
+
async activate(key) {
|
|
60
|
+
if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) return;
|
|
61
|
+
const version = this.syncVersion;
|
|
62
|
+
const cleanup = await loadPlugin(key, this.context);
|
|
63
|
+
if (!cleanup) return;
|
|
64
|
+
if (version !== this.syncVersion || !this.shouldEnablePlugin(key)) {
|
|
65
|
+
cleanup();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.activeCleanups.set(key, cleanup);
|
|
69
|
+
}
|
|
70
|
+
sync() {
|
|
71
|
+
this.syncVersion += 1;
|
|
72
|
+
for (const plugin of PLUGIN_MANIFEST) {
|
|
73
|
+
if (plugin.name === "errors") continue;
|
|
74
|
+
if (this.shouldEnablePlugin(plugin.name)) {
|
|
75
|
+
this.activate(plugin.name).catch(() => {
|
|
76
|
+
log.warn("non-critical plugin loading failed");
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
this.deactivate(plugin.name);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
export { PluginRuntime };
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.mts","names":[],"sources":["../../src/plugins/fingerprint.ts"],"mappings":";;;cAGa,iBAAA,EAAmB,MAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
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 };
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -4,6 +4,7 @@ import { PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
|
4
4
|
//#region src/plugins/lib/loader.d.ts
|
|
5
5
|
type PluginOverrides = Partial<Record<PluginKey, boolean>>;
|
|
6
6
|
declare function resolveFeatures(overrides?: PluginOverrides): Record<PluginKey, boolean>;
|
|
7
|
+
declare function loadPlugin(key: PluginKey, context: PluginContext): Promise<PluginCleanup | null>;
|
|
7
8
|
declare function loadPlugins(overrides: PluginOverrides | undefined, context: PluginContext): Promise<PluginCleanup[]>;
|
|
8
9
|
//#endregion
|
|
9
|
-
export { PluginOverrides, loadPlugins, resolveFeatures };
|
|
10
|
+
export { PluginOverrides, loadPlugin, loadPlugins, resolveFeatures };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.mts","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"mappings":";;;;
|
|
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"}
|
|
@@ -4,6 +4,7 @@ import { PLUGIN_MANIFEST } from "@interfere/types/sdk/plugins/manifest";
|
|
|
4
4
|
const log = createLogger("plugins");
|
|
5
5
|
const LOADERS = {
|
|
6
6
|
errors: () => import("../errors.mjs"),
|
|
7
|
+
fingerprint: () => import("../fingerprint.mjs"),
|
|
7
8
|
pageEvents: () => import("../pages.mjs"),
|
|
8
9
|
rageClick: () => import("../rage-clicks.mjs"),
|
|
9
10
|
replay: () => import("../replay.mjs")
|
|
@@ -18,30 +19,25 @@ function resolveFeatures(overrides) {
|
|
|
18
19
|
function resolvePlugin(mod) {
|
|
19
20
|
return "default" in mod && typeof mod.default.setup === "function" ? mod.default : mod;
|
|
20
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
|
+
}
|
|
21
34
|
async function loadPlugins(overrides, context) {
|
|
22
35
|
const resolved = {
|
|
23
36
|
...DEFAULTS,
|
|
24
37
|
...overrides
|
|
25
38
|
};
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const loader = LOADERS[key];
|
|
29
|
-
if (!loader) return null;
|
|
30
|
-
try {
|
|
31
|
-
return await loader();
|
|
32
|
-
} catch {
|
|
33
|
-
log.error("failed to load plugin %s", key);
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}));
|
|
37
|
-
const cleanups = [];
|
|
38
|
-
for (const [i, mod] of modules.entries()) {
|
|
39
|
-
if (!mod) continue;
|
|
40
|
-
const cleanup = resolvePlugin(mod).setup(context);
|
|
41
|
-
if (typeof cleanup === "function") cleanups.push(cleanup);
|
|
42
|
-
log.debug("loaded %s", entries[i]?.[0]);
|
|
43
|
-
}
|
|
44
|
-
return cleanups;
|
|
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);
|
|
45
41
|
}
|
|
46
42
|
//#endregion
|
|
47
|
-
export { loadPlugins, resolveFeatures };
|
|
43
|
+
export { loadPlugin, loadPlugins, resolveFeatures };
|
|
@@ -1 +1 @@
|
|
|
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 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
|
|
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"}
|
package/dist/provider.d.mts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { PropsWithChildren, ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
3
3
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
4
4
|
|
|
5
5
|
//#region src/provider.d.ts
|
|
6
6
|
interface InterfereContextValue {
|
|
7
|
-
|
|
7
|
+
consent: {
|
|
8
|
+
get(): ConsentState | null;
|
|
9
|
+
set(state?: ConsentState): void;
|
|
10
|
+
};
|
|
8
11
|
identity: {
|
|
9
12
|
get(): IdentifyParams | null;
|
|
10
13
|
set(params: IdentifyParams): void;
|
|
@@ -14,9 +17,13 @@ interface InterfereContextValue {
|
|
|
14
17
|
getWindowId(): string | null;
|
|
15
18
|
};
|
|
16
19
|
}
|
|
20
|
+
interface InterfereProviderProps extends PropsWithChildren {
|
|
21
|
+
consent?: ConsentState;
|
|
22
|
+
}
|
|
17
23
|
declare function InterfereProvider({
|
|
18
|
-
children
|
|
19
|
-
|
|
24
|
+
children,
|
|
25
|
+
consent
|
|
26
|
+
}: InterfereProviderProps): ReactNode;
|
|
20
27
|
declare function useInterfere(): InterfereContextValue;
|
|
21
28
|
declare function useSession(): string | null;
|
|
22
29
|
//#endregion
|
package/dist/provider.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.mts","names":[],"sources":["../src/provider.tsx"],"mappings":";;;;;
|
|
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
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { identity, session } from "./tracking/api.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { createContext, useContext } from "react";
|
|
3
|
+
import { consent, syncConsent } from "./internal/client.mjs";
|
|
4
|
+
import { createContext, useContext, useEffect } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/provider.tsx
|
|
7
7
|
const InterfereContext = createContext(null);
|
|
8
|
-
function InterfereProvider({ children }) {
|
|
8
|
+
function InterfereProvider({ children, consent: consent$1 }) {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
syncConsent(consent$1);
|
|
11
|
+
}, [consent$1]);
|
|
9
12
|
return /* @__PURE__ */ jsx(InterfereContext, {
|
|
10
13
|
value: {
|
|
11
|
-
|
|
14
|
+
consent,
|
|
12
15
|
identity,
|
|
13
16
|
session
|
|
14
17
|
},
|
package/dist/provider.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.mjs","names":[],"sources":["../src/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport type {
|
|
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"}
|
package/dist/tracking/api.d.mts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import { PluginOverrides } from "../plugins/lib/loader.mjs";
|
|
2
1
|
import { IngestTarget } from "../transport/http.mjs";
|
|
3
2
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
4
3
|
|
|
5
4
|
//#region src/tracking/api.d.ts
|
|
6
|
-
|
|
7
|
-
* Bootstraps the tracking subsystem: creates the session manager,
|
|
8
|
-
* resolves the session target, and optionally starts Fingerprint Pro.
|
|
9
|
-
*/
|
|
10
|
-
declare function bootstrap(sessionTarget: IngestTarget, plugins?: PluginOverrides): void;
|
|
5
|
+
declare function bootstrap(sessionTarget: IngestTarget): void;
|
|
11
6
|
declare const session: {
|
|
12
7
|
getId(): string | null;
|
|
13
8
|
getWindowId(): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.mts","names":[],"sources":["../../src/tracking/api.ts"],"mappings":"
|
|
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"}
|
package/dist/tracking/api.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "../util/log.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { getVisitorId, whenVisitorReady } from "./visitor.mjs";
|
|
3
|
+
import { buildHeaders } from "../transport/http.mjs";
|
|
3
4
|
import { SessionManager } from "./session.mjs";
|
|
4
|
-
import { getVisitorId, initVisitor, whenVisitorReady } from "./visitor.mjs";
|
|
5
|
-
import { make } from "tctx/traceparent";
|
|
6
5
|
//#region src/tracking/api.ts
|
|
7
6
|
const log = createLogger("tracking");
|
|
8
7
|
let mgr = null;
|
|
@@ -10,15 +9,9 @@ let target = null;
|
|
|
10
9
|
let currentIdentity = null;
|
|
11
10
|
let identifiedSessionId = null;
|
|
12
11
|
function fire(url, method, headers, body) {
|
|
13
|
-
const h = Object.fromEntries(headers.entries());
|
|
14
|
-
h.traceparent = String(make());
|
|
15
|
-
const sid = mgr?.getSessionId();
|
|
16
|
-
if (sid) h["x-interfere-session"] = sid;
|
|
17
|
-
const vid = getVisitorId();
|
|
18
|
-
if (vid) h["x-interfere-visitor"] = vid;
|
|
19
12
|
fetch(url, {
|
|
20
13
|
method,
|
|
21
|
-
headers:
|
|
14
|
+
headers: buildHeaders(headers),
|
|
22
15
|
body: JSON.stringify(body),
|
|
23
16
|
keepalive: true
|
|
24
17
|
}).catch(() => {
|
|
@@ -34,13 +27,8 @@ async function onRotate(sessionId) {
|
|
|
34
27
|
visitorId
|
|
35
28
|
});
|
|
36
29
|
}
|
|
37
|
-
|
|
38
|
-
* Bootstraps the tracking subsystem: creates the session manager,
|
|
39
|
-
* resolves the session target, and optionally starts Fingerprint Pro.
|
|
40
|
-
*/
|
|
41
|
-
function bootstrap(sessionTarget, plugins) {
|
|
30
|
+
function bootstrap(sessionTarget) {
|
|
42
31
|
target = sessionTarget;
|
|
43
|
-
if (resolveFeatures(plugins).fingerprint) initVisitor();
|
|
44
32
|
mgr = new SessionManager((id) => {
|
|
45
33
|
onRotate(id).catch(() => {});
|
|
46
34
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/tracking/api.ts"],"sourcesContent":["import type { IdentifyParams } from \"@interfere/types/sdk/identify\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/tracking/api.ts"],"sourcesContent":["import type { IdentifyParams } from \"@interfere/types/sdk/identify\";\n\nimport { buildHeaders, type IngestTarget } from \"../transport/http.js\";\nimport { createLogger } from \"../util/log.js\";\nimport { SessionManager } from \"./session.js\";\nimport { getVisitorId, whenVisitorReady } from \"./visitor.js\";\n\nconst log = createLogger(\"tracking\");\n\nlet mgr: SessionManager | null = null;\nlet target: IngestTarget | null = null;\nlet currentIdentity: IdentifyParams | null = null;\nlet identifiedSessionId: string | null = null;\n\nfunction fire(\n url: string,\n method: \"POST\" | \"PUT\",\n headers: Headers,\n body: unknown\n): void {\n fetch(url, {\n method,\n headers: buildHeaders(headers),\n body: JSON.stringify(body),\n keepalive: true,\n }).catch(() => {\n log.warn(\"fire-and-forget %s failed\", method);\n });\n}\n\nasync function onRotate(sessionId: string): Promise<void> {\n if (!target) {\n return;\n }\n const visitorId = await whenVisitorReady();\n log.debug(\"POST session %s (visitor=%s)\", sessionId, visitorId ?? \"pending\");\n fire(target.url, \"POST\", target.headers, { sessionId, visitorId });\n}\n\nexport function bootstrap(sessionTarget: IngestTarget): void {\n target = sessionTarget;\n\n mgr = new SessionManager((id) => {\n onRotate(id).catch(() => {\n /* best-effort */\n });\n });\n}\n\nexport const session = {\n getId(): string | null {\n return mgr?.getSessionId() ?? null;\n },\n\n getWindowId(): string | null {\n return mgr?.getWindowId() ?? null;\n },\n};\n\nexport const identity = {\n get(): IdentifyParams | null {\n return currentIdentity;\n },\n\n set(params: IdentifyParams): void {\n if (!(mgr && target)) {\n return;\n }\n const sessionId = mgr.getSessionId();\n if (identifiedSessionId === sessionId) {\n log.debug(\"skipped, already identified for session %s\", sessionId);\n return;\n }\n\n currentIdentity = params;\n identifiedSessionId = sessionId;\n\n const visitorId = getVisitorId();\n log.info(\"PUT session %s → user %s\", sessionId, params.identifier);\n fire(target.url, \"PUT\", target.headers, {\n sessionId,\n visitorId,\n ...params,\n });\n },\n\n clear(): void {\n currentIdentity = null;\n identifiedSessionId = null;\n },\n};\n\nexport function teardown(): void {\n identity.clear();\n mgr = null;\n target = null;\n}\n"],"mappings":";;;;;AAOA,MAAM,MAAM,aAAa,WAAW;AAEpC,IAAI,MAA6B;AACjC,IAAI,SAA8B;AAClC,IAAI,kBAAyC;AAC7C,IAAI,sBAAqC;AAEzC,SAAS,KACP,KACA,QACA,SACA,MACM;AACN,OAAM,KAAK;EACT;EACA,SAAS,aAAa,QAAQ;EAC9B,MAAM,KAAK,UAAU,KAAK;EAC1B,WAAW;EACZ,CAAC,CAAC,YAAY;AACb,MAAI,KAAK,6BAA6B,OAAO;GAC7C;;AAGJ,eAAe,SAAS,WAAkC;AACxD,KAAI,CAAC,OACH;CAEF,MAAM,YAAY,MAAM,kBAAkB;AAC1C,KAAI,MAAM,gCAAgC,WAAW,aAAa,UAAU;AAC5E,MAAK,OAAO,KAAK,QAAQ,OAAO,SAAS;EAAE;EAAW;EAAW,CAAC;;AAGpE,SAAgB,UAAU,eAAmC;AAC3D,UAAS;AAET,OAAM,IAAI,gBAAgB,OAAO;AAC/B,WAAS,GAAG,CAAC,YAAY,GAEvB;GACF;;AAGJ,MAAa,UAAU;CACrB,QAAuB;AACrB,SAAO,KAAK,cAAc,IAAI;;CAGhC,cAA6B;AAC3B,SAAO,KAAK,aAAa,IAAI;;CAEhC;AAED,MAAa,WAAW;CACtB,MAA6B;AAC3B,SAAO;;CAGT,IAAI,QAA8B;AAChC,MAAI,EAAE,OAAO,QACX;EAEF,MAAM,YAAY,IAAI,cAAc;AACpC,MAAI,wBAAwB,WAAW;AACrC,OAAI,MAAM,8CAA8C,UAAU;AAClE;;AAGF,oBAAkB;AAClB,wBAAsB;EAEtB,MAAM,YAAY,cAAc;AAChC,MAAI,KAAK,4BAA4B,WAAW,OAAO,WAAW;AAClE,OAAK,OAAO,KAAK,OAAO,OAAO,SAAS;GACtC;GACA;GACA,GAAG;GACJ,CAAC;;CAGJ,QAAc;AACZ,oBAAkB;AAClB,wBAAsB;;CAEzB;AAED,SAAgB,WAAiB;AAC/B,UAAS,OAAO;AAChB,OAAM;AACN,UAAS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/tracking/session.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/tracking/session.ts"],"mappings":";KAoBY,QAAA,IAAY,SAAA;AAAA,cAEX,cAAA;EACX,SAAA;EACA,QAAA;EAAA,iBACiB,KAAA;EAAA,iBACA,OAAA;EAAA,iBACA,QAAA;cAEL,QAAA,GAAW,QAAA;EAOvB,YAAA,CAAA;EAQA,WAAA,CAAA;EAAA,QAgBQ,OAAA;EAAA,QAWA,MAAA;EAAA,QAQA,KAAA;EAAA,QAIA,SAAA;AAAA"}
|
|
@@ -5,7 +5,6 @@ const SESSION_ID_KEY = "interfere:session_id";
|
|
|
5
5
|
const LAST_ACTIVITY_KEY = "interfere:last_activity";
|
|
6
6
|
const WINDOW_ID_KEY = "interfere:window_id";
|
|
7
7
|
const SESSION_TIMEOUT_MS = 1800 * 1e3;
|
|
8
|
-
const POLL_INTERVAL_MS = 250;
|
|
9
8
|
function tryStorage(type) {
|
|
10
9
|
try {
|
|
11
10
|
const s = globalThis[type];
|
|
@@ -47,24 +46,6 @@ var SessionManager = class {
|
|
|
47
46
|
this.session?.setItem(WINDOW_ID_KEY, this.windowId);
|
|
48
47
|
return this.windowId;
|
|
49
48
|
}
|
|
50
|
-
whenReady(signal) {
|
|
51
|
-
const id = this.sessionId;
|
|
52
|
-
if (id && !this.isExpired()) return Promise.resolve(id);
|
|
53
|
-
return new Promise((resolve, reject) => {
|
|
54
|
-
const check = setInterval(() => {
|
|
55
|
-
if (signal?.aborted) {
|
|
56
|
-
clearInterval(check);
|
|
57
|
-
reject(signal.reason);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const current = this.getSessionId();
|
|
61
|
-
if (current) {
|
|
62
|
-
clearInterval(check);
|
|
63
|
-
resolve(current);
|
|
64
|
-
}
|
|
65
|
-
}, POLL_INTERVAL_MS);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
49
|
restore() {
|
|
69
50
|
const stored = this.local?.getItem(SESSION_ID_KEY);
|
|
70
51
|
if (stored && !this.isExpired()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/tracking/session.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\nimport { v7 as uuidv7 } from \"uuid\";\n\nconst SESSION_ID_KEY = \"interfere:session_id\";\nconst LAST_ACTIVITY_KEY = \"interfere:last_activity\";\nconst WINDOW_ID_KEY = \"interfere:window_id\";\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000
|
|
1
|
+
{"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/tracking/session.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\nimport { v7 as uuidv7 } from \"uuid\";\n\nconst SESSION_ID_KEY = \"interfere:session_id\";\nconst LAST_ACTIVITY_KEY = \"interfere:last_activity\";\nconst WINDOW_ID_KEY = \"interfere:window_id\";\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n\nfunction tryStorage(type: \"localStorage\" | \"sessionStorage\"): Storage | null {\n try {\n const s = globalThis[type];\n const key = \"__interfere_probe__\";\n s.setItem(key, \"1\");\n s.removeItem(key);\n return s;\n } catch {\n return null;\n }\n}\n\nexport type OnRotate = (sessionId: string) => void;\n\nexport class SessionManager {\n sessionId: string | null = null;\n windowId: string | null = null;\n private readonly local: Storage | null;\n private readonly session: Storage | null;\n private readonly onRotate: OnRotate | null = null;\n\n constructor(onRotate?: OnRotate) {\n this.local = tryStorage(\"localStorage\");\n this.session = tryStorage(\"sessionStorage\");\n this.onRotate = onRotate ?? null;\n this.restore();\n }\n\n getSessionId(): string {\n if (this.sessionId && !this.isExpired()) {\n this.touch();\n return this.sessionId;\n }\n return this.rotate();\n }\n\n getWindowId(): string {\n if (this.windowId) {\n return this.windowId;\n }\n\n const stored = this.session?.getItem(WINDOW_ID_KEY);\n if (stored) {\n this.windowId = stored;\n return stored;\n }\n\n this.windowId = `win_${nanoid(10)}`;\n this.session?.setItem(WINDOW_ID_KEY, this.windowId);\n return this.windowId;\n }\n\n private restore(): void {\n const stored = this.local?.getItem(SESSION_ID_KEY);\n\n if (stored && !this.isExpired()) {\n this.sessionId = stored;\n this.touch();\n } else {\n this.rotate();\n }\n }\n\n private rotate(): string {\n this.sessionId = uuidv7();\n this.local?.setItem(SESSION_ID_KEY, this.sessionId);\n this.touch();\n this.onRotate?.(this.sessionId);\n return this.sessionId;\n }\n\n private touch(): void {\n this.local?.setItem(LAST_ACTIVITY_KEY, String(Date.now()));\n }\n\n private isExpired(): boolean {\n const raw = this.local?.getItem(LAST_ACTIVITY_KEY);\n\n if (!raw) {\n return true;\n }\n return Date.now() - Number(raw) > SESSION_TIMEOUT_MS;\n }\n}\n"],"mappings":";;;AAGA,MAAM,iBAAiB;AACvB,MAAM,oBAAoB;AAC1B,MAAM,gBAAgB;AACtB,MAAM,qBAAqB,OAAU;AAErC,SAAS,WAAW,MAAyD;AAC3E,KAAI;EACF,MAAM,IAAI,WAAW;EACrB,MAAM,MAAM;AACZ,IAAE,QAAQ,KAAK,IAAI;AACnB,IAAE,WAAW,IAAI;AACjB,SAAO;SACD;AACN,SAAO;;;AAMX,IAAa,iBAAb,MAA4B;CAC1B,YAA2B;CAC3B,WAA0B;CAC1B;CACA;CACA,WAA6C;CAE7C,YAAY,UAAqB;AAC/B,OAAK,QAAQ,WAAW,eAAe;AACvC,OAAK,UAAU,WAAW,iBAAiB;AAC3C,OAAK,WAAW,YAAY;AAC5B,OAAK,SAAS;;CAGhB,eAAuB;AACrB,MAAI,KAAK,aAAa,CAAC,KAAK,WAAW,EAAE;AACvC,QAAK,OAAO;AACZ,UAAO,KAAK;;AAEd,SAAO,KAAK,QAAQ;;CAGtB,cAAsB;AACpB,MAAI,KAAK,SACP,QAAO,KAAK;EAGd,MAAM,SAAS,KAAK,SAAS,QAAQ,cAAc;AACnD,MAAI,QAAQ;AACV,QAAK,WAAW;AAChB,UAAO;;AAGT,OAAK,WAAW,OAAO,OAAO,GAAG;AACjC,OAAK,SAAS,QAAQ,eAAe,KAAK,SAAS;AACnD,SAAO,KAAK;;CAGd,UAAwB;EACtB,MAAM,SAAS,KAAK,OAAO,QAAQ,eAAe;AAElD,MAAI,UAAU,CAAC,KAAK,WAAW,EAAE;AAC/B,QAAK,YAAY;AACjB,QAAK,OAAO;QAEZ,MAAK,QAAQ;;CAIjB,SAAyB;AACvB,OAAK,YAAYA,IAAQ;AACzB,OAAK,OAAO,QAAQ,gBAAgB,KAAK,UAAU;AACnD,OAAK,OAAO;AACZ,OAAK,WAAW,KAAK,UAAU;AAC/B,SAAO,KAAK;;CAGd,QAAsB;AACpB,OAAK,OAAO,QAAQ,mBAAmB,OAAO,KAAK,KAAK,CAAC,CAAC;;CAG5D,YAA6B;EAC3B,MAAM,MAAM,KAAK,OAAO,QAAQ,kBAAkB;AAElD,MAAI,CAAC,IACH,QAAO;AAET,SAAO,KAAK,KAAK,GAAG,OAAO,IAAI,GAAG"}
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
declare function initVisitor(): void;
|
|
3
3
|
declare function getVisitorId(): string | null;
|
|
4
4
|
declare function whenVisitorReady(): Promise<string | null>;
|
|
5
|
+
declare function resetVisitor(): void;
|
|
5
6
|
//#endregion
|
|
6
|
-
export { getVisitorId, initVisitor, whenVisitorReady };
|
|
7
|
+
export { getVisitorId, initVisitor, resetVisitor, whenVisitorReady };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visitor.d.mts","names":[],"sources":["../../src/tracking/visitor.ts"],"mappings":";iBAcgB,WAAA,CAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"visitor.d.mts","names":[],"sources":["../../src/tracking/visitor.ts"],"mappings":";iBAcgB,WAAA,CAAA;AAAA,iBA+BA,YAAA,CAAA;AAAA,iBAIA,gBAAA,CAAA,GAAoB,OAAA;AAAA,iBAOpB,YAAA,CAAA"}
|
|
@@ -8,6 +8,7 @@ function resolveApiKey() {
|
|
|
8
8
|
return process.env.INTERFERE_FINGERPRINT_KEY ?? void 0;
|
|
9
9
|
}
|
|
10
10
|
function initVisitor() {
|
|
11
|
+
if (visitorId || pending) return;
|
|
11
12
|
const apiKey = resolveApiKey();
|
|
12
13
|
if (!apiKey) {
|
|
13
14
|
log.info("no INTERFERE_FINGERPRINT_KEY, skipping");
|
|
@@ -31,5 +32,9 @@ function whenVisitorReady() {
|
|
|
31
32
|
if (visitorId) return Promise.resolve(visitorId);
|
|
32
33
|
return pending ?? Promise.resolve(null);
|
|
33
34
|
}
|
|
35
|
+
function resetVisitor() {
|
|
36
|
+
visitorId = null;
|
|
37
|
+
pending = null;
|
|
38
|
+
}
|
|
34
39
|
//#endregion
|
|
35
|
-
export { getVisitorId, initVisitor, whenVisitorReady };
|
|
40
|
+
export { getVisitorId, initVisitor, resetVisitor, whenVisitorReady };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visitor.mjs","names":[],"sources":["../../src/tracking/visitor.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"visitor\");\n\nlet visitorId: string | null = null;\nlet pending: Promise<string | null> | null = null;\n\nfunction resolveApiKey(): string | undefined {\n if (typeof process === \"undefined\") {\n return undefined;\n }\n return process.env.INTERFERE_FINGERPRINT_KEY ?? undefined;\n}\n\nexport function initVisitor(): void {\n const apiKey = resolveApiKey();\n if (!apiKey) {\n log.info(\"no INTERFERE_FINGERPRINT_KEY, skipping\");\n return;\n }\n\n pending = (async () => {\n try {\n const FingerprintJS = await import(\"@fingerprintjs/fingerprintjs-pro\");\n\n const fp = await FingerprintJS.load({ apiKey });\n const result = await fp.get();\n\n visitorId = result.visitorId;\n\n log.debug(\"resolved %s\", visitorId);\n\n return visitorId;\n } catch {\n log.error(\"fingerprint failed\");\n\n return null;\n }\n })();\n}\n\nexport function getVisitorId(): string | null {\n return visitorId;\n}\n\nexport function whenVisitorReady(): Promise<string | null> {\n if (visitorId) {\n return Promise.resolve(visitorId);\n }\n return pending ?? Promise.resolve(null);\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,UAAU;AAEnC,IAAI,YAA2B;AAC/B,IAAI,UAAyC;AAE7C,SAAS,gBAAoC;AAC3C,KAAI,OAAO,YAAY,YACrB;AAEF,QAAO,QAAQ,IAAI,6BAA6B,KAAA;;AAGlD,SAAgB,cAAoB;
|
|
1
|
+
{"version":3,"file":"visitor.mjs","names":[],"sources":["../../src/tracking/visitor.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"visitor\");\n\nlet visitorId: string | null = null;\nlet pending: Promise<string | null> | null = null;\n\nfunction resolveApiKey(): string | undefined {\n if (typeof process === \"undefined\") {\n return undefined;\n }\n return process.env.INTERFERE_FINGERPRINT_KEY ?? undefined;\n}\n\nexport function initVisitor(): void {\n if (visitorId || pending) {\n return;\n }\n\n const apiKey = resolveApiKey();\n if (!apiKey) {\n log.info(\"no INTERFERE_FINGERPRINT_KEY, skipping\");\n return;\n }\n\n pending = (async () => {\n try {\n const FingerprintJS = await import(\"@fingerprintjs/fingerprintjs-pro\");\n\n const fp = await FingerprintJS.load({ apiKey });\n const result = await fp.get();\n\n visitorId = result.visitorId;\n\n log.debug(\"resolved %s\", visitorId);\n\n return visitorId;\n } catch {\n log.error(\"fingerprint failed\");\n\n return null;\n }\n })();\n}\n\nexport function getVisitorId(): string | null {\n return visitorId;\n}\n\nexport function whenVisitorReady(): Promise<string | null> {\n if (visitorId) {\n return Promise.resolve(visitorId);\n }\n return pending ?? Promise.resolve(null);\n}\n\nexport function resetVisitor(): void {\n visitorId = null;\n pending = null;\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,UAAU;AAEnC,IAAI,YAA2B;AAC/B,IAAI,UAAyC;AAE7C,SAAS,gBAAoC;AAC3C,KAAI,OAAO,YAAY,YACrB;AAEF,QAAO,QAAQ,IAAI,6BAA6B,KAAA;;AAGlD,SAAgB,cAAoB;AAClC,KAAI,aAAa,QACf;CAGF,MAAM,SAAS,eAAe;AAC9B,KAAI,CAAC,QAAQ;AACX,MAAI,KAAK,yCAAyC;AAClD;;AAGF,YAAW,YAAY;AACrB,MAAI;AAMF,gBAFe,OADJ,OAFW,MAAM,OAAO,qCAEJ,KAAK,EAAE,QAAQ,CAAC,EACvB,KAAK,EAEV;AAEnB,OAAI,MAAM,eAAe,UAAU;AAEnC,UAAO;UACD;AACN,OAAI,MAAM,qBAAqB;AAE/B,UAAO;;KAEP;;AAGN,SAAgB,eAA8B;AAC5C,QAAO;;AAGT,SAAgB,mBAA2C;AACzD,KAAI,UACF,QAAO,QAAQ,QAAQ,UAAU;AAEnC,QAAO,WAAW,QAAQ,QAAQ,KAAK;;AAGzC,SAAgB,eAAqB;AACnC,aAAY;AACZ,WAAU"}
|
|
@@ -5,6 +5,7 @@ interface IngestTarget {
|
|
|
5
5
|
headers: Headers;
|
|
6
6
|
url: string;
|
|
7
7
|
}
|
|
8
|
+
declare function buildHeaders(base: Headers): Record<string, string>;
|
|
8
9
|
declare class HttpTransport {
|
|
9
10
|
private readonly target;
|
|
10
11
|
private pendingKeepalive;
|
|
@@ -12,4 +13,4 @@ declare class HttpTransport {
|
|
|
12
13
|
send(envelopes: Envelope[]): Promise<void>;
|
|
13
14
|
}
|
|
14
15
|
//#endregion
|
|
15
|
-
export { HttpTransport, IngestTarget };
|
|
16
|
+
export { HttpTransport, IngestTarget, buildHeaders };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.mts","names":[],"sources":["../../src/transport/http.ts"],"mappings":";;;UAUiB,YAAA;EACf,OAAA,EAAS,OAAA;EACT,GAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"http.d.mts","names":[],"sources":["../../src/transport/http.ts"],"mappings":";;;UAUiB,YAAA;EACf,OAAA,EAAS,OAAA;EACT,GAAA;AAAA;AAAA,iBAGc,YAAA,CAAa,IAAA,EAAM,OAAA,GAAU,MAAA;AAAA,cA4BhC,aAAA;EAAA,iBACM,MAAA;EAAA,QACT,gBAAA;cAEI,MAAA,EAAQ,YAAA;EAId,IAAA,CAAK,SAAA,EAAW,QAAA,KAAa,OAAA;AAAA"}
|
package/dist/transport/http.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/transport/http.ts"],"sourcesContent":["import type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport { make } from \"tctx/traceparent\";\n\nimport { session } from \"../tracking/api.js\";\nimport { getVisitorId } from \"../tracking/visitor.js\";\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"http\");\n\nexport interface IngestTarget {\n headers: Headers;\n url: string;\n}\n\
|
|
1
|
+
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/transport/http.ts"],"sourcesContent":["import type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport { make } from \"tctx/traceparent\";\n\nimport { session } from \"../tracking/api.js\";\nimport { getVisitorId } from \"../tracking/visitor.js\";\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"http\");\n\nexport interface IngestTarget {\n headers: Headers;\n url: string;\n}\n\nexport function buildHeaders(base: Headers): Record<string, string> {\n const h: Record<string, string> = Object.fromEntries(base.entries());\n h.traceparent = String(make());\n\n const sessionId = session.getId();\n if (sessionId) {\n h[\"x-interfere-session\"] = sessionId;\n }\n\n const visitorId = getVisitorId();\n if (visitorId) {\n h[\"x-interfere-visitor\"] = visitorId;\n }\n\n return h;\n}\n\nfunction hasServiceWorker(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n \"serviceWorker\" in navigator &&\n navigator.serviceWorker.controller !== null\n );\n}\n\nconst KEEPALIVE_BUDGET_BYTES = 61_440;\nconst MAX_CONCURRENT_KEEPALIVE = 15;\n\nexport class HttpTransport {\n private readonly target: IngestTarget;\n private pendingKeepalive = 0;\n\n constructor(target: IngestTarget) {\n this.target = target;\n }\n\n async send(envelopes: Envelope[]): Promise<void> {\n const body = JSON.stringify(envelopes);\n const headers = buildHeaders(this.target.headers);\n\n if (hasServiceWorker()) {\n log.debug(\"POST %d envelopes via SW\", envelopes.length);\n await fetch(this.target.url, {\n method: \"POST\",\n headers,\n body,\n });\n return;\n }\n\n const bytes = new TextEncoder().encode(body).byteLength;\n const useKeepalive =\n bytes < KEEPALIVE_BUDGET_BYTES &&\n this.pendingKeepalive < MAX_CONCURRENT_KEEPALIVE;\n\n if (useKeepalive) {\n this.pendingKeepalive++;\n }\n\n log.debug(\n \"POST %d envelopes direct (%d bytes, keepalive=%s)\",\n envelopes.length,\n bytes,\n useKeepalive\n );\n\n try {\n await fetch(this.target.url, {\n method: \"POST\",\n headers,\n body,\n keepalive: useKeepalive,\n });\n } finally {\n if (useKeepalive) {\n this.pendingKeepalive--;\n }\n }\n }\n}\n"],"mappings":";;;;;AAQA,MAAM,MAAM,aAAa,OAAO;AAOhC,SAAgB,aAAa,MAAuC;CAClE,MAAM,IAA4B,OAAO,YAAY,KAAK,SAAS,CAAC;AACpE,GAAE,cAAc,OAAO,MAAM,CAAC;CAE9B,MAAM,YAAY,QAAQ,OAAO;AACjC,KAAI,UACF,GAAE,yBAAyB;CAG7B,MAAM,YAAY,cAAc;AAChC,KAAI,UACF,GAAE,yBAAyB;AAG7B,QAAO;;AAGT,SAAS,mBAA4B;AACnC,QACE,OAAO,cAAc,eACrB,mBAAmB,aACnB,UAAU,cAAc,eAAe;;AAI3C,MAAM,yBAAyB;AAC/B,MAAM,2BAA2B;AAEjC,IAAa,gBAAb,MAA2B;CACzB;CACA,mBAA2B;CAE3B,YAAY,QAAsB;AAChC,OAAK,SAAS;;CAGhB,MAAM,KAAK,WAAsC;EAC/C,MAAM,OAAO,KAAK,UAAU,UAAU;EACtC,MAAM,UAAU,aAAa,KAAK,OAAO,QAAQ;AAEjD,MAAI,kBAAkB,EAAE;AACtB,OAAI,MAAM,4BAA4B,UAAU,OAAO;AACvD,SAAM,MAAM,KAAK,OAAO,KAAK;IAC3B,QAAQ;IACR;IACA;IACD,CAAC;AACF;;EAGF,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,KAAK,CAAC;EAC7C,MAAM,eACJ,QAAQ,0BACR,KAAK,mBAAmB;AAE1B,MAAI,aACF,MAAK;AAGP,MAAI,MACF,qDACA,UAAU,QACV,OACA,aACD;AAED,MAAI;AACF,SAAM,MAAM,KAAK,OAAO,KAAK;IAC3B,QAAQ;IACR;IACA;IACA,WAAW;IACZ,CAAC;YACM;AACR,OAAI,aACF,MAAK"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interfere/react",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Client-side React SDK for Interfere. Error tracking, session replay, and analytics.",
|
|
6
6
|
"keywords": [
|
|
@@ -18,22 +18,27 @@
|
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
20
|
"url": "git+https://github.com/interfere-inc/interfere.git",
|
|
21
|
-
"directory": "src/packages/public/react
|
|
21
|
+
"directory": "src/packages/public/react"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist"
|
|
25
25
|
],
|
|
26
26
|
"type": "module",
|
|
27
27
|
"exports": {
|
|
28
|
-
"./*": {
|
|
29
|
-
"@source": "./src/*.ts",
|
|
30
|
-
"types": "./dist/*.d.mts",
|
|
31
|
-
"default": "./dist/*.mjs"
|
|
32
|
-
},
|
|
33
28
|
"./provider": {
|
|
34
29
|
"@source": "./src/provider.tsx",
|
|
35
30
|
"types": "./dist/provider.d.mts",
|
|
36
31
|
"default": "./dist/provider.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./error-boundary": {
|
|
34
|
+
"@source": "./src/error-boundary.tsx",
|
|
35
|
+
"types": "./dist/error-boundary.d.mts",
|
|
36
|
+
"default": "./dist/error-boundary.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./internal/client": {
|
|
39
|
+
"@source": "./src/internal/client.ts",
|
|
40
|
+
"types": "./dist/internal/client.d.mts",
|
|
41
|
+
"default": "./dist/internal/client.mjs"
|
|
37
42
|
}
|
|
38
43
|
},
|
|
39
44
|
"sideEffects": false,
|
|
@@ -50,8 +55,8 @@
|
|
|
50
55
|
},
|
|
51
56
|
"dependencies": {
|
|
52
57
|
"@fingerprintjs/fingerprintjs-pro": "^3.12.8",
|
|
53
|
-
"@interfere/constants": "0.2.0-alpha.
|
|
54
|
-
"@interfere/types": "0.2.0-alpha.
|
|
58
|
+
"@interfere/constants": "0.2.0-alpha.4",
|
|
59
|
+
"@interfere/types": "0.2.0-alpha.5",
|
|
55
60
|
"@ua-parser-js/pro-enterprise": "^2.0.6",
|
|
56
61
|
"nanoid": "^5.1.6",
|
|
57
62
|
"rrweb": "2.0.0-alpha.4",
|
|
@@ -63,8 +68,8 @@
|
|
|
63
68
|
"react-dom": ">=19"
|
|
64
69
|
},
|
|
65
70
|
"devDependencies": {
|
|
66
|
-
"@interfere/typescript-config": "1.1.0-alpha.
|
|
67
|
-
"@interfere/vitest-config": "1.1.0-alpha.
|
|
71
|
+
"@interfere/typescript-config": "1.1.0-alpha.6",
|
|
72
|
+
"@interfere/vitest-config": "1.1.0-alpha.6",
|
|
68
73
|
"@rrweb/types": "2.0.0-alpha.20",
|
|
69
74
|
"@testing-library/react": "^16.3.2",
|
|
70
75
|
"@types/node": "^24.12.0",
|