@genn-inc/cluebase-frontend-sdk 0.0.2 → 0.0.3
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/context/action-context.js +27 -7
- package/dist/index.js +7 -5
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/sdk-error.d.ts +7 -0
- package/dist/provider/sdk-error.js +48 -0
- package/dist/provider/sdk.js +15 -1
- package/package.json +53 -55
|
@@ -10,6 +10,17 @@ const resolveRestoredFunction = (candidate) => {
|
|
|
10
10
|
};
|
|
11
11
|
const captureFlag = (options) => typeof options === "boolean" ? options : options?.capture === true;
|
|
12
12
|
const isCallable = (value) => typeof value === "function";
|
|
13
|
+
const isWeakMapKey = (value) => (value !== null && typeof value === "object") || typeof value === "function";
|
|
14
|
+
const resolveEventTarget = (receiver) => {
|
|
15
|
+
if (isWeakMapKey(receiver))
|
|
16
|
+
return receiver;
|
|
17
|
+
if (receiver === undefined &&
|
|
18
|
+
typeof EventTarget !== "undefined" &&
|
|
19
|
+
globalThis instanceof EventTarget) {
|
|
20
|
+
return globalThis;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
13
24
|
const invokeListener = (listener, thisValue, event) => {
|
|
14
25
|
if (typeof listener === "function") {
|
|
15
26
|
return listener.call(thisValue, event);
|
|
@@ -185,11 +196,16 @@ export class PrivateActionContextManager {
|
|
|
185
196
|
originalAdd.call(this, type, listener, options);
|
|
186
197
|
return;
|
|
187
198
|
}
|
|
188
|
-
|
|
199
|
+
const target = resolveEventTarget(this);
|
|
200
|
+
if (!target) {
|
|
189
201
|
originalAdd.call(this, type, listener, options);
|
|
190
202
|
return;
|
|
191
203
|
}
|
|
192
|
-
|
|
204
|
+
if (!listener) {
|
|
205
|
+
originalAdd.call(target, type, listener, options);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const registrations = manager.eventRegistrations.get(target) ?? [];
|
|
193
209
|
if (registrations.some((registration) => registration.type === type &&
|
|
194
210
|
registration.listener === listener &&
|
|
195
211
|
captureFlag(registration.options) === captureFlag(options))) {
|
|
@@ -198,11 +214,10 @@ export class PrivateActionContextManager {
|
|
|
198
214
|
if (typeof options === "object" &&
|
|
199
215
|
options !== null &&
|
|
200
216
|
options.signal?.aborted) {
|
|
201
|
-
originalAdd.call(
|
|
217
|
+
originalAdd.call(target, type, listener, options);
|
|
202
218
|
return;
|
|
203
219
|
}
|
|
204
220
|
const registrationSnapshot = manager.capture();
|
|
205
|
-
const target = this;
|
|
206
221
|
const wrappedListener = function (event) {
|
|
207
222
|
const eventSnapshot = manager.eventContexts.get(event);
|
|
208
223
|
const snapshot = eventSnapshot ?? registrationSnapshot;
|
|
@@ -251,16 +266,21 @@ export class PrivateActionContextManager {
|
|
|
251
266
|
originalRemove.call(this, type, listener, options);
|
|
252
267
|
return;
|
|
253
268
|
}
|
|
254
|
-
|
|
269
|
+
const target = resolveEventTarget(this);
|
|
270
|
+
if (!target) {
|
|
255
271
|
originalRemove.call(this, type, listener, options);
|
|
256
272
|
return;
|
|
257
273
|
}
|
|
258
|
-
|
|
274
|
+
if (!listener) {
|
|
275
|
+
originalRemove.call(target, type, listener, options);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const registrations = manager.eventRegistrations.get(target) ?? [];
|
|
259
279
|
const index = registrations.findIndex((registration) => registration.type === type &&
|
|
260
280
|
registration.listener === listener &&
|
|
261
281
|
captureFlag(registration.options) === captureFlag(options));
|
|
262
282
|
if (index < 0) {
|
|
263
|
-
originalRemove.call(
|
|
283
|
+
originalRemove.call(target, type, listener, options);
|
|
264
284
|
return;
|
|
265
285
|
}
|
|
266
286
|
const registration = registrations[index];
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseSendableUrl } from "./provider/config.js";
|
|
2
2
|
import { createCluebaseBrowserSdk } from "./provider/sdk.js";
|
|
3
|
+
import { reportSdkError } from "./provider/sdk-error.js";
|
|
3
4
|
const GLOBAL_SDK_KEY = "__cluebase_frontend_sdk_singleton__";
|
|
4
5
|
const globalWithCluebaseSdk = globalThis;
|
|
5
6
|
const existingSdk = globalWithCluebaseSdk[GLOBAL_SDK_KEY];
|
|
@@ -7,7 +8,8 @@ const internalSdk = existingSdk ?? createCluebaseBrowserSdk();
|
|
|
7
8
|
if (!existingSdk) {
|
|
8
9
|
globalWithCluebaseSdk[GLOBAL_SDK_KEY] = internalSdk;
|
|
9
10
|
}
|
|
10
|
-
const reportPublicSdkError = (operation) => {
|
|
11
|
+
const reportPublicSdkError = (operation, error) => {
|
|
12
|
+
reportSdkError(operation, error);
|
|
11
13
|
try {
|
|
12
14
|
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
13
15
|
console.warn(`[cluebase] ${operation} failed`, {
|
|
@@ -22,16 +24,16 @@ const safeCall = (operation, callback) => {
|
|
|
22
24
|
try {
|
|
23
25
|
callback();
|
|
24
26
|
}
|
|
25
|
-
catch {
|
|
26
|
-
reportPublicSdkError(operation);
|
|
27
|
+
catch (error) {
|
|
28
|
+
reportPublicSdkError(operation, error);
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
const safeAsyncCall = async (operation, callback) => {
|
|
30
32
|
try {
|
|
31
33
|
await callback();
|
|
32
34
|
}
|
|
33
|
-
catch {
|
|
34
|
-
reportPublicSdkError(operation);
|
|
35
|
+
catch (error) {
|
|
36
|
+
reportPublicSdkError(operation, error);
|
|
35
37
|
}
|
|
36
38
|
};
|
|
37
39
|
const REMOVED_PRIVACY_OPTIONS = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION: "0.0.
|
|
1
|
+
export declare const SDK_VERSION: "0.0.3";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type SdkErrorDetails = {
|
|
2
|
+
error_name: string;
|
|
3
|
+
error_message: string;
|
|
4
|
+
error_stack?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const getSdkErrorDetails: (error: unknown) => SdkErrorDetails;
|
|
7
|
+
export declare const reportSdkError: (operation: string, error: unknown) => SdkErrorDetails;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const redactSensitiveText = (value) => value
|
|
2
|
+
.replace(/\bpk_[A-Za-z0-9_-]+\b/g, "[redacted-project-key]")
|
|
3
|
+
.replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/-]+=*/gi, "$1 [redacted]")
|
|
4
|
+
.replace(/(https?:\/\/[^\s?]+)\?[^\s]*/gi, "$1?[redacted]")
|
|
5
|
+
.slice(0, 500);
|
|
6
|
+
const readErrorField = (error, field) => {
|
|
7
|
+
try {
|
|
8
|
+
const value = error?.[field];
|
|
9
|
+
return typeof value === "string" ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export const getSdkErrorDetails = (error) => {
|
|
16
|
+
let fallbackMessage;
|
|
17
|
+
try {
|
|
18
|
+
fallbackMessage = String(error);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
fallbackMessage = "Unknown thrown value";
|
|
22
|
+
}
|
|
23
|
+
const stack = readErrorField(error, "stack");
|
|
24
|
+
return {
|
|
25
|
+
error_name: readErrorField(error, "name") || "UnknownError",
|
|
26
|
+
error_message: redactSensitiveText(readErrorField(error, "message") || fallbackMessage),
|
|
27
|
+
...(stack
|
|
28
|
+
? {
|
|
29
|
+
error_stack: stack
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.slice(1, 9)
|
|
32
|
+
.map(redactSensitiveText)
|
|
33
|
+
.join("\n"),
|
|
34
|
+
}
|
|
35
|
+
: {}),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export const reportSdkError = (operation, error) => {
|
|
39
|
+
const details = getSdkErrorDetails(error);
|
|
40
|
+
try {
|
|
41
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
42
|
+
const stack = details.error_stack ? `\n${details.error_stack}` : "";
|
|
43
|
+
console.error(`[cluebase] ${operation} failed (${details.error_name}): ${details.error_message}${stack}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
return details;
|
|
48
|
+
};
|
package/dist/provider/sdk.js
CHANGED
|
@@ -15,6 +15,7 @@ import { isValidPublicCustomEventName, normalizeProfileAvatarUrl, normalizePubli
|
|
|
15
15
|
import { OFFICIAL_CUSTOM_EVENT_NAME, ProviderEventPipeline, readCurrentLifecycleUrl, } from "./sdk-event-pipeline.js";
|
|
16
16
|
import { createCluebaseOtelPipeline, forceFlushCluebaseOtelProvider, shutdownCluebaseOtelProvider, shutdownCluebaseOtelProviderAsync, } from "./sdk-otel-pipeline.js";
|
|
17
17
|
import { ProviderRuntimeSurfaces } from "./sdk-runtime-surfaces.js";
|
|
18
|
+
import { getSdkErrorDetails, reportSdkError } from "./sdk-error.js";
|
|
18
19
|
const createNoopLogger = () => {
|
|
19
20
|
return {
|
|
20
21
|
debug: () => undefined,
|
|
@@ -192,11 +193,24 @@ class CluebaseBrowserSdkImpl {
|
|
|
192
193
|
}
|
|
193
194
|
this.installUnloadHandlers();
|
|
194
195
|
}
|
|
195
|
-
catch {
|
|
196
|
+
catch (error) {
|
|
197
|
+
const errorDetails = getSdkErrorDetails(error);
|
|
196
198
|
this.logger.error("Cluebase Frontend SDK init failed", {
|
|
197
199
|
error_code: "sdk_operation_failed",
|
|
200
|
+
...errorDetails,
|
|
198
201
|
});
|
|
202
|
+
reportSdkError("init", error);
|
|
199
203
|
this.warnPublicDrop("init", "sdk_operation_failed");
|
|
204
|
+
try {
|
|
205
|
+
this.shutdown();
|
|
206
|
+
}
|
|
207
|
+
catch (cleanupError) {
|
|
208
|
+
this.logger.error("Cluebase Frontend SDK init cleanup failed", {
|
|
209
|
+
error_code: "sdk_cleanup_failed",
|
|
210
|
+
...getSdkErrorDetails(cleanupError),
|
|
211
|
+
});
|
|
212
|
+
reportSdkError("init cleanup", cleanupError);
|
|
213
|
+
}
|
|
200
214
|
}
|
|
201
215
|
}
|
|
202
216
|
identify(userId, traits) {
|
package/package.json
CHANGED
|
@@ -1,56 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
}
|
|
2
|
+
"name": "@genn-inc/cluebase-frontend-sdk",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/genn-inc/cluebase.git"
|
|
7
|
+
},
|
|
8
|
+
"description": "Cluebase Frontend SDK — OTel-based observation-source event capture for frontend apps",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"browser": "./dist/index.js",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
25
|
+
"jsdom": "^26.1.0",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "^3.2.7",
|
|
28
|
+
"@cluebase/schema": "1.0.0",
|
|
29
|
+
"@cluebase/shared": "1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@noble/hashes": "1.8.0",
|
|
33
|
+
"@opentelemetry/api": ">=1.9.1 <1.10.0",
|
|
34
|
+
"@opentelemetry/api-logs": ">=0.218.0 <1.0.0",
|
|
35
|
+
"@opentelemetry/browser-instrumentation": ">=0.5.2 <1.0.0",
|
|
36
|
+
"@opentelemetry/core": ">=2.7.1 <3.0.0",
|
|
37
|
+
"@opentelemetry/instrumentation-document-load": ">=0.63.0 <1.0.0",
|
|
38
|
+
"@opentelemetry/instrumentation-fetch": ">=0.218.0 <1.0.0",
|
|
39
|
+
"@opentelemetry/instrumentation-xml-http-request": ">=0.218.0 <1.0.0",
|
|
40
|
+
"@opentelemetry/resources": ">=2.7.1 <3.0.0",
|
|
41
|
+
"@opentelemetry/sdk-trace-base": ">=2.7.1 <3.0.0",
|
|
42
|
+
"@opentelemetry/sdk-trace-web": ">=2.7.1 <3.0.0",
|
|
43
|
+
"json-canonicalize": "2.0.0",
|
|
44
|
+
"zod": "^4.4.3"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "node ./scripts/clean-dist.mjs && node ../../../scripts/generate-shared-contract-tables.mjs && node ./scripts/generate-version-module.mjs && tsc -p tsconfig.build.json && node ./scripts/fix-esm-relative-imports.mjs",
|
|
48
|
+
"lint": "pnpm exec biome lint src",
|
|
49
|
+
"test:package-import": "pnpm build && node ../../../tests/distribution/frontend-sdk/check-package-import.mjs",
|
|
50
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
51
|
+
"test": "vitest run --config ../../../tests/config/vitest/frontend-sdk.config.ts",
|
|
52
|
+
"test:cov": "vitest run --config ../../../tests/config/vitest/frontend-sdk.config.ts --coverage"
|
|
53
|
+
}
|
|
54
|
+
}
|