@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.
@@ -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
- if (!listener) {
199
+ const target = resolveEventTarget(this);
200
+ if (!target) {
189
201
  originalAdd.call(this, type, listener, options);
190
202
  return;
191
203
  }
192
- const registrations = manager.eventRegistrations.get(this) ?? [];
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(this, type, listener, options);
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
- if (!listener) {
269
+ const target = resolveEventTarget(this);
270
+ if (!target) {
255
271
  originalRemove.call(this, type, listener, options);
256
272
  return;
257
273
  }
258
- const registrations = manager.eventRegistrations.get(this) ?? [];
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(this, type, listener, options);
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.2";
1
+ export declare const SDK_VERSION: "0.0.3";
@@ -1,3 +1,3 @@
1
1
  // This file is generated by scripts/generate-version-module.mjs.
2
2
  // Do NOT edit by hand. The source of truth is `package.json` `version`.
3
- export const SDK_VERSION = "0.0.2";
3
+ export 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
+ };
@@ -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
- "name": "@genn-inc/cluebase-frontend-sdk",
3
- "version": "0.0.2",
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
- "scripts": {
24
- "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",
25
- "prepack": "pnpm build",
26
- "prepublishOnly": "pnpm lint && pnpm test:cov && pnpm typecheck && pnpm test:package-import",
27
- "lint": "pnpm exec biome lint src",
28
- "test:package-import": "pnpm build && node ../../../tests/distribution/frontend-sdk/check-package-import.mjs",
29
- "typecheck": "tsc -p tsconfig.typecheck.json",
30
- "test": "vitest run --config ../../../tests/config/vitest/frontend-sdk.config.ts",
31
- "test:cov": "vitest run --config ../../../tests/config/vitest/frontend-sdk.config.ts --coverage"
32
- },
33
- "devDependencies": {
34
- "@cluebase/schema": "workspace:*",
35
- "@cluebase/shared": "workspace:*",
36
- "@vitest/coverage-v8": "^3.2.4",
37
- "jsdom": "^26.1.0",
38
- "typescript": "^5.9.3",
39
- "vitest": "^3.2.7"
40
- },
41
- "dependencies": {
42
- "@noble/hashes": "1.8.0",
43
- "@opentelemetry/api": ">=1.9.1 <1.10.0",
44
- "@opentelemetry/api-logs": ">=0.218.0 <1.0.0",
45
- "@opentelemetry/browser-instrumentation": ">=0.5.2 <1.0.0",
46
- "@opentelemetry/core": ">=2.7.1 <3.0.0",
47
- "@opentelemetry/instrumentation-document-load": ">=0.63.0 <1.0.0",
48
- "@opentelemetry/instrumentation-fetch": ">=0.218.0 <1.0.0",
49
- "@opentelemetry/instrumentation-xml-http-request": ">=0.218.0 <1.0.0",
50
- "@opentelemetry/resources": ">=2.7.1 <3.0.0",
51
- "@opentelemetry/sdk-trace-base": ">=2.7.1 <3.0.0",
52
- "@opentelemetry/sdk-trace-web": ">=2.7.1 <3.0.0",
53
- "json-canonicalize": "2.0.0",
54
- "zod": "^4.4.3"
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
+ }