@drakkar.software/sunglasses-core 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -523,9 +523,8 @@ interface ISunglassesTypedClient<T extends EventMap> extends ISunglassesClient {
523
523
  capture<K extends keyof T & string>(eventName: K, ...args: T[K] extends undefined ? [properties?: Record<string, unknown>] : [properties: T[K]]): void;
524
524
  }
525
525
  /**
526
- * Standard properties emitted by error-capturing adapters as `$error` events.
527
- * Shared by `@drakkar.software/sunglasses-adapter-sentry`,
528
- * `@drakkar.software/sunglasses-adapter-posthog`, and `SunglassesErrorBoundary`.
526
+ * Standard properties emitted by error-capturing code as `$error` events.
527
+ * Used by `captureException`, `SunglassesErrorBoundary`, and `autoCaptureErrors`.
529
528
  */
530
529
  interface ErrorEventProperties {
531
530
  $error_message: string;
@@ -1179,6 +1178,20 @@ interface CaptureExceptionOptions {
1179
1178
  * object to capture, or `null` to skip capture entirely.
1180
1179
  */
1181
1180
  beforeCapture?: (props: ErrorEventProperties) => Record<string, unknown> | null;
1181
+ /**
1182
+ * Drop errors with the same fingerprint (name + message + first stack frame)
1183
+ * captured within {@link dedupeWindowMs}. This collapses the common
1184
+ * double-capture cases — e.g. an error boundary plus `console.error` capture
1185
+ * reporting the same render error, or a global handler firing repeatedly for
1186
+ * the same throw. The fingerprint deliberately ignores the `handled` flag and
1187
+ * `$error_source`, so the first capture wins. Default: `true`.
1188
+ */
1189
+ dedupe?: boolean;
1190
+ /**
1191
+ * Time window (ms) used by {@link dedupe}. Identical errors within this window
1192
+ * are dropped. Default: `1000`.
1193
+ */
1194
+ dedupeWindowMs?: number;
1182
1195
  }
1183
1196
  /**
1184
1197
  * Normalize any thrown value into a SunGlasses `$error` event and capture it
@@ -1230,10 +1243,15 @@ interface ConsoleCaptureOptions {
1230
1243
  */
1231
1244
  interface AutoCaptureErrorsOptions extends CaptureExceptionOptions {
1232
1245
  /**
1233
- * Install the platform global error handlers (web `window` `error` /
1234
- * `unhandledrejection`, React Native `ErrorUtils`). Default: `true`.
1246
+ * Install the platform uncaught-error handlers (web `window` `'error'`,
1247
+ * React Native `ErrorUtils`). Default: `true`.
1235
1248
  */
1236
1249
  globalHandlers?: boolean;
1250
+ /**
1251
+ * Capture unhandled promise rejections (web `window` `'unhandledrejection'`,
1252
+ * React Native engine-specific rejection tracking). Default: `true`.
1253
+ */
1254
+ unhandledRejections?: boolean;
1237
1255
  /**
1238
1256
  * Also capture console output as `$error` events. `true` captures
1239
1257
  * `console.error`; pass {@link ConsoleCaptureOptions} to configure levels.
@@ -1268,6 +1286,43 @@ interface AutoCaptureErrorsOptions extends CaptureExceptionOptions {
1268
1286
  */
1269
1287
  declare function patchConsole(client: ISunglassesClient, options?: ConsoleCaptureOptions): () => void;
1270
1288
 
1289
+ /**
1290
+ * Describes a global (non-render) error surfaced by a platform global handler.
1291
+ * Published by the providers' auto-capture handlers and consumed by
1292
+ * `SunglassesGlobalErrorBoundary` to decide whether to render its fallback UI.
1293
+ *
1294
+ * The raw `error` reference is forwarded only to in-process subscribers; nothing
1295
+ * is persisted, logged, or sent anywhere by the bus itself.
1296
+ */
1297
+ interface GlobalErrorInfo {
1298
+ /** The thrown value (an `Error`, string, or arbitrary object). */
1299
+ error: unknown;
1300
+ /**
1301
+ * Whether the runtime considered the error fatal. Uncaught errors are fatal
1302
+ * unless the platform reports otherwise; unhandled promise rejections are
1303
+ * non-fatal.
1304
+ */
1305
+ fatal: boolean;
1306
+ /** Whether the error came from an uncaught error or an unhandled rejection. */
1307
+ kind: 'error' | 'rejection';
1308
+ }
1309
+ /** A subscriber notified for every published {@link GlobalErrorInfo}. */
1310
+ type GlobalErrorListener = (info: GlobalErrorInfo) => void;
1311
+ /**
1312
+ * Publish a global error to all current subscribers. Subscriber errors are
1313
+ * swallowed so one faulty listener cannot break others or the caller.
1314
+ *
1315
+ * @param info - The global error descriptor.
1316
+ */
1317
+ declare function publishGlobalError(info: GlobalErrorInfo): void;
1318
+ /**
1319
+ * Subscribe to global errors published via {@link publishGlobalError}.
1320
+ *
1321
+ * @param listener - Called for each published error.
1322
+ * @returns An unsubscribe function.
1323
+ */
1324
+ declare function subscribeGlobalError(listener: GlobalErrorListener): () => void;
1325
+
1271
1326
  /**
1272
1327
  * A typed analytics stub that is safe to use before the SDK initialises.
1273
1328
  *
@@ -1340,4 +1395,4 @@ declare function sha256Hex(input: string): Promise<string>;
1340
1395
  */
1341
1396
  declare function nowISO(): string;
1342
1397
 
1343
- export { type AppMetadata, type AppUpdateInfo, type AutoCaptureErrorsOptions, type CaptureExceptionOptions, type CleanupConfig, type ConsentHistoryEntry, ConsentManager, type ConsentState, type ConsentStatus, type ConsoleCaptureOptions, type ConsoleLevel, type ErrorEventProperties, type EventContext, type EventCountPeriod, EventCounter, type EventMap, EventQueue, type EventType, FrequencyMiddleware, type FrequencyMiddlewareOptions, type HttpAdapterConfig, type IAnalyticsAdapter, type IEventCounter, type IMiddleware, type IStorageAdapter, type ISunglassesClient, type ISunglassesTypedClient, IdentityManager, type IdentityState, LocalEventArchive, type Logger, type MiddlewareNext, MiddlewarePipeline, PiiSanitizer, SamplingMiddleware, type SamplingMiddlewareOptions, type ScreenTrackingOptions, SessionManager, type SessionState, type SunglassesConfig, SunglassesCore, type SunglassesEvent, TraitManager, type UserDataExport, asTyped, captureException, createLazyClient, createLogger, generateUUID, nowISO, patchConsole, sha256Hex };
1398
+ export { type AppMetadata, type AppUpdateInfo, type AutoCaptureErrorsOptions, type CaptureExceptionOptions, type CleanupConfig, type ConsentHistoryEntry, ConsentManager, type ConsentState, type ConsentStatus, type ConsoleCaptureOptions, type ConsoleLevel, type ErrorEventProperties, type EventContext, type EventCountPeriod, EventCounter, type EventMap, EventQueue, type EventType, FrequencyMiddleware, type FrequencyMiddlewareOptions, type GlobalErrorInfo, type GlobalErrorListener, type HttpAdapterConfig, type IAnalyticsAdapter, type IEventCounter, type IMiddleware, type IStorageAdapter, type ISunglassesClient, type ISunglassesTypedClient, IdentityManager, type IdentityState, LocalEventArchive, type Logger, type MiddlewareNext, MiddlewarePipeline, PiiSanitizer, SamplingMiddleware, type SamplingMiddlewareOptions, type ScreenTrackingOptions, SessionManager, type SessionState, type SunglassesConfig, SunglassesCore, type SunglassesEvent, TraitManager, type UserDataExport, asTyped, captureException, createLazyClient, createLogger, generateUUID, nowISO, patchConsole, publishGlobalError, sha256Hex, subscribeGlobalError };
package/dist/index.d.ts CHANGED
@@ -523,9 +523,8 @@ interface ISunglassesTypedClient<T extends EventMap> extends ISunglassesClient {
523
523
  capture<K extends keyof T & string>(eventName: K, ...args: T[K] extends undefined ? [properties?: Record<string, unknown>] : [properties: T[K]]): void;
524
524
  }
525
525
  /**
526
- * Standard properties emitted by error-capturing adapters as `$error` events.
527
- * Shared by `@drakkar.software/sunglasses-adapter-sentry`,
528
- * `@drakkar.software/sunglasses-adapter-posthog`, and `SunglassesErrorBoundary`.
526
+ * Standard properties emitted by error-capturing code as `$error` events.
527
+ * Used by `captureException`, `SunglassesErrorBoundary`, and `autoCaptureErrors`.
529
528
  */
530
529
  interface ErrorEventProperties {
531
530
  $error_message: string;
@@ -1179,6 +1178,20 @@ interface CaptureExceptionOptions {
1179
1178
  * object to capture, or `null` to skip capture entirely.
1180
1179
  */
1181
1180
  beforeCapture?: (props: ErrorEventProperties) => Record<string, unknown> | null;
1181
+ /**
1182
+ * Drop errors with the same fingerprint (name + message + first stack frame)
1183
+ * captured within {@link dedupeWindowMs}. This collapses the common
1184
+ * double-capture cases — e.g. an error boundary plus `console.error` capture
1185
+ * reporting the same render error, or a global handler firing repeatedly for
1186
+ * the same throw. The fingerprint deliberately ignores the `handled` flag and
1187
+ * `$error_source`, so the first capture wins. Default: `true`.
1188
+ */
1189
+ dedupe?: boolean;
1190
+ /**
1191
+ * Time window (ms) used by {@link dedupe}. Identical errors within this window
1192
+ * are dropped. Default: `1000`.
1193
+ */
1194
+ dedupeWindowMs?: number;
1182
1195
  }
1183
1196
  /**
1184
1197
  * Normalize any thrown value into a SunGlasses `$error` event and capture it
@@ -1230,10 +1243,15 @@ interface ConsoleCaptureOptions {
1230
1243
  */
1231
1244
  interface AutoCaptureErrorsOptions extends CaptureExceptionOptions {
1232
1245
  /**
1233
- * Install the platform global error handlers (web `window` `error` /
1234
- * `unhandledrejection`, React Native `ErrorUtils`). Default: `true`.
1246
+ * Install the platform uncaught-error handlers (web `window` `'error'`,
1247
+ * React Native `ErrorUtils`). Default: `true`.
1235
1248
  */
1236
1249
  globalHandlers?: boolean;
1250
+ /**
1251
+ * Capture unhandled promise rejections (web `window` `'unhandledrejection'`,
1252
+ * React Native engine-specific rejection tracking). Default: `true`.
1253
+ */
1254
+ unhandledRejections?: boolean;
1237
1255
  /**
1238
1256
  * Also capture console output as `$error` events. `true` captures
1239
1257
  * `console.error`; pass {@link ConsoleCaptureOptions} to configure levels.
@@ -1268,6 +1286,43 @@ interface AutoCaptureErrorsOptions extends CaptureExceptionOptions {
1268
1286
  */
1269
1287
  declare function patchConsole(client: ISunglassesClient, options?: ConsoleCaptureOptions): () => void;
1270
1288
 
1289
+ /**
1290
+ * Describes a global (non-render) error surfaced by a platform global handler.
1291
+ * Published by the providers' auto-capture handlers and consumed by
1292
+ * `SunglassesGlobalErrorBoundary` to decide whether to render its fallback UI.
1293
+ *
1294
+ * The raw `error` reference is forwarded only to in-process subscribers; nothing
1295
+ * is persisted, logged, or sent anywhere by the bus itself.
1296
+ */
1297
+ interface GlobalErrorInfo {
1298
+ /** The thrown value (an `Error`, string, or arbitrary object). */
1299
+ error: unknown;
1300
+ /**
1301
+ * Whether the runtime considered the error fatal. Uncaught errors are fatal
1302
+ * unless the platform reports otherwise; unhandled promise rejections are
1303
+ * non-fatal.
1304
+ */
1305
+ fatal: boolean;
1306
+ /** Whether the error came from an uncaught error or an unhandled rejection. */
1307
+ kind: 'error' | 'rejection';
1308
+ }
1309
+ /** A subscriber notified for every published {@link GlobalErrorInfo}. */
1310
+ type GlobalErrorListener = (info: GlobalErrorInfo) => void;
1311
+ /**
1312
+ * Publish a global error to all current subscribers. Subscriber errors are
1313
+ * swallowed so one faulty listener cannot break others or the caller.
1314
+ *
1315
+ * @param info - The global error descriptor.
1316
+ */
1317
+ declare function publishGlobalError(info: GlobalErrorInfo): void;
1318
+ /**
1319
+ * Subscribe to global errors published via {@link publishGlobalError}.
1320
+ *
1321
+ * @param listener - Called for each published error.
1322
+ * @returns An unsubscribe function.
1323
+ */
1324
+ declare function subscribeGlobalError(listener: GlobalErrorListener): () => void;
1325
+
1271
1326
  /**
1272
1327
  * A typed analytics stub that is safe to use before the SDK initialises.
1273
1328
  *
@@ -1340,4 +1395,4 @@ declare function sha256Hex(input: string): Promise<string>;
1340
1395
  */
1341
1396
  declare function nowISO(): string;
1342
1397
 
1343
- export { type AppMetadata, type AppUpdateInfo, type AutoCaptureErrorsOptions, type CaptureExceptionOptions, type CleanupConfig, type ConsentHistoryEntry, ConsentManager, type ConsentState, type ConsentStatus, type ConsoleCaptureOptions, type ConsoleLevel, type ErrorEventProperties, type EventContext, type EventCountPeriod, EventCounter, type EventMap, EventQueue, type EventType, FrequencyMiddleware, type FrequencyMiddlewareOptions, type HttpAdapterConfig, type IAnalyticsAdapter, type IEventCounter, type IMiddleware, type IStorageAdapter, type ISunglassesClient, type ISunglassesTypedClient, IdentityManager, type IdentityState, LocalEventArchive, type Logger, type MiddlewareNext, MiddlewarePipeline, PiiSanitizer, SamplingMiddleware, type SamplingMiddlewareOptions, type ScreenTrackingOptions, SessionManager, type SessionState, type SunglassesConfig, SunglassesCore, type SunglassesEvent, TraitManager, type UserDataExport, asTyped, captureException, createLazyClient, createLogger, generateUUID, nowISO, patchConsole, sha256Hex };
1398
+ export { type AppMetadata, type AppUpdateInfo, type AutoCaptureErrorsOptions, type CaptureExceptionOptions, type CleanupConfig, type ConsentHistoryEntry, ConsentManager, type ConsentState, type ConsentStatus, type ConsoleCaptureOptions, type ConsoleLevel, type ErrorEventProperties, type EventContext, type EventCountPeriod, EventCounter, type EventMap, EventQueue, type EventType, FrequencyMiddleware, type FrequencyMiddlewareOptions, type GlobalErrorInfo, type GlobalErrorListener, type HttpAdapterConfig, type IAnalyticsAdapter, type IEventCounter, type IMiddleware, type IStorageAdapter, type ISunglassesClient, type ISunglassesTypedClient, IdentityManager, type IdentityState, LocalEventArchive, type Logger, type MiddlewareNext, MiddlewarePipeline, PiiSanitizer, SamplingMiddleware, type SamplingMiddlewareOptions, type ScreenTrackingOptions, SessionManager, type SessionState, type SunglassesConfig, SunglassesCore, type SunglassesEvent, TraitManager, type UserDataExport, asTyped, captureException, createLazyClient, createLogger, generateUUID, nowISO, patchConsole, publishGlobalError, sha256Hex, subscribeGlobalError };
package/dist/index.js CHANGED
@@ -39,7 +39,9 @@ __export(index_exports, {
39
39
  generateUUID: () => generateUUID,
40
40
  nowISO: () => nowISO,
41
41
  patchConsole: () => patchConsole,
42
- sha256Hex: () => sha256Hex
42
+ publishGlobalError: () => publishGlobalError,
43
+ sha256Hex: () => sha256Hex,
44
+ subscribeGlobalError: () => subscribeGlobalError
43
45
  });
44
46
  module.exports = __toCommonJS(index_exports);
45
47
 
@@ -1612,6 +1614,22 @@ function extractStack(stack, maxFrames) {
1612
1614
  }
1613
1615
  return void 0;
1614
1616
  }
1617
+ var dedupeStore = /* @__PURE__ */ new WeakMap();
1618
+ function isDuplicate(client, fingerprint, windowMs) {
1619
+ const now = Date.now();
1620
+ let seen = dedupeStore.get(client);
1621
+ if (!seen) {
1622
+ seen = /* @__PURE__ */ new Map();
1623
+ dedupeStore.set(client, seen);
1624
+ }
1625
+ for (const [fp, ts] of seen) {
1626
+ if (now - ts > windowMs) seen.delete(fp);
1627
+ }
1628
+ const last = seen.get(fingerprint);
1629
+ if (last !== void 0 && now - last <= windowMs) return true;
1630
+ seen.set(fingerprint, now);
1631
+ return false;
1632
+ }
1615
1633
  function captureException(client, error, options = {}) {
1616
1634
  const {
1617
1635
  handled = true,
@@ -1621,11 +1639,18 @@ function captureException(client, error, options = {}) {
1621
1639
  maxMessageLength = 200,
1622
1640
  ignorePatterns = [],
1623
1641
  properties,
1624
- beforeCapture
1642
+ beforeCapture,
1643
+ dedupe = true,
1644
+ dedupeWindowMs = 1e3
1625
1645
  } = options;
1626
1646
  const normalized = normalizeError(error);
1627
1647
  const rawMessage = normalized.message;
1628
1648
  if (ignorePatterns.some((p) => p.test(rawMessage))) return;
1649
+ if (dedupe) {
1650
+ const firstFrame = extractStack(normalized.stack, 1) ?? "";
1651
+ const fingerprint = `${normalized.name}|${rawMessage}|${firstFrame}`;
1652
+ if (isDuplicate(client, fingerprint, dedupeWindowMs)) return;
1653
+ }
1629
1654
  let props = {
1630
1655
  ...properties,
1631
1656
  $error_message: rawMessage.slice(0, maxMessageLength),
@@ -1705,6 +1730,23 @@ function patchConsole(client, options = {}) {
1705
1730
  };
1706
1731
  }
1707
1732
 
1733
+ // src/errorBus.ts
1734
+ var listeners = /* @__PURE__ */ new Set();
1735
+ function publishGlobalError(info) {
1736
+ for (const listener of listeners) {
1737
+ try {
1738
+ listener(info);
1739
+ } catch {
1740
+ }
1741
+ }
1742
+ }
1743
+ function subscribeGlobalError(listener) {
1744
+ listeners.add(listener);
1745
+ return () => {
1746
+ listeners.delete(listener);
1747
+ };
1748
+ }
1749
+
1708
1750
  // src/LazyClient.ts
1709
1751
  function createLazyClient() {
1710
1752
  let _inner = null;
@@ -1843,5 +1885,7 @@ function createLazyClient() {
1843
1885
  generateUUID,
1844
1886
  nowISO,
1845
1887
  patchConsole,
1846
- sha256Hex
1888
+ publishGlobalError,
1889
+ sha256Hex,
1890
+ subscribeGlobalError
1847
1891
  });
package/dist/index.mjs CHANGED
@@ -1567,6 +1567,22 @@ function extractStack(stack, maxFrames) {
1567
1567
  }
1568
1568
  return void 0;
1569
1569
  }
1570
+ var dedupeStore = /* @__PURE__ */ new WeakMap();
1571
+ function isDuplicate(client, fingerprint, windowMs) {
1572
+ const now = Date.now();
1573
+ let seen = dedupeStore.get(client);
1574
+ if (!seen) {
1575
+ seen = /* @__PURE__ */ new Map();
1576
+ dedupeStore.set(client, seen);
1577
+ }
1578
+ for (const [fp, ts] of seen) {
1579
+ if (now - ts > windowMs) seen.delete(fp);
1580
+ }
1581
+ const last = seen.get(fingerprint);
1582
+ if (last !== void 0 && now - last <= windowMs) return true;
1583
+ seen.set(fingerprint, now);
1584
+ return false;
1585
+ }
1570
1586
  function captureException(client, error, options = {}) {
1571
1587
  const {
1572
1588
  handled = true,
@@ -1576,11 +1592,18 @@ function captureException(client, error, options = {}) {
1576
1592
  maxMessageLength = 200,
1577
1593
  ignorePatterns = [],
1578
1594
  properties,
1579
- beforeCapture
1595
+ beforeCapture,
1596
+ dedupe = true,
1597
+ dedupeWindowMs = 1e3
1580
1598
  } = options;
1581
1599
  const normalized = normalizeError(error);
1582
1600
  const rawMessage = normalized.message;
1583
1601
  if (ignorePatterns.some((p) => p.test(rawMessage))) return;
1602
+ if (dedupe) {
1603
+ const firstFrame = extractStack(normalized.stack, 1) ?? "";
1604
+ const fingerprint = `${normalized.name}|${rawMessage}|${firstFrame}`;
1605
+ if (isDuplicate(client, fingerprint, dedupeWindowMs)) return;
1606
+ }
1584
1607
  let props = {
1585
1608
  ...properties,
1586
1609
  $error_message: rawMessage.slice(0, maxMessageLength),
@@ -1660,6 +1683,23 @@ function patchConsole(client, options = {}) {
1660
1683
  };
1661
1684
  }
1662
1685
 
1686
+ // src/errorBus.ts
1687
+ var listeners = /* @__PURE__ */ new Set();
1688
+ function publishGlobalError(info) {
1689
+ for (const listener of listeners) {
1690
+ try {
1691
+ listener(info);
1692
+ } catch {
1693
+ }
1694
+ }
1695
+ }
1696
+ function subscribeGlobalError(listener) {
1697
+ listeners.add(listener);
1698
+ return () => {
1699
+ listeners.delete(listener);
1700
+ };
1701
+ }
1702
+
1663
1703
  // src/LazyClient.ts
1664
1704
  function createLazyClient() {
1665
1705
  let _inner = null;
@@ -1797,5 +1837,7 @@ export {
1797
1837
  generateUUID,
1798
1838
  nowISO,
1799
1839
  patchConsole,
1800
- sha256Hex
1840
+ publishGlobalError,
1841
+ sha256Hex,
1842
+ subscribeGlobalError
1801
1843
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakkar.software/sunglasses-core",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Platform-agnostic event tracking engine for SunGlasses",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",