@genn-inc/cluebase-frontend-sdk 0.0.6 → 0.0.9
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/_generated/correlation-attribute-keys.d.ts +3 -0
- package/dist/_generated/correlation-attribute-keys.js +35 -0
- package/dist/_generated/diagnostic-codes.d.ts +4 -0
- package/dist/_generated/diagnostic-codes.js +48 -0
- package/dist/_generated/drop-reasons.d.ts +2 -0
- package/dist/_generated/drop-reasons.js +25 -0
- package/dist/_generated/ingest-routes.d.ts +5 -0
- package/dist/_generated/ingest-routes.js +12 -0
- package/dist/context/trace.js +2 -4
- package/dist/exporter/batch.d.ts +23 -26
- package/dist/exporter/batch.js +254 -289
- package/dist/exporter/browser-outbox-records.d.ts +7 -24
- package/dist/exporter/browser-outbox-records.js +6 -94
- package/dist/exporter/browser-outbox.d.ts +23 -28
- package/dist/exporter/browser-outbox.js +99 -211
- package/dist/exporter/client.d.ts +24 -34
- package/dist/exporter/client.js +107 -318
- package/dist/exporter/delivery-pipeline.d.ts +72 -0
- package/dist/exporter/delivery-pipeline.js +199 -0
- package/dist/exporter/delivery-response-contract.d.ts +5 -81
- package/dist/exporter/delivery-response-contract.js +4 -166
- package/dist/exporter/delivery-response.d.ts +8 -4
- package/dist/exporter/delivery-response.js +62 -133
- package/dist/exporter/diagnostics.d.ts +2 -1
- package/dist/exporter/diagnostics.js +1 -25
- package/dist/exporter/event-id.d.ts +1 -0
- package/dist/exporter/event-id.js +3 -0
- package/dist/exporter/observation-source-event-exporter.d.ts +1 -1
- package/dist/exporter/observation-source-event-exporter.js +2 -2
- package/dist/exporter/parameter-encryption.d.ts +10 -0
- package/dist/exporter/parameter-encryption.js +219 -0
- package/dist/exporter/parameter-snapshot.js +25 -3
- package/dist/exporter/sdk-core-delivery.d.ts +226 -0
- package/dist/exporter/sdk-core-delivery.js +97 -0
- package/dist/exporter/sdk-core-wasm.d.ts +24 -1
- package/dist/exporter/sdk-core-wasm.js +31 -3
- package/dist/exporter/source-event.d.ts +2 -0
- package/dist/exporter/source-event.js +17 -0
- package/dist/exporter/span-to-source-signal.d.ts +1 -1
- package/dist/exporter/span-to-source-signal.js +20 -140
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -3
- package/dist/instrumentation/network.d.ts +1 -1
- package/dist/instrumentation/network.js +17 -0
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/config.js +16 -3
- package/dist/provider/constants.js +0 -1
- package/dist/provider/contracts.d.ts +2 -0
- package/dist/provider/contracts.js +2 -0
- package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
- package/dist/provider/lifecycle-event-dedup.js +31 -0
- package/dist/provider/sdk-encryption-store.d.ts +7 -0
- package/dist/provider/sdk-encryption-store.js +15 -0
- package/dist/provider/sdk-event-pipeline.d.ts +1 -3
- package/dist/provider/sdk-event-pipeline.js +0 -15
- package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
- package/dist/provider/sdk-otel-pipeline.js +2 -11
- package/dist/provider/sdk-runtime-surfaces.d.ts +3 -2
- package/dist/provider/sdk-runtime-surfaces.js +7 -6
- package/dist/provider/sdk.js +88 -54
- package/dist/provider/types.d.ts +14 -19
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +18 -9
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +170 -29
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +10 -9
- package/package.json +1 -1
- package/dist/provider/usage-governor.d.ts +0 -7
- package/dist/provider/usage-governor.js +0 -114
package/dist/index.js
CHANGED
|
@@ -97,14 +97,16 @@ const cluebase = {
|
|
|
97
97
|
internalSdk.setOrganization(groupKey, traits);
|
|
98
98
|
});
|
|
99
99
|
},
|
|
100
|
+
link(input) {
|
|
101
|
+
safeCall("link", () => {
|
|
102
|
+
internalSdk.link(input);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
100
105
|
linkExternalAccount(namespace, externalId) {
|
|
101
106
|
safeCall("linkExternalAccount", () => {
|
|
102
107
|
internalSdk.linkExternalAccount(namespace, externalId);
|
|
103
108
|
});
|
|
104
109
|
},
|
|
105
|
-
logout() {
|
|
106
|
-
safeCall("logout", () => internalSdk.logout());
|
|
107
|
-
},
|
|
108
110
|
reset() {
|
|
109
111
|
safeCall("reset", () => internalSdk.reset());
|
|
110
112
|
},
|
|
@@ -9,7 +9,7 @@ export declare const createCluebaseNetworkPropagator: () => TextMapPropagator;
|
|
|
9
9
|
export declare const startNetworkCapture: (context: CaptureModuleContext, options?: {
|
|
10
10
|
tracerProvider?: TracerProvider;
|
|
11
11
|
deniedKeys?: readonly string[];
|
|
12
|
-
ignoreUrlPrefixes?: string[];
|
|
12
|
+
ignoreUrlPrefixes?: readonly string[];
|
|
13
13
|
tracePropagationOrigins?: string[];
|
|
14
14
|
createRequestTraceContext?: (input?: RequestTraceContextInput) => RequestTraceContext;
|
|
15
15
|
}) => CaptureStopHandle;
|
|
@@ -497,6 +497,23 @@ export const startNetworkCapture = (context, options = {}) => {
|
|
|
497
497
|
flush: async () => {
|
|
498
498
|
await waitForFetchSpanEnd();
|
|
499
499
|
},
|
|
500
|
+
refreshIgnoreUrlPrefixes: () => {
|
|
501
|
+
if (activeCapture !== capture)
|
|
502
|
+
return;
|
|
503
|
+
const ignoreUrls = urlPrefixPatterns(capture.ignoreUrlPrefixes);
|
|
504
|
+
if (fetchInstrumentation) {
|
|
505
|
+
fetchInstrumentation.setConfig({
|
|
506
|
+
...fetchInstrumentation.getConfig(),
|
|
507
|
+
ignoreUrls,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (xhrInstrumentation) {
|
|
511
|
+
xhrInstrumentation.setConfig({
|
|
512
|
+
...xhrInstrumentation.getConfig(),
|
|
513
|
+
ignoreUrls,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
},
|
|
500
517
|
stop: () => {
|
|
501
518
|
if (activeCapture !== capture)
|
|
502
519
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION: "0.0.
|
|
1
|
+
export declare const SDK_VERSION: "0.0.9";
|
package/dist/provider/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CANONICAL_BROWSER_INGEST_PATH, CLUEBASE_API_V1_PREFIX, } from "../_generated/ingest-routes.js";
|
|
1
2
|
import { ALWAYS_DENIED_KEYS, DEFAULT_API_BASE_URL, DEFAULT_CONFIG, DEFAULT_FRONTEND_SOURCE_IDENTIFIER, MAX_DIAGNOSTIC_TTL_MS, } from "./constants.js";
|
|
2
3
|
import { SDK_COLLECTION_MODE } from "./contracts.js";
|
|
3
4
|
const normalizeKeyList = (keys) => {
|
|
@@ -74,6 +75,12 @@ export const parseSendableUrl = (value) => {
|
|
|
74
75
|
return null;
|
|
75
76
|
}
|
|
76
77
|
};
|
|
78
|
+
const isExtendableIngestPath = (pathname) => {
|
|
79
|
+
const path = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
80
|
+
return (path === "" ||
|
|
81
|
+
path.endsWith(CLUEBASE_API_V1_PREFIX) ||
|
|
82
|
+
path.endsWith(CANONICAL_BROWSER_INGEST_PATH));
|
|
83
|
+
};
|
|
77
84
|
const normalizeConfiguredEndpoint = (value) => parseSendableUrl(value)?.toString() ?? "";
|
|
78
85
|
const normalizeTracePropagationOrigin = (value) => {
|
|
79
86
|
const trimmed = value.trim();
|
|
@@ -101,7 +108,6 @@ export const resolveConfig = (options) => {
|
|
|
101
108
|
return {
|
|
102
109
|
endpoint: originalEndpoint,
|
|
103
110
|
originalEndpoint,
|
|
104
|
-
localSetupCheckEndpoint: null,
|
|
105
111
|
projectKey: options.projectKey?.trim() ?? "",
|
|
106
112
|
producerId: DEFAULT_FRONTEND_SOURCE_IDENTIFIER,
|
|
107
113
|
autocapture: options.autocapture ?? DEFAULT_CONFIG.autocapture,
|
|
@@ -138,8 +144,15 @@ export const resolveConfig = (options) => {
|
|
|
138
144
|
};
|
|
139
145
|
export const validateConfig = (config) => {
|
|
140
146
|
const issues = [];
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
const endpoint = parseSendableUrl(config.endpoint);
|
|
148
|
+
if (!endpoint) {
|
|
149
|
+
issues.push("endpoint must be an absolute http(s) url with a host, such as https://api.cluebase.io");
|
|
150
|
+
}
|
|
151
|
+
else if (endpoint.search || endpoint.hash) {
|
|
152
|
+
issues.push("endpoint must not carry a query string or fragment; pass the api base url only");
|
|
153
|
+
}
|
|
154
|
+
else if (!isExtendableIngestPath(endpoint.pathname)) {
|
|
155
|
+
issues.push(`endpoint path cannot be extended to ${CANONICAL_BROWSER_INGEST_PATH}; pass the api base url, the ${CLUEBASE_API_V1_PREFIX} base, or the canonical ingest path`);
|
|
143
156
|
}
|
|
144
157
|
if (!config.projectKey) {
|
|
145
158
|
issues.push("projectKey is required");
|
|
@@ -47,5 +47,7 @@ export declare const PARAMETER_CAPTURE_MODE: {
|
|
|
47
47
|
readonly maskedFingerprint: "masked_fingerprint";
|
|
48
48
|
readonly forbidden: "forbidden";
|
|
49
49
|
readonly truncated: "truncated";
|
|
50
|
+
readonly encrypted: "encrypted";
|
|
51
|
+
readonly plain: "plain";
|
|
50
52
|
};
|
|
51
53
|
export type ParameterCaptureMode = (typeof PARAMETER_CAPTURE_MODE)[keyof typeof PARAMETER_CAPTURE_MODE];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CanonicalJsonValue } from "./types.js";
|
|
2
|
+
export type LifecycleEventIdentity = Readonly<Record<string, CanonicalJsonValue>>;
|
|
3
|
+
export declare class LifecycleEventDedup {
|
|
4
|
+
private readonly seenAtMs;
|
|
5
|
+
shouldEmit(identity: LifecycleEventIdentity): boolean;
|
|
6
|
+
clear(): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { canonicalize } from "json-canonicalize";
|
|
2
|
+
const LIFECYCLE_DEDUPE_TTL_MS = 300000;
|
|
3
|
+
const LIFECYCLE_DEDUPE_MAX_ENTRIES = 4096;
|
|
4
|
+
export class LifecycleEventDedup {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.seenAtMs = new Map();
|
|
7
|
+
}
|
|
8
|
+
shouldEmit(identity) {
|
|
9
|
+
const key = canonicalize(identity);
|
|
10
|
+
const now = Date.now();
|
|
11
|
+
for (const [seenKey, seenAt] of this.seenAtMs) {
|
|
12
|
+
if (now - seenAt > LIFECYCLE_DEDUPE_TTL_MS) {
|
|
13
|
+
this.seenAtMs.delete(seenKey);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (this.seenAtMs.has(key)) {
|
|
17
|
+
this.seenAtMs.set(key, now);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (this.seenAtMs.size >= LIFECYCLE_DEDUPE_MAX_ENTRIES) {
|
|
21
|
+
const oldest = [...this.seenAtMs.entries()].sort((left, right) => left[1] - right[1])[0]?.[0];
|
|
22
|
+
if (oldest !== undefined)
|
|
23
|
+
this.seenAtMs.delete(oldest);
|
|
24
|
+
}
|
|
25
|
+
this.seenAtMs.set(key, now);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
clear() {
|
|
29
|
+
this.seenAtMs.clear();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type SdkEncryptionConfig = {
|
|
2
|
+
readonly keyVersion: number;
|
|
3
|
+
readonly publicKey: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const applyDeliveredSdkEncryption: (value: unknown) => void;
|
|
6
|
+
export declare const resetSdkEncryption: () => void;
|
|
7
|
+
export declare const currentSdkEncryption: () => SdkEncryptionConfig | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ingestSdkEncryptionSchema } from "../exporter/delivery-response-contract.js";
|
|
2
|
+
let appliedEncryption = null;
|
|
3
|
+
export const applyDeliveredSdkEncryption = (value) => {
|
|
4
|
+
const parsed = ingestSdkEncryptionSchema.safeParse(value);
|
|
5
|
+
appliedEncryption = parsed.success
|
|
6
|
+
? {
|
|
7
|
+
keyVersion: parsed.data.key_version,
|
|
8
|
+
publicKey: parsed.data.public_key,
|
|
9
|
+
}
|
|
10
|
+
: null;
|
|
11
|
+
};
|
|
12
|
+
export const resetSdkEncryption = () => {
|
|
13
|
+
appliedEncryption = null;
|
|
14
|
+
};
|
|
15
|
+
export const currentSdkEncryption = () => appliedEncryption;
|
|
@@ -6,7 +6,7 @@ import type { TabManager } from "../context/tab.js";
|
|
|
6
6
|
import { TraceManager } from "../context/trace.js";
|
|
7
7
|
import type { BatchTransport } from "../exporter/batch.js";
|
|
8
8
|
import type { InternalMetricsStore } from "../internal/metrics.js";
|
|
9
|
-
import type { CluebaseConsentState, CluebaseContext, CluebaseInternalLogger,
|
|
9
|
+
import type { CluebaseConsentState, CluebaseContext, CluebaseInternalLogger, RawCapturedEvent, ResolvedCluebaseConfig } from "./types.js";
|
|
10
10
|
export declare const OFFICIAL_CUSTOM_EVENT_NAME: {
|
|
11
11
|
readonly identityIdentified: "identity_identified";
|
|
12
12
|
readonly identityReset: "identity_reset";
|
|
@@ -33,7 +33,6 @@ export declare const getSessionCaptureEligibility: (transport: BatchTransport) =
|
|
|
33
33
|
export declare class ProviderEventPipeline {
|
|
34
34
|
private runtime;
|
|
35
35
|
private currentViewId;
|
|
36
|
-
private usageGovernorFeedback;
|
|
37
36
|
private sessionCaptureEligible;
|
|
38
37
|
private unregisterSessionCaptureEligibility;
|
|
39
38
|
configure(runtime: ProviderEventPipelineRuntime): void;
|
|
@@ -48,7 +47,6 @@ export declare class ProviderEventPipeline {
|
|
|
48
47
|
tabId: string | null;
|
|
49
48
|
};
|
|
50
49
|
enqueueRawEvent(event: RawCapturedEvent, skipSessionTouch?: boolean): void;
|
|
51
|
-
applyUsageGovernorFeedback(feedback: IngestUsageGovernorFeedback | null | undefined): void;
|
|
52
50
|
refreshSessionCaptureEligibility(): void;
|
|
53
51
|
buildContext(event?: RawCapturedEvent): CluebaseContext;
|
|
54
52
|
private attachInteractionContext;
|
|
@@ -6,7 +6,6 @@ import { sanitizeObject, } from "../exporter/sanitize.js";
|
|
|
6
6
|
import { FRONTEND_EVENT_NAME } from "./contracts.js";
|
|
7
7
|
import { shouldSampleSession } from "./sampling.js";
|
|
8
8
|
import { stripQueryAndHashFromUrl, toStringOrNull, toSubjectProfile, } from "./sdk-input-normalization.js";
|
|
9
|
-
import { DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK, hasUsageGovernorFeedbackChanged, isCostCapabilityDisabled, normalizeIngestUsageGovernorFeedback, } from "./usage-governor.js";
|
|
10
9
|
const TRACE_ROOT_EVENTS = new Set([
|
|
11
10
|
"page_view",
|
|
12
11
|
"route_change",
|
|
@@ -76,7 +75,6 @@ export class ProviderEventPipeline {
|
|
|
76
75
|
constructor() {
|
|
77
76
|
this.runtime = null;
|
|
78
77
|
this.currentViewId = null;
|
|
79
|
-
this.usageGovernorFeedback = DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK;
|
|
80
78
|
this.sessionCaptureEligible = true;
|
|
81
79
|
this.unregisterSessionCaptureEligibility = null;
|
|
82
80
|
}
|
|
@@ -86,14 +84,12 @@ export class ProviderEventPipeline {
|
|
|
86
84
|
this.unregisterSessionCaptureEligibility =
|
|
87
85
|
registerSessionCaptureEligibility(runtime.transport, () => this.sessionCaptureEligible);
|
|
88
86
|
this.currentViewId = generateViewId();
|
|
89
|
-
this.usageGovernorFeedback = DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK;
|
|
90
87
|
this.refreshSessionCaptureEligibility();
|
|
91
88
|
}
|
|
92
89
|
clearRuntime() {
|
|
93
90
|
this.unregisterSessionCaptureEligibility?.();
|
|
94
91
|
this.unregisterSessionCaptureEligibility = null;
|
|
95
92
|
this.runtime = null;
|
|
96
|
-
this.usageGovernorFeedback = DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK;
|
|
97
93
|
this.sessionCaptureEligible = true;
|
|
98
94
|
}
|
|
99
95
|
resetViewId() {
|
|
@@ -150,10 +146,6 @@ export class ProviderEventPipeline {
|
|
|
150
146
|
runtime.internalMetrics.increment("sampling_dropped_count", droppedCount);
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
|
-
if (isCostCapabilityDisabled(this.usageGovernorFeedback, "event_collection")) {
|
|
154
|
-
runtime.internalMetrics.increment("dropped_count");
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
149
|
const sourceSignal = rawEventToSourceSignalEvent(eventWithLocation, {
|
|
158
150
|
context,
|
|
159
151
|
resource: createFrontendResourceAttributes(runtime.config),
|
|
@@ -188,13 +180,6 @@ export class ProviderEventPipeline {
|
|
|
188
180
|
});
|
|
189
181
|
}
|
|
190
182
|
}
|
|
191
|
-
applyUsageGovernorFeedback(feedback) {
|
|
192
|
-
const nextFeedback = normalizeIngestUsageGovernorFeedback(feedback);
|
|
193
|
-
if (!hasUsageGovernorFeedbackChanged(this.usageGovernorFeedback, nextFeedback)) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
this.usageGovernorFeedback = nextFeedback;
|
|
197
|
-
}
|
|
198
183
|
refreshSessionCaptureEligibility() {
|
|
199
184
|
const config = this.runtime?.config;
|
|
200
185
|
if (!config) {
|
|
@@ -6,8 +6,9 @@ type CreateCluebaseOtelPipelineOptions = {
|
|
|
6
6
|
resolved: ResolvedCluebaseConfig;
|
|
7
7
|
getTransport: () => BatchTransport | null;
|
|
8
8
|
getContext: () => CluebaseContext;
|
|
9
|
+
ignoreUrlPrefixes: readonly string[];
|
|
9
10
|
};
|
|
10
|
-
export declare const createCluebaseOtelPipeline: ({ resolved, getTransport, getContext, }: CreateCluebaseOtelPipelineOptions) => CluebaseOtelProvider | null;
|
|
11
|
+
export declare const createCluebaseOtelPipeline: ({ resolved, getTransport, getContext, ignoreUrlPrefixes, }: CreateCluebaseOtelPipelineOptions) => CluebaseOtelProvider | null;
|
|
11
12
|
export declare const shutdownCluebaseOtelProvider: (provider: CluebaseOtelProvider | null) => void;
|
|
12
13
|
export declare const shutdownCluebaseOtelProviderAsync: (provider: CluebaseOtelProvider | null) => Promise<void>;
|
|
13
14
|
export declare const forceFlushCluebaseOtelProvider: (provider: CluebaseOtelProvider | null, signal?: AbortSignal) => Promise<void>;
|
|
@@ -2,7 +2,6 @@ import { context, propagation, trace } from "@opentelemetry/api";
|
|
|
2
2
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
3
3
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
4
4
|
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
5
|
-
import { resolveBrowserTokenEndpoint, resolveCanonicalIngestEndpoint, } from "../exporter/client.js";
|
|
6
5
|
import { createFrontendResourceAttributes } from "../exporter/frontend-resource.js";
|
|
7
6
|
import { ObservationSourceEventExporter } from "../exporter/observation-source-event-exporter.js";
|
|
8
7
|
import { createCluebaseNetworkPropagator } from "../instrumentation/network.js";
|
|
@@ -16,7 +15,7 @@ const getTracerDelegate = (provider) => {
|
|
|
16
15
|
const candidate = provider;
|
|
17
16
|
return candidate.getDelegate?.();
|
|
18
17
|
};
|
|
19
|
-
export const createCluebaseOtelPipeline = ({ resolved, getTransport, getContext, }) => {
|
|
18
|
+
export const createCluebaseOtelPipeline = ({ resolved, getTransport, getContext, ignoreUrlPrefixes, }) => {
|
|
20
19
|
if (!getTransport()) {
|
|
21
20
|
return null;
|
|
22
21
|
}
|
|
@@ -58,15 +57,7 @@ export const createCluebaseOtelPipeline = ({ resolved, getTransport, getContext,
|
|
|
58
57
|
},
|
|
59
58
|
getContext,
|
|
60
59
|
deniedKeys: resolved.privacy?.deniedKeys ?? [],
|
|
61
|
-
ignoreUrlPrefixes
|
|
62
|
-
resolveCanonicalIngestEndpoint(resolved.endpoint),
|
|
63
|
-
resolveCanonicalIngestEndpoint(resolved.originalEndpoint),
|
|
64
|
-
resolveBrowserTokenEndpoint(resolved.endpoint),
|
|
65
|
-
resolveBrowserTokenEndpoint(resolved.originalEndpoint),
|
|
66
|
-
...(resolved.localSetupCheckEndpoint
|
|
67
|
-
? [resolved.localSetupCheckEndpoint]
|
|
68
|
-
: []),
|
|
69
|
-
].filter((url) => typeof url === "string" && url.length > 0),
|
|
60
|
+
ignoreUrlPrefixes,
|
|
70
61
|
});
|
|
71
62
|
const provider = new WebTracerProvider({
|
|
72
63
|
resource,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TraceManager } from "../context/trace.js";
|
|
2
2
|
import type { BatchTransport } from "../exporter/batch.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { IngestClient } from "../exporter/client.js";
|
|
4
4
|
import type { InternalMetricsStore } from "../internal/metrics.js";
|
|
5
5
|
import { type CluebaseOtelProvider } from "./sdk-otel-pipeline.js";
|
|
6
6
|
import type { CaptureModuleContext, CluebaseConsentState, CluebaseInternalLogger, RawCapturedEvent, ResolvedCluebaseConfig } from "./types.js";
|
|
@@ -41,7 +41,8 @@ export declare class ProviderRuntimeSurfaces {
|
|
|
41
41
|
constructor(options: ProviderRuntimeSurfacesOptions);
|
|
42
42
|
buildCaptureContext(): CaptureModuleContext;
|
|
43
43
|
warmupBrowserToken(client: IngestClient): void;
|
|
44
|
-
|
|
44
|
+
refreshIgnoreUrlPrefixes(): void;
|
|
45
|
+
startCaptureModules(config: ResolvedCluebaseConfig, ignoreUrlPrefixes: readonly string[]): void;
|
|
45
46
|
stopCaptureModules(): void;
|
|
46
47
|
abortBrowserTokenWarmup(): void;
|
|
47
48
|
flushCaptureSummaries(reason: string): Promise<void>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { TraceManager } from "../context/trace.js";
|
|
2
|
-
import { resolveBrowserTokenEndpoint, resolveCanonicalIngestEndpoint, } from "../exporter/client.js";
|
|
3
2
|
import { startActionCapture } from "../instrumentation/action.js";
|
|
4
3
|
import { startDragCapture } from "../instrumentation/drag.js";
|
|
5
4
|
import { startErrorCapture } from "../instrumentation/error.js";
|
|
@@ -88,7 +87,12 @@ export class ProviderRuntimeSurfaces {
|
|
|
88
87
|
}
|
|
89
88
|
});
|
|
90
89
|
}
|
|
91
|
-
|
|
90
|
+
refreshIgnoreUrlPrefixes() {
|
|
91
|
+
for (const handle of this.captureHandles) {
|
|
92
|
+
handle.refreshIgnoreUrlPrefixes?.();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
startCaptureModules(config, ignoreUrlPrefixes) {
|
|
92
96
|
const captureContext = this.buildCaptureContext();
|
|
93
97
|
const handles = [];
|
|
94
98
|
handles.push(startInputCapture(captureContext));
|
|
@@ -101,10 +105,7 @@ export class ProviderRuntimeSurfaces {
|
|
|
101
105
|
handles.push(startNetworkCapture(captureContext, {
|
|
102
106
|
tracerProvider: this.getTracerProvider() ?? undefined,
|
|
103
107
|
deniedKeys: config.privacy.deniedKeys,
|
|
104
|
-
ignoreUrlPrefixes
|
|
105
|
-
resolveCanonicalIngestEndpoint(config.endpoint),
|
|
106
|
-
resolveBrowserTokenEndpoint(config.endpoint),
|
|
107
|
-
],
|
|
108
|
+
ignoreUrlPrefixes,
|
|
108
109
|
tracePropagationOrigins: config.network.tracePropagationOrigins,
|
|
109
110
|
createRequestTraceContext: (input) => {
|
|
110
111
|
const traceManager = this.getTraceManager();
|
package/dist/provider/sdk.js
CHANGED
|
@@ -12,6 +12,7 @@ import { initSdkCoreWasm } from "../exporter/sdk-core-wasm.js";
|
|
|
12
12
|
import { InternalMetricsStore } from "../internal/metrics.js";
|
|
13
13
|
import { isConfigValid, resolveConfig, validateConfig } from "./config.js";
|
|
14
14
|
import { DEFAULT_BATCH_FLUSH_THRESHOLD_BYTES, DEFAULT_CONSENT, DEFAULT_MAX_BATCH_QUEUE_BYTES, DEFAULT_MAX_BATCH_QUEUE_ITEMS, SDK_VERSION, } from "./constants.js";
|
|
15
|
+
import { LifecycleEventDedup } from "./lifecycle-event-dedup.js";
|
|
15
16
|
import { getSdkErrorDetails, reportSdkError } from "./sdk-error.js";
|
|
16
17
|
import { OFFICIAL_CUSTOM_EVENT_NAME, ProviderEventPipeline, readCurrentLifecycleUrl, } from "./sdk-event-pipeline.js";
|
|
17
18
|
import { isValidPublicCustomEventName, normalizeProfileAvatarUrl, normalizePublicIdentityId, toStringOrNull, } from "./sdk-input-normalization.js";
|
|
@@ -48,6 +49,8 @@ class CluebaseBrowserSdkImpl {
|
|
|
48
49
|
this.environmentManager = null;
|
|
49
50
|
this.traceManager = null;
|
|
50
51
|
this.diagnostics = new DiagnosticStore();
|
|
52
|
+
this.lifecycleDedup = new LifecycleEventDedup();
|
|
53
|
+
this.ingestUrlPrefixes = [];
|
|
51
54
|
this.transport = null;
|
|
52
55
|
this.tracerProvider = null;
|
|
53
56
|
this.logger = logger;
|
|
@@ -99,7 +102,6 @@ class CluebaseBrowserSdkImpl {
|
|
|
99
102
|
const nextSignature = JSON.stringify({
|
|
100
103
|
endpoint: resolved.endpoint,
|
|
101
104
|
originalEndpoint: resolved.originalEndpoint,
|
|
102
|
-
localSetupCheckEndpoint: resolved.localSetupCheckEndpoint,
|
|
103
105
|
projectKey: resolved.projectKey,
|
|
104
106
|
producerId: resolved.producerId,
|
|
105
107
|
autocapture: resolved.autocapture,
|
|
@@ -122,6 +124,7 @@ class CluebaseBrowserSdkImpl {
|
|
|
122
124
|
this.shutdown();
|
|
123
125
|
this.internalMetrics.reset();
|
|
124
126
|
this.diagnostics = new DiagnosticStore();
|
|
127
|
+
this.lifecycleDedup.clear();
|
|
125
128
|
this.config = resolved;
|
|
126
129
|
this.initSignature = nextSignature;
|
|
127
130
|
this.identityManager = new IdentityManager();
|
|
@@ -143,6 +146,9 @@ class CluebaseBrowserSdkImpl {
|
|
|
143
146
|
schemaVersion: 1,
|
|
144
147
|
metrics: this.internalMetrics,
|
|
145
148
|
diagnostics: this.diagnostics,
|
|
149
|
+
onEndpointsResolved: (endpoints) => {
|
|
150
|
+
this.adoptIngestUrlPrefixes(endpoints);
|
|
151
|
+
},
|
|
146
152
|
});
|
|
147
153
|
this.transport = new BatchTransport({
|
|
148
154
|
client: ingestClient,
|
|
@@ -154,12 +160,13 @@ class CluebaseBrowserSdkImpl {
|
|
|
154
160
|
queueMaxBytes: DEFAULT_MAX_BATCH_QUEUE_BYTES,
|
|
155
161
|
queueMaxItems: DEFAULT_MAX_BATCH_QUEUE_ITEMS,
|
|
156
162
|
projectKey: resolved.projectKey,
|
|
163
|
+
endpoint: resolved.endpoint,
|
|
164
|
+
sdkVersion: SDK_VERSION,
|
|
165
|
+
sourceSchemaVersion: "1",
|
|
166
|
+
sdkRequest: "browser",
|
|
157
167
|
initialConsent: this.consent,
|
|
158
168
|
metrics: this.internalMetrics,
|
|
159
169
|
diagnostics: this.diagnostics,
|
|
160
|
-
onGovernorFeedback: (feedback) => {
|
|
161
|
-
this.applyUsageGovernorFeedback(feedback);
|
|
162
|
-
},
|
|
163
170
|
});
|
|
164
171
|
this.consent = this.transport.currentConsent();
|
|
165
172
|
this.transport.start();
|
|
@@ -186,6 +193,7 @@ class CluebaseBrowserSdkImpl {
|
|
|
186
193
|
resolved,
|
|
187
194
|
getTransport: () => this.transport,
|
|
188
195
|
getContext: () => this.buildContext(),
|
|
196
|
+
ignoreUrlPrefixes: this.ingestUrlPrefixes,
|
|
189
197
|
});
|
|
190
198
|
this.initialized = true;
|
|
191
199
|
this.refreshSessionCaptureEligibility();
|
|
@@ -248,6 +256,16 @@ class CluebaseBrowserSdkImpl {
|
|
|
248
256
|
const sanitizedTraits = this.sanitizeIdentifyTraits(traits);
|
|
249
257
|
const subjectProfileKeys = safeProfileKeys(traits, this.config?.privacy.deniedKeys ?? []);
|
|
250
258
|
this.identityManager.identify(sanitizedUserId, sanitizedTraits);
|
|
259
|
+
if (!this.lifecycleDedup.shouldEmit({
|
|
260
|
+
action: "identify",
|
|
261
|
+
userId: sanitizedUserId,
|
|
262
|
+
anonymousId: previousAnonymousId,
|
|
263
|
+
organizationId: this.organizationManager?.getContext().organizationId ?? null,
|
|
264
|
+
subjectTraits: this.identityManager.getContext().traits,
|
|
265
|
+
profileKeys: subjectProfileKeys,
|
|
266
|
+
})) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
251
269
|
this.enqueueOfficialCustomEvent(OFFICIAL_CUSTOM_EVENT_NAME.identityIdentified, {
|
|
252
270
|
identity_action: "identify",
|
|
253
271
|
profile_keys: subjectProfileKeys,
|
|
@@ -279,9 +297,20 @@ class CluebaseBrowserSdkImpl {
|
|
|
279
297
|
return;
|
|
280
298
|
}
|
|
281
299
|
const sanitizedOrgId = this.sanitizeIdentityValue("organizationId", normalizedOrganizationId);
|
|
282
|
-
const
|
|
300
|
+
const identityBeforeGroup = this.identityManager?.getContext();
|
|
301
|
+
const previousUserId = identityBeforeGroup?.userId ?? null;
|
|
283
302
|
const organizationProfileKeys = safeProfileKeys(traits, this.config?.privacy.deniedKeys ?? []);
|
|
284
303
|
this.organizationManager.setOrganization(sanitizedOrgId, traits);
|
|
304
|
+
if (!this.lifecycleDedup.shouldEmit({
|
|
305
|
+
action: "associate_organization",
|
|
306
|
+
userId: previousUserId,
|
|
307
|
+
anonymousId: identityBeforeGroup?.anonymousId ?? null,
|
|
308
|
+
organizationId: sanitizedOrgId,
|
|
309
|
+
organizationTraits: this.organizationManager.getContext().traits,
|
|
310
|
+
profileKeys: organizationProfileKeys,
|
|
311
|
+
})) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
285
314
|
this.enqueueOfficialCustomEvent(OFFICIAL_CUSTOM_EVENT_NAME.organizationAssociated, {
|
|
286
315
|
organization_action: "associate",
|
|
287
316
|
profile_keys: organizationProfileKeys,
|
|
@@ -298,6 +327,22 @@ class CluebaseBrowserSdkImpl {
|
|
|
298
327
|
});
|
|
299
328
|
}
|
|
300
329
|
}
|
|
330
|
+
link(input) {
|
|
331
|
+
if (!this.ensureInitializedForPublicCall("link")) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const subject = input?.subject;
|
|
335
|
+
if (subject?.type !== "organization") {
|
|
336
|
+
this.warnPublicDrop("link", "subject.type must be 'organization'");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const organizationId = normalizePublicIdentityId(subject.id);
|
|
340
|
+
if (!organizationId) {
|
|
341
|
+
this.warnPublicDrop("link", "subject.id must be a non-empty string");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this.emitExternalLink("link", organizationId, input?.external?.namespace, input?.external?.id);
|
|
345
|
+
}
|
|
301
346
|
linkExternalAccount(namespace, externalId) {
|
|
302
347
|
if (!this.ensureInitializedForPublicCall("linkExternalAccount")) {
|
|
303
348
|
return;
|
|
@@ -306,21 +351,32 @@ class CluebaseBrowserSdkImpl {
|
|
|
306
351
|
this.warnPublicDrop("linkExternalAccount", "organization manager is unavailable");
|
|
307
352
|
return;
|
|
308
353
|
}
|
|
354
|
+
const organizationId = this.organizationManager.getContext().organizationId;
|
|
355
|
+
if (!organizationId) {
|
|
356
|
+
this.warnPublicDrop("linkExternalAccount", "setOrganization must be called before linkExternalAccount");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
this.emitExternalLink("linkExternalAccount", organizationId, namespace, externalId);
|
|
360
|
+
}
|
|
361
|
+
emitExternalLink(publicCallName, organizationId, namespace, externalId) {
|
|
309
362
|
try {
|
|
310
|
-
const organizationId = this.organizationManager.getContext().organizationId;
|
|
311
|
-
if (!organizationId) {
|
|
312
|
-
this.warnPublicDrop("linkExternalAccount", "setOrganization must be called before linkExternalAccount");
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
363
|
const normalizedNamespace = normalizePublicIdentityId(namespace);
|
|
316
364
|
const normalizedExternalId = normalizePublicIdentityId(externalId);
|
|
317
365
|
if (!normalizedNamespace || !normalizedExternalId) {
|
|
318
|
-
this.warnPublicDrop(
|
|
366
|
+
this.warnPublicDrop(publicCallName, "namespace and externalId must be non-empty strings");
|
|
319
367
|
return;
|
|
320
368
|
}
|
|
321
369
|
const sanitizedExternalId = this.sanitizeIdentityValue("externalId", normalizedExternalId);
|
|
370
|
+
if (!this.lifecycleDedup.shouldEmit({
|
|
371
|
+
action: "link",
|
|
372
|
+
organizationId,
|
|
373
|
+
externalNamespace: normalizedNamespace,
|
|
374
|
+
externalId: sanitizedExternalId,
|
|
375
|
+
})) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
322
378
|
this.enqueueOfficialCustomEvent(OFFICIAL_CUSTOM_EVENT_NAME.externalAccountLinked, {
|
|
323
|
-
identity_action: "
|
|
379
|
+
identity_action: "link",
|
|
324
380
|
external_namespace: normalizedNamespace,
|
|
325
381
|
}, false, {
|
|
326
382
|
link_type: "canonical_subject_to_external",
|
|
@@ -331,36 +387,12 @@ class CluebaseBrowserSdkImpl {
|
|
|
331
387
|
this.flushLifecycleEvents();
|
|
332
388
|
}
|
|
333
389
|
catch {
|
|
334
|
-
this.logger.error(
|
|
390
|
+
this.logger.error(`${publicCallName} failed`, {
|
|
335
391
|
error_code: "sdk_operation_failed",
|
|
336
392
|
});
|
|
337
393
|
}
|
|
338
394
|
}
|
|
339
395
|
reset() {
|
|
340
|
-
if (!this.initialized) {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
try {
|
|
344
|
-
const previousUserId = this.identityManager?.getContext().userId ?? null;
|
|
345
|
-
const previousOrganizationId = this.organizationManager?.getContext().organizationId ?? null;
|
|
346
|
-
this.enqueueOfficialCustomEvent(OFFICIAL_CUSTOM_EVENT_NAME.identityReset, {
|
|
347
|
-
identity_action: "reset",
|
|
348
|
-
}, false, {
|
|
349
|
-
link_type: "logout",
|
|
350
|
-
user_id: previousUserId,
|
|
351
|
-
organization_id: previousOrganizationId,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
catch {
|
|
355
|
-
this.logger.error("reset failed", {
|
|
356
|
-
error_code: "sdk_operation_failed",
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
finally {
|
|
360
|
-
this.resetSessionState();
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
logout() {
|
|
364
396
|
if (!this.initialized) {
|
|
365
397
|
return;
|
|
366
398
|
}
|
|
@@ -376,7 +408,7 @@ class CluebaseBrowserSdkImpl {
|
|
|
376
408
|
});
|
|
377
409
|
}
|
|
378
410
|
catch {
|
|
379
|
-
this.logger.error("
|
|
411
|
+
this.logger.error("reset failed", {
|
|
380
412
|
error_code: "sdk_operation_failed",
|
|
381
413
|
});
|
|
382
414
|
}
|
|
@@ -441,6 +473,9 @@ class CluebaseBrowserSdkImpl {
|
|
|
441
473
|
});
|
|
442
474
|
}
|
|
443
475
|
setConsent(consent) {
|
|
476
|
+
if (this.consent !== consent) {
|
|
477
|
+
this.lifecycleDedup.clear();
|
|
478
|
+
}
|
|
444
479
|
this.consent = consent;
|
|
445
480
|
if (!this.initialized) {
|
|
446
481
|
return;
|
|
@@ -538,21 +573,15 @@ class CluebaseBrowserSdkImpl {
|
|
|
538
573
|
void this.transport?.flush();
|
|
539
574
|
}
|
|
540
575
|
async flush() {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
await forceFlushCluebaseOtelProvider(this.tracerProvider, deadline?.signal);
|
|
545
|
-
await this.transport?.flush(deadline);
|
|
546
|
-
}
|
|
547
|
-
finally {
|
|
548
|
-
this.transport?.endFlushDeadline(deadline);
|
|
549
|
-
}
|
|
576
|
+
await this.flushCaptureSummaries("manual_flush");
|
|
577
|
+
await forceFlushCluebaseOtelProvider(this.tracerProvider);
|
|
578
|
+
await this.transport?.flush();
|
|
550
579
|
}
|
|
551
580
|
async close() {
|
|
552
|
-
|
|
553
|
-
await
|
|
581
|
+
this.transport?.beginClose();
|
|
582
|
+
await this.flushCaptureSummaries("close");
|
|
554
583
|
this.stopCaptureModules();
|
|
555
|
-
await forceFlushCluebaseOtelProvider(this.tracerProvider
|
|
584
|
+
await forceFlushCluebaseOtelProvider(this.tracerProvider);
|
|
556
585
|
await this.transport?.close();
|
|
557
586
|
if (this.transport?.getDiagnostics().lifecycle === "closing_failed") {
|
|
558
587
|
await this.shutdownAsync();
|
|
@@ -563,11 +592,19 @@ class CluebaseBrowserSdkImpl {
|
|
|
563
592
|
warmupBrowserToken(client) {
|
|
564
593
|
this.runtimeSurfaces.warmupBrowserToken(client);
|
|
565
594
|
}
|
|
595
|
+
adoptIngestUrlPrefixes(endpoints) {
|
|
596
|
+
for (const url of [endpoints.ingestBatchUrl, endpoints.browserTokenUrl]) {
|
|
597
|
+
if (url.length > 0 && !this.ingestUrlPrefixes.includes(url)) {
|
|
598
|
+
this.ingestUrlPrefixes.push(url);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
this.runtimeSurfaces.refreshIgnoreUrlPrefixes();
|
|
602
|
+
}
|
|
566
603
|
startCaptureModules() {
|
|
567
604
|
if (!this.config) {
|
|
568
605
|
return;
|
|
569
606
|
}
|
|
570
|
-
this.runtimeSurfaces.startCaptureModules(this.config);
|
|
607
|
+
this.runtimeSurfaces.startCaptureModules(this.config, this.ingestUrlPrefixes);
|
|
571
608
|
}
|
|
572
609
|
stopCaptureModules() {
|
|
573
610
|
this.runtimeSurfaces.stopCaptureModules();
|
|
@@ -575,9 +612,6 @@ class CluebaseBrowserSdkImpl {
|
|
|
575
612
|
async flushCaptureSummaries(reason) {
|
|
576
613
|
await this.runtimeSurfaces.flushCaptureSummaries(reason);
|
|
577
614
|
}
|
|
578
|
-
applyUsageGovernorFeedback(feedback) {
|
|
579
|
-
this.eventPipeline.applyUsageGovernorFeedback(feedback);
|
|
580
|
-
}
|
|
581
615
|
buildContext() {
|
|
582
616
|
return this.eventPipeline.buildContext();
|
|
583
617
|
}
|