@drakkar.software/sunglasses-core 0.8.0 → 0.11.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
@@ -55,6 +55,39 @@ interface IMiddleware {
55
55
  process(event: SunglassesEvent, next: MiddlewareNext): Promise<SunglassesEvent | null>;
56
56
  }
57
57
  type EventType = 'capture' | 'screen' | 'identify' | 'alias' | 'group';
58
+ /**
59
+ * Over-the-air (OTA) / app update information, e.g. an Expo EAS Update or a web
60
+ * deploy identifier. Useful for correlating events with a specific shipped bundle.
61
+ */
62
+ interface AppUpdateInfo {
63
+ /** Unique identifier of the applied update (e.g. EAS update ID). */
64
+ id?: string;
65
+ /** Release channel the update was published to (e.g. 'production'). */
66
+ channel?: string;
67
+ /** Runtime/native version the update is compatible with. */
68
+ runtimeVersion?: string;
69
+ /** True when running the bundle shipped in the binary (no OTA applied). */
70
+ embedded?: boolean;
71
+ /** ISO-8601 timestamp of when the update was created/published. */
72
+ createdAt?: string;
73
+ }
74
+ /**
75
+ * Mutable global metadata attached to every event's `context`. Set via
76
+ * `SunglassesConfig` at init and updatable at runtime. In-memory only — the host
77
+ * app must re-supply these values on each boot.
78
+ */
79
+ interface AppMetadata {
80
+ /** Deployment environment, e.g. 'production' | 'staging' | 'development'. */
81
+ environment?: string;
82
+ /** App variant / build flavor, e.g. 'pro' | 'lite' | 'beta'. */
83
+ appVariant?: string;
84
+ /** OTA / app update info for the currently running bundle. */
85
+ appUpdate?: AppUpdateInfo;
86
+ /** Enabled features / experiment variants (app-scoped). */
87
+ features?: string[];
88
+ /** Active user entitlements (user-scoped, cleared on reset()). */
89
+ entitlements?: string[];
90
+ }
58
91
  interface EventContext {
59
92
  library: {
60
93
  name: string;
@@ -65,6 +98,8 @@ interface EventContext {
65
98
  name?: string;
66
99
  version?: string;
67
100
  build?: string;
101
+ variant?: string;
102
+ update?: AppUpdateInfo;
68
103
  };
69
104
  device?: {
70
105
  type?: string;
@@ -75,6 +110,12 @@ interface EventContext {
75
110
  height?: number;
76
111
  };
77
112
  locale?: string;
113
+ /** Deployment environment, e.g. 'production' | 'staging' | 'development'. */
114
+ environment?: string;
115
+ /** Enabled features / experiment variants (app-scoped). */
116
+ features?: string[];
117
+ /** Active user entitlements (user-scoped). */
118
+ entitlements?: string[];
78
119
  /** Current session ID. Present when enableSessionTracking is true. */
79
120
  sessionId?: string;
80
121
  /** Persisted user traits set via identify(). Forwarded to backends for segmentation. */
@@ -171,6 +212,16 @@ interface SunglassesConfig {
171
212
  appName?: string;
172
213
  appVersion?: string;
173
214
  appBuild?: string;
215
+ /** App variant / build flavor, e.g. 'pro' | 'lite' | 'beta'. Attached to context.app.variant. */
216
+ appVariant?: string;
217
+ /** OTA / app update info for the currently running bundle. Attached to context.app.update. */
218
+ appUpdate?: AppUpdateInfo;
219
+ /** Deployment environment, e.g. 'production' | 'staging'. Attached to context.environment. */
220
+ environment?: string;
221
+ /** Enabled features / experiment variants (app-scoped). Attached to context.features. */
222
+ features?: string[];
223
+ /** Active user entitlements (user-scoped). Attached to context.entitlements. */
224
+ entitlements?: string[];
174
225
  /** Enables verbose console logging. Never enable in production. */
175
226
  debug?: boolean;
176
227
  /** Hard-disables all tracking (e.g. CI environments, test suites). */
@@ -269,6 +320,34 @@ interface ISunglassesClient {
269
320
  unregister(...keys: string[]): void;
270
321
  /** Returns a snapshot of all currently registered super properties. */
271
322
  getRegisteredProperties(): Record<string, unknown>;
323
+ /**
324
+ * Set the deployment environment (e.g. 'production', 'staging'). Attached to
325
+ * every subsequent event's `context.environment`. In-memory only.
326
+ */
327
+ setEnvironment(environment: string): void;
328
+ /**
329
+ * Set OTA / app update info for the currently running bundle. Attached to
330
+ * every subsequent event's `context.app.update`. In-memory only.
331
+ */
332
+ setAppUpdate(update: AppUpdateInfo): void;
333
+ /**
334
+ * Set the list of enabled features / experiment variants (app-scoped).
335
+ * Attached to every subsequent event's `context.features`. In-memory only.
336
+ */
337
+ setFeatures(features: string[]): void;
338
+ /**
339
+ * Set the active user entitlements (user-scoped). Attached to every subsequent
340
+ * event's `context.entitlements`. Cleared by `reset()` and `deleteUserData()`.
341
+ * In-memory only.
342
+ */
343
+ setEntitlements(entitlements: string[]): void;
344
+ /**
345
+ * Merge a partial set of global app metadata. Only the provided keys are
346
+ * updated; omitted keys keep their current value. In-memory only.
347
+ */
348
+ setAppMetadata(meta: Partial<AppMetadata>): void;
349
+ /** Returns a snapshot of the current global app metadata. */
350
+ getAppMetadata(): AppMetadata;
272
351
  optIn(): Promise<void>;
273
352
  optOut(): Promise<void>;
274
353
  hasOptedIn(): boolean;
@@ -498,6 +577,12 @@ declare class SunglassesCore implements ISunglassesClient {
498
577
  private readonly superProperties;
499
578
  /** In-memory group ID attached to every event's context after group() is called. */
500
579
  private groupId;
580
+ /** In-memory global app metadata attached to every event's context. */
581
+ private environment;
582
+ private appVariant;
583
+ private appUpdate;
584
+ private features;
585
+ private entitlements;
501
586
  private constructor();
502
587
  /**
503
588
  * Create and initialize a SunglassesCore instance.
@@ -512,6 +597,12 @@ declare class SunglassesCore implements ISunglassesClient {
512
597
  register(properties: Record<string, unknown>): void;
513
598
  unregister(...keys: string[]): void;
514
599
  getRegisteredProperties(): Record<string, unknown>;
600
+ setEnvironment(environment: string): void;
601
+ setAppUpdate(update: AppUpdateInfo): void;
602
+ setFeatures(features: string[]): void;
603
+ setEntitlements(entitlements: string[]): void;
604
+ setAppMetadata(meta: Partial<AppMetadata>): void;
605
+ getAppMetadata(): AppMetadata;
515
606
  reset(): Promise<void>;
516
607
  optIn(): Promise<void>;
517
608
  optOut(): Promise<void>;
@@ -1045,6 +1136,138 @@ declare class LocalEventArchive {
1045
1136
  */
1046
1137
  declare function asTyped<T extends EventMap>(client: ISunglassesClient): ISunglassesTypedClient<T>;
1047
1138
 
1139
+ /**
1140
+ * Options for {@link captureException}. Mirrors the privacy-safe defaults used
1141
+ * by the error-capturing adapters so behaviour is consistent everywhere.
1142
+ */
1143
+ interface CaptureExceptionOptions {
1144
+ /**
1145
+ * Whether the error was handled (caught by an error boundary / try-catch) or
1146
+ * unhandled (surfaced by a global error handler). Default: `true`.
1147
+ */
1148
+ handled?: boolean;
1149
+ /** Sentry-compatible severity level. Default: `'error'`. */
1150
+ level?: string;
1151
+ /**
1152
+ * Include the stack trace in `$error_stack`. Default: `false` (privacy-safe).
1153
+ * Stack traces may expose internal file paths and function names.
1154
+ */
1155
+ includeStack?: boolean;
1156
+ /**
1157
+ * Maximum number of stack frames to include when `includeStack` is `true`.
1158
+ * Default: `5`.
1159
+ */
1160
+ maxStackFrames?: number;
1161
+ /**
1162
+ * Truncate error messages to this many characters. Default: `200`.
1163
+ * Error messages sometimes contain user data ("User foo@bar.com not found").
1164
+ */
1165
+ maxMessageLength?: number;
1166
+ /**
1167
+ * Skip errors whose message matches any of these patterns.
1168
+ * Pattern is tested against the raw (pre-truncation) message.
1169
+ */
1170
+ ignorePatterns?: RegExp[];
1171
+ /**
1172
+ * Extra properties merged into the captured `$error` event. Lower precedence
1173
+ * than the computed `$error_*` properties.
1174
+ */
1175
+ properties?: Record<string, unknown>;
1176
+ /**
1177
+ * Optional transform applied before `client.capture()`.
1178
+ * Receives typed `ErrorEventProperties`; return a (possibly extended) props
1179
+ * object to capture, or `null` to skip capture entirely.
1180
+ */
1181
+ beforeCapture?: (props: ErrorEventProperties) => Record<string, unknown> | null;
1182
+ }
1183
+ /**
1184
+ * Normalize any thrown value into a SunGlasses `$error` event and capture it
1185
+ * via `client.capture()`.
1186
+ *
1187
+ * This is the single source of error-event construction shared by the built-in
1188
+ * error boundaries and the provider global error handlers. It never throws and
1189
+ * respects consent automatically (capture is consent-gated in the core).
1190
+ *
1191
+ * @param client - SunGlasses client instance.
1192
+ * @param error - The thrown value (an `Error`, string, or arbitrary object).
1193
+ * @param options - Optional capture configuration.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * try {
1198
+ * doRiskyThing();
1199
+ * } catch (err) {
1200
+ * captureException(client, err); // $error_handled: true
1201
+ * }
1202
+ * ```
1203
+ */
1204
+ declare function captureException(client: ISunglassesClient, error: unknown, options?: CaptureExceptionOptions): void;
1205
+
1206
+ /** Console methods that can be captured. */
1207
+ type ConsoleLevel = 'error' | 'warn';
1208
+ /**
1209
+ * Options for {@link patchConsole}.
1210
+ */
1211
+ interface ConsoleCaptureOptions {
1212
+ /** Console methods to capture. Default: `['error']`. */
1213
+ levels?: ConsoleLevel[];
1214
+ /**
1215
+ * Skip console output whose composed message matches any of these patterns.
1216
+ * Pattern is tested against the raw (pre-truncation) message.
1217
+ */
1218
+ ignorePatterns?: RegExp[];
1219
+ /** Truncate the composed message to this many characters. Default: `200`. */
1220
+ maxMessageLength?: number;
1221
+ /** Include a stack trace in `$error_stack`. Default: `false` (privacy-safe). */
1222
+ includeStack?: boolean;
1223
+ /** Extra properties merged into every captured `$error` event. */
1224
+ properties?: Record<string, unknown>;
1225
+ }
1226
+ /**
1227
+ * Configuration for the providers' `autoCaptureErrors` option. Extends
1228
+ * {@link CaptureExceptionOptions} (applied to unhandled global errors) with
1229
+ * toggles for the global handlers and console capture.
1230
+ */
1231
+ interface AutoCaptureErrorsOptions extends CaptureExceptionOptions {
1232
+ /**
1233
+ * Install the platform global error handlers (web `window` `error` /
1234
+ * `unhandledrejection`, React Native `ErrorUtils`). Default: `true`.
1235
+ */
1236
+ globalHandlers?: boolean;
1237
+ /**
1238
+ * Also capture console output as `$error` events. `true` captures
1239
+ * `console.error`; pass {@link ConsoleCaptureOptions} to configure levels.
1240
+ * Default: off.
1241
+ */
1242
+ console?: boolean | ConsoleCaptureOptions;
1243
+ }
1244
+ /**
1245
+ * Patch the global `console` so that `console.error` / `console.warn` (per
1246
+ * `levels`) are also captured as SunGlasses `$error` events
1247
+ * (`$error_handled: false`, `$error_source: 'console'`).
1248
+ *
1249
+ * Works identically on web and React Native — both expose a global `console`.
1250
+ * The original method is always called first, so logs still appear in the
1251
+ * console. Returns an unpatch function that restores the original methods.
1252
+ *
1253
+ * Guards against infinite recursion: SunGlasses' own logger writes to
1254
+ * `console.error`/`console.warn` (prefixed with `[SunGlasses]`), and
1255
+ * `client.capture()` may log on failure. A re-entrancy flag plus a prefix skip
1256
+ * prevent a capture -> log -> capture loop.
1257
+ *
1258
+ * @param client - SunGlasses client instance.
1259
+ * @param options - Optional capture configuration.
1260
+ * @returns A function that restores the original console methods.
1261
+ *
1262
+ * @example
1263
+ * ```ts
1264
+ * const unpatch = patchConsole(client, { levels: ['error', 'warn'] });
1265
+ * // ... later
1266
+ * unpatch();
1267
+ * ```
1268
+ */
1269
+ declare function patchConsole(client: ISunglassesClient, options?: ConsoleCaptureOptions): () => void;
1270
+
1048
1271
  /**
1049
1272
  * A typed analytics stub that is safe to use before the SDK initialises.
1050
1273
  *
@@ -1117,4 +1340,4 @@ declare function sha256Hex(input: string): Promise<string>;
1117
1340
  */
1118
1341
  declare function nowISO(): string;
1119
1342
 
1120
- export { type CleanupConfig, type ConsentHistoryEntry, ConsentManager, type ConsentState, type ConsentStatus, 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, createLazyClient, createLogger, generateUUID, nowISO, sha256Hex };
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 };
package/dist/index.d.ts CHANGED
@@ -55,6 +55,39 @@ interface IMiddleware {
55
55
  process(event: SunglassesEvent, next: MiddlewareNext): Promise<SunglassesEvent | null>;
56
56
  }
57
57
  type EventType = 'capture' | 'screen' | 'identify' | 'alias' | 'group';
58
+ /**
59
+ * Over-the-air (OTA) / app update information, e.g. an Expo EAS Update or a web
60
+ * deploy identifier. Useful for correlating events with a specific shipped bundle.
61
+ */
62
+ interface AppUpdateInfo {
63
+ /** Unique identifier of the applied update (e.g. EAS update ID). */
64
+ id?: string;
65
+ /** Release channel the update was published to (e.g. 'production'). */
66
+ channel?: string;
67
+ /** Runtime/native version the update is compatible with. */
68
+ runtimeVersion?: string;
69
+ /** True when running the bundle shipped in the binary (no OTA applied). */
70
+ embedded?: boolean;
71
+ /** ISO-8601 timestamp of when the update was created/published. */
72
+ createdAt?: string;
73
+ }
74
+ /**
75
+ * Mutable global metadata attached to every event's `context`. Set via
76
+ * `SunglassesConfig` at init and updatable at runtime. In-memory only — the host
77
+ * app must re-supply these values on each boot.
78
+ */
79
+ interface AppMetadata {
80
+ /** Deployment environment, e.g. 'production' | 'staging' | 'development'. */
81
+ environment?: string;
82
+ /** App variant / build flavor, e.g. 'pro' | 'lite' | 'beta'. */
83
+ appVariant?: string;
84
+ /** OTA / app update info for the currently running bundle. */
85
+ appUpdate?: AppUpdateInfo;
86
+ /** Enabled features / experiment variants (app-scoped). */
87
+ features?: string[];
88
+ /** Active user entitlements (user-scoped, cleared on reset()). */
89
+ entitlements?: string[];
90
+ }
58
91
  interface EventContext {
59
92
  library: {
60
93
  name: string;
@@ -65,6 +98,8 @@ interface EventContext {
65
98
  name?: string;
66
99
  version?: string;
67
100
  build?: string;
101
+ variant?: string;
102
+ update?: AppUpdateInfo;
68
103
  };
69
104
  device?: {
70
105
  type?: string;
@@ -75,6 +110,12 @@ interface EventContext {
75
110
  height?: number;
76
111
  };
77
112
  locale?: string;
113
+ /** Deployment environment, e.g. 'production' | 'staging' | 'development'. */
114
+ environment?: string;
115
+ /** Enabled features / experiment variants (app-scoped). */
116
+ features?: string[];
117
+ /** Active user entitlements (user-scoped). */
118
+ entitlements?: string[];
78
119
  /** Current session ID. Present when enableSessionTracking is true. */
79
120
  sessionId?: string;
80
121
  /** Persisted user traits set via identify(). Forwarded to backends for segmentation. */
@@ -171,6 +212,16 @@ interface SunglassesConfig {
171
212
  appName?: string;
172
213
  appVersion?: string;
173
214
  appBuild?: string;
215
+ /** App variant / build flavor, e.g. 'pro' | 'lite' | 'beta'. Attached to context.app.variant. */
216
+ appVariant?: string;
217
+ /** OTA / app update info for the currently running bundle. Attached to context.app.update. */
218
+ appUpdate?: AppUpdateInfo;
219
+ /** Deployment environment, e.g. 'production' | 'staging'. Attached to context.environment. */
220
+ environment?: string;
221
+ /** Enabled features / experiment variants (app-scoped). Attached to context.features. */
222
+ features?: string[];
223
+ /** Active user entitlements (user-scoped). Attached to context.entitlements. */
224
+ entitlements?: string[];
174
225
  /** Enables verbose console logging. Never enable in production. */
175
226
  debug?: boolean;
176
227
  /** Hard-disables all tracking (e.g. CI environments, test suites). */
@@ -269,6 +320,34 @@ interface ISunglassesClient {
269
320
  unregister(...keys: string[]): void;
270
321
  /** Returns a snapshot of all currently registered super properties. */
271
322
  getRegisteredProperties(): Record<string, unknown>;
323
+ /**
324
+ * Set the deployment environment (e.g. 'production', 'staging'). Attached to
325
+ * every subsequent event's `context.environment`. In-memory only.
326
+ */
327
+ setEnvironment(environment: string): void;
328
+ /**
329
+ * Set OTA / app update info for the currently running bundle. Attached to
330
+ * every subsequent event's `context.app.update`. In-memory only.
331
+ */
332
+ setAppUpdate(update: AppUpdateInfo): void;
333
+ /**
334
+ * Set the list of enabled features / experiment variants (app-scoped).
335
+ * Attached to every subsequent event's `context.features`. In-memory only.
336
+ */
337
+ setFeatures(features: string[]): void;
338
+ /**
339
+ * Set the active user entitlements (user-scoped). Attached to every subsequent
340
+ * event's `context.entitlements`. Cleared by `reset()` and `deleteUserData()`.
341
+ * In-memory only.
342
+ */
343
+ setEntitlements(entitlements: string[]): void;
344
+ /**
345
+ * Merge a partial set of global app metadata. Only the provided keys are
346
+ * updated; omitted keys keep their current value. In-memory only.
347
+ */
348
+ setAppMetadata(meta: Partial<AppMetadata>): void;
349
+ /** Returns a snapshot of the current global app metadata. */
350
+ getAppMetadata(): AppMetadata;
272
351
  optIn(): Promise<void>;
273
352
  optOut(): Promise<void>;
274
353
  hasOptedIn(): boolean;
@@ -498,6 +577,12 @@ declare class SunglassesCore implements ISunglassesClient {
498
577
  private readonly superProperties;
499
578
  /** In-memory group ID attached to every event's context after group() is called. */
500
579
  private groupId;
580
+ /** In-memory global app metadata attached to every event's context. */
581
+ private environment;
582
+ private appVariant;
583
+ private appUpdate;
584
+ private features;
585
+ private entitlements;
501
586
  private constructor();
502
587
  /**
503
588
  * Create and initialize a SunglassesCore instance.
@@ -512,6 +597,12 @@ declare class SunglassesCore implements ISunglassesClient {
512
597
  register(properties: Record<string, unknown>): void;
513
598
  unregister(...keys: string[]): void;
514
599
  getRegisteredProperties(): Record<string, unknown>;
600
+ setEnvironment(environment: string): void;
601
+ setAppUpdate(update: AppUpdateInfo): void;
602
+ setFeatures(features: string[]): void;
603
+ setEntitlements(entitlements: string[]): void;
604
+ setAppMetadata(meta: Partial<AppMetadata>): void;
605
+ getAppMetadata(): AppMetadata;
515
606
  reset(): Promise<void>;
516
607
  optIn(): Promise<void>;
517
608
  optOut(): Promise<void>;
@@ -1045,6 +1136,138 @@ declare class LocalEventArchive {
1045
1136
  */
1046
1137
  declare function asTyped<T extends EventMap>(client: ISunglassesClient): ISunglassesTypedClient<T>;
1047
1138
 
1139
+ /**
1140
+ * Options for {@link captureException}. Mirrors the privacy-safe defaults used
1141
+ * by the error-capturing adapters so behaviour is consistent everywhere.
1142
+ */
1143
+ interface CaptureExceptionOptions {
1144
+ /**
1145
+ * Whether the error was handled (caught by an error boundary / try-catch) or
1146
+ * unhandled (surfaced by a global error handler). Default: `true`.
1147
+ */
1148
+ handled?: boolean;
1149
+ /** Sentry-compatible severity level. Default: `'error'`. */
1150
+ level?: string;
1151
+ /**
1152
+ * Include the stack trace in `$error_stack`. Default: `false` (privacy-safe).
1153
+ * Stack traces may expose internal file paths and function names.
1154
+ */
1155
+ includeStack?: boolean;
1156
+ /**
1157
+ * Maximum number of stack frames to include when `includeStack` is `true`.
1158
+ * Default: `5`.
1159
+ */
1160
+ maxStackFrames?: number;
1161
+ /**
1162
+ * Truncate error messages to this many characters. Default: `200`.
1163
+ * Error messages sometimes contain user data ("User foo@bar.com not found").
1164
+ */
1165
+ maxMessageLength?: number;
1166
+ /**
1167
+ * Skip errors whose message matches any of these patterns.
1168
+ * Pattern is tested against the raw (pre-truncation) message.
1169
+ */
1170
+ ignorePatterns?: RegExp[];
1171
+ /**
1172
+ * Extra properties merged into the captured `$error` event. Lower precedence
1173
+ * than the computed `$error_*` properties.
1174
+ */
1175
+ properties?: Record<string, unknown>;
1176
+ /**
1177
+ * Optional transform applied before `client.capture()`.
1178
+ * Receives typed `ErrorEventProperties`; return a (possibly extended) props
1179
+ * object to capture, or `null` to skip capture entirely.
1180
+ */
1181
+ beforeCapture?: (props: ErrorEventProperties) => Record<string, unknown> | null;
1182
+ }
1183
+ /**
1184
+ * Normalize any thrown value into a SunGlasses `$error` event and capture it
1185
+ * via `client.capture()`.
1186
+ *
1187
+ * This is the single source of error-event construction shared by the built-in
1188
+ * error boundaries and the provider global error handlers. It never throws and
1189
+ * respects consent automatically (capture is consent-gated in the core).
1190
+ *
1191
+ * @param client - SunGlasses client instance.
1192
+ * @param error - The thrown value (an `Error`, string, or arbitrary object).
1193
+ * @param options - Optional capture configuration.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * try {
1198
+ * doRiskyThing();
1199
+ * } catch (err) {
1200
+ * captureException(client, err); // $error_handled: true
1201
+ * }
1202
+ * ```
1203
+ */
1204
+ declare function captureException(client: ISunglassesClient, error: unknown, options?: CaptureExceptionOptions): void;
1205
+
1206
+ /** Console methods that can be captured. */
1207
+ type ConsoleLevel = 'error' | 'warn';
1208
+ /**
1209
+ * Options for {@link patchConsole}.
1210
+ */
1211
+ interface ConsoleCaptureOptions {
1212
+ /** Console methods to capture. Default: `['error']`. */
1213
+ levels?: ConsoleLevel[];
1214
+ /**
1215
+ * Skip console output whose composed message matches any of these patterns.
1216
+ * Pattern is tested against the raw (pre-truncation) message.
1217
+ */
1218
+ ignorePatterns?: RegExp[];
1219
+ /** Truncate the composed message to this many characters. Default: `200`. */
1220
+ maxMessageLength?: number;
1221
+ /** Include a stack trace in `$error_stack`. Default: `false` (privacy-safe). */
1222
+ includeStack?: boolean;
1223
+ /** Extra properties merged into every captured `$error` event. */
1224
+ properties?: Record<string, unknown>;
1225
+ }
1226
+ /**
1227
+ * Configuration for the providers' `autoCaptureErrors` option. Extends
1228
+ * {@link CaptureExceptionOptions} (applied to unhandled global errors) with
1229
+ * toggles for the global handlers and console capture.
1230
+ */
1231
+ interface AutoCaptureErrorsOptions extends CaptureExceptionOptions {
1232
+ /**
1233
+ * Install the platform global error handlers (web `window` `error` /
1234
+ * `unhandledrejection`, React Native `ErrorUtils`). Default: `true`.
1235
+ */
1236
+ globalHandlers?: boolean;
1237
+ /**
1238
+ * Also capture console output as `$error` events. `true` captures
1239
+ * `console.error`; pass {@link ConsoleCaptureOptions} to configure levels.
1240
+ * Default: off.
1241
+ */
1242
+ console?: boolean | ConsoleCaptureOptions;
1243
+ }
1244
+ /**
1245
+ * Patch the global `console` so that `console.error` / `console.warn` (per
1246
+ * `levels`) are also captured as SunGlasses `$error` events
1247
+ * (`$error_handled: false`, `$error_source: 'console'`).
1248
+ *
1249
+ * Works identically on web and React Native — both expose a global `console`.
1250
+ * The original method is always called first, so logs still appear in the
1251
+ * console. Returns an unpatch function that restores the original methods.
1252
+ *
1253
+ * Guards against infinite recursion: SunGlasses' own logger writes to
1254
+ * `console.error`/`console.warn` (prefixed with `[SunGlasses]`), and
1255
+ * `client.capture()` may log on failure. A re-entrancy flag plus a prefix skip
1256
+ * prevent a capture -> log -> capture loop.
1257
+ *
1258
+ * @param client - SunGlasses client instance.
1259
+ * @param options - Optional capture configuration.
1260
+ * @returns A function that restores the original console methods.
1261
+ *
1262
+ * @example
1263
+ * ```ts
1264
+ * const unpatch = patchConsole(client, { levels: ['error', 'warn'] });
1265
+ * // ... later
1266
+ * unpatch();
1267
+ * ```
1268
+ */
1269
+ declare function patchConsole(client: ISunglassesClient, options?: ConsoleCaptureOptions): () => void;
1270
+
1048
1271
  /**
1049
1272
  * A typed analytics stub that is safe to use before the SDK initialises.
1050
1273
  *
@@ -1117,4 +1340,4 @@ declare function sha256Hex(input: string): Promise<string>;
1117
1340
  */
1118
1341
  declare function nowISO(): string;
1119
1342
 
1120
- export { type CleanupConfig, type ConsentHistoryEntry, ConsentManager, type ConsentState, type ConsentStatus, 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, createLazyClient, createLogger, generateUUID, nowISO, sha256Hex };
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 };
package/dist/index.js CHANGED
@@ -33,10 +33,12 @@ __export(index_exports, {
33
33
  SunglassesCore: () => SunglassesCore,
34
34
  TraitManager: () => TraitManager,
35
35
  asTyped: () => asTyped,
36
+ captureException: () => captureException,
36
37
  createLazyClient: () => createLazyClient,
37
38
  createLogger: () => createLogger,
38
39
  generateUUID: () => generateUUID,
39
40
  nowISO: () => nowISO,
41
+ patchConsole: () => patchConsole,
40
42
  sha256Hex: () => sha256Hex
41
43
  });
42
44
  module.exports = __toCommonJS(index_exports);
@@ -1013,6 +1015,11 @@ var SunglassesCore = class _SunglassesCore {
1013
1015
  this.sessionManager = sessionManager;
1014
1016
  this.traitManager = traitManager;
1015
1017
  this.localArchive = localArchive;
1018
+ this.environment = config.environment;
1019
+ this.appVariant = config.appVariant;
1020
+ this.appUpdate = config.appUpdate;
1021
+ this.features = config.features;
1022
+ this.entitlements = config.entitlements;
1016
1023
  }
1017
1024
  /**
1018
1025
  * Create and initialize a SunglassesCore instance.
@@ -1145,11 +1152,41 @@ var SunglassesCore = class _SunglassesCore {
1145
1152
  getRegisteredProperties() {
1146
1153
  return Object.fromEntries(this.superProperties);
1147
1154
  }
1155
+ // ── App metadata ───────────────────────────────────────────────────────────
1156
+ setEnvironment(environment) {
1157
+ this.environment = environment;
1158
+ }
1159
+ setAppUpdate(update) {
1160
+ this.appUpdate = update;
1161
+ }
1162
+ setFeatures(features) {
1163
+ this.features = features;
1164
+ }
1165
+ setEntitlements(entitlements) {
1166
+ this.entitlements = entitlements;
1167
+ }
1168
+ setAppMetadata(meta) {
1169
+ if ("environment" in meta) this.environment = meta.environment;
1170
+ if ("appVariant" in meta) this.appVariant = meta.appVariant;
1171
+ if ("appUpdate" in meta) this.appUpdate = meta.appUpdate;
1172
+ if ("features" in meta) this.features = meta.features;
1173
+ if ("entitlements" in meta) this.entitlements = meta.entitlements;
1174
+ }
1175
+ getAppMetadata() {
1176
+ return {
1177
+ environment: this.environment,
1178
+ appVariant: this.appVariant,
1179
+ appUpdate: this.appUpdate,
1180
+ features: this.features,
1181
+ entitlements: this.entitlements
1182
+ };
1183
+ }
1148
1184
  async reset() {
1149
1185
  await this.identity.reset();
1150
1186
  await this.queue.clear();
1151
1187
  await this.traitManager.clearTraits();
1152
1188
  this.groupId = null;
1189
+ this.entitlements = void 0;
1153
1190
  if (this.sessionManager) {
1154
1191
  await this.sessionManager.end();
1155
1192
  }
@@ -1267,6 +1304,7 @@ var SunglassesCore = class _SunglassesCore {
1267
1304
  await this.identity.reset();
1268
1305
  this.groupId = null;
1269
1306
  this.superProperties.clear();
1307
+ this.entitlements = void 0;
1270
1308
  if (options.resetConsent) {
1271
1309
  await this.consent.resetToUnknown(this.config.consentPolicyVersion);
1272
1310
  this.stopFlushTimer();
@@ -1352,13 +1390,24 @@ var SunglassesCore = class _SunglassesCore {
1352
1390
  library: { name: LIBRARY_NAME, version: LIBRARY_VERSION },
1353
1391
  platform: this.config.platform
1354
1392
  };
1355
- if (this.config.appName || this.config.appVersion) {
1393
+ if (this.config.appName || this.config.appVersion || this.appVariant || this.appUpdate) {
1356
1394
  ctx.app = {
1357
1395
  name: this.config.appName || void 0,
1358
1396
  version: this.config.appVersion || void 0,
1359
- build: this.config.appBuild || void 0
1397
+ build: this.config.appBuild || void 0,
1398
+ variant: this.appVariant || void 0,
1399
+ update: this.appUpdate || void 0
1360
1400
  };
1361
1401
  }
1402
+ if (this.environment) {
1403
+ ctx.environment = this.environment;
1404
+ }
1405
+ if (this.features && this.features.length > 0) {
1406
+ ctx.features = this.features;
1407
+ }
1408
+ if (this.entitlements && this.entitlements.length > 0) {
1409
+ ctx.entitlements = this.entitlements;
1410
+ }
1362
1411
  if (sessionId !== void 0) {
1363
1412
  ctx.sessionId = sessionId;
1364
1413
  }
@@ -1532,6 +1581,130 @@ function asTyped(client) {
1532
1581
  return client;
1533
1582
  }
1534
1583
 
1584
+ // src/captureException.ts
1585
+ function normalizeError(error) {
1586
+ if (error instanceof Error) {
1587
+ return { message: error.message, name: error.name || "Error", stack: error.stack };
1588
+ }
1589
+ if (typeof error === "string") {
1590
+ return { message: error, name: "Error" };
1591
+ }
1592
+ if (error && typeof error === "object") {
1593
+ const maybe = error;
1594
+ return {
1595
+ message: typeof maybe.message === "string" ? maybe.message : String(error),
1596
+ name: typeof maybe.name === "string" && maybe.name ? maybe.name : "Error",
1597
+ stack: typeof maybe.stack === "string" ? maybe.stack : void 0
1598
+ };
1599
+ }
1600
+ return { message: String(error), name: "Error" };
1601
+ }
1602
+ function extractStack(stack, maxFrames) {
1603
+ if (!stack) return void 0;
1604
+ const lines = stack.split("\n").map((l) => l.trim());
1605
+ const v8Frames = lines.filter((l) => l.startsWith("at "));
1606
+ if (v8Frames.length > 0) {
1607
+ return v8Frames.slice(0, maxFrames).join("\n");
1608
+ }
1609
+ const rnFrames = lines.filter((l) => l.includes("@") && !l.includes(" "));
1610
+ if (rnFrames.length > 0) {
1611
+ return rnFrames.slice(0, maxFrames).join("\n");
1612
+ }
1613
+ return void 0;
1614
+ }
1615
+ function captureException(client, error, options = {}) {
1616
+ const {
1617
+ handled = true,
1618
+ level = "error",
1619
+ includeStack = false,
1620
+ maxStackFrames = 5,
1621
+ maxMessageLength = 200,
1622
+ ignorePatterns = [],
1623
+ properties,
1624
+ beforeCapture
1625
+ } = options;
1626
+ const normalized = normalizeError(error);
1627
+ const rawMessage = normalized.message;
1628
+ if (ignorePatterns.some((p) => p.test(rawMessage))) return;
1629
+ let props = {
1630
+ ...properties,
1631
+ $error_message: rawMessage.slice(0, maxMessageLength),
1632
+ $error_type: normalized.name,
1633
+ $error_handled: handled,
1634
+ $error_level: level
1635
+ };
1636
+ if (includeStack) {
1637
+ const frames = extractStack(normalized.stack, maxStackFrames);
1638
+ if (frames) props = { ...props, $error_stack: frames };
1639
+ }
1640
+ if (beforeCapture) {
1641
+ const transformed = beforeCapture(props);
1642
+ if (!transformed) return;
1643
+ client.capture("$error", transformed);
1644
+ } else {
1645
+ client.capture("$error", props);
1646
+ }
1647
+ }
1648
+
1649
+ // src/patchConsole.ts
1650
+ var SELF_LOG_PREFIX = "[SunGlasses]";
1651
+ var LEVEL_TO_SEVERITY = {
1652
+ error: "error",
1653
+ warn: "warning"
1654
+ };
1655
+ function composeMessage(args) {
1656
+ return args.map((arg) => {
1657
+ if (typeof arg === "string") return arg;
1658
+ if (arg instanceof Error) return arg.message;
1659
+ try {
1660
+ return typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg);
1661
+ } catch {
1662
+ return "[object]";
1663
+ }
1664
+ }).join(" ");
1665
+ }
1666
+ function patchConsole(client, options = {}) {
1667
+ const {
1668
+ levels = ["error"],
1669
+ ignorePatterns = [],
1670
+ maxMessageLength = 200,
1671
+ includeStack = false,
1672
+ properties
1673
+ } = options;
1674
+ const uniqueLevels = Array.from(new Set(levels));
1675
+ const originals = /* @__PURE__ */ new Map();
1676
+ let isCapturing = false;
1677
+ for (const level of uniqueLevels) {
1678
+ const original = console[level];
1679
+ originals.set(level, original);
1680
+ console[level] = (...args) => {
1681
+ original.apply(console, args);
1682
+ if (isCapturing) return;
1683
+ if (typeof args[0] === "string" && args[0].startsWith(SELF_LOG_PREFIX)) return;
1684
+ const rawMessage = composeMessage(args);
1685
+ if (ignorePatterns.some((p) => p.test(rawMessage))) return;
1686
+ const errorArg = args.find((a) => a instanceof Error);
1687
+ isCapturing = true;
1688
+ try {
1689
+ captureException(client, errorArg ?? rawMessage, {
1690
+ handled: false,
1691
+ level: LEVEL_TO_SEVERITY[level],
1692
+ includeStack,
1693
+ maxMessageLength,
1694
+ properties: { ...properties, $error_source: "console" }
1695
+ });
1696
+ } finally {
1697
+ isCapturing = false;
1698
+ }
1699
+ };
1700
+ }
1701
+ return () => {
1702
+ for (const [level, original] of originals) {
1703
+ console[level] = original;
1704
+ }
1705
+ };
1706
+ }
1707
+
1535
1708
  // src/LazyClient.ts
1536
1709
  function createLazyClient() {
1537
1710
  let _inner = null;
@@ -1568,6 +1741,25 @@ function createLazyClient() {
1568
1741
  getRegisteredProperties() {
1569
1742
  return _inner?.getRegisteredProperties() ?? {};
1570
1743
  },
1744
+ // ── App metadata ──────────────────────────────────────────────────────────
1745
+ setEnvironment(environment) {
1746
+ _inner?.setEnvironment(environment);
1747
+ },
1748
+ setAppUpdate(update) {
1749
+ _inner?.setAppUpdate(update);
1750
+ },
1751
+ setFeatures(features) {
1752
+ _inner?.setFeatures(features);
1753
+ },
1754
+ setEntitlements(entitlements) {
1755
+ _inner?.setEntitlements(entitlements);
1756
+ },
1757
+ setAppMetadata(meta) {
1758
+ _inner?.setAppMetadata(meta);
1759
+ },
1760
+ getAppMetadata() {
1761
+ return _inner?.getAppMetadata() ?? {};
1762
+ },
1571
1763
  // ── Consent ───────────────────────────────────────────────────────────────
1572
1764
  async optIn() {
1573
1765
  await _inner?.optIn();
@@ -1645,9 +1837,11 @@ function createLazyClient() {
1645
1837
  SunglassesCore,
1646
1838
  TraitManager,
1647
1839
  asTyped,
1840
+ captureException,
1648
1841
  createLazyClient,
1649
1842
  createLogger,
1650
1843
  generateUUID,
1651
1844
  nowISO,
1845
+ patchConsole,
1652
1846
  sha256Hex
1653
1847
  });
package/dist/index.mjs CHANGED
@@ -970,6 +970,11 @@ var SunglassesCore = class _SunglassesCore {
970
970
  this.sessionManager = sessionManager;
971
971
  this.traitManager = traitManager;
972
972
  this.localArchive = localArchive;
973
+ this.environment = config.environment;
974
+ this.appVariant = config.appVariant;
975
+ this.appUpdate = config.appUpdate;
976
+ this.features = config.features;
977
+ this.entitlements = config.entitlements;
973
978
  }
974
979
  /**
975
980
  * Create and initialize a SunglassesCore instance.
@@ -1102,11 +1107,41 @@ var SunglassesCore = class _SunglassesCore {
1102
1107
  getRegisteredProperties() {
1103
1108
  return Object.fromEntries(this.superProperties);
1104
1109
  }
1110
+ // ── App metadata ───────────────────────────────────────────────────────────
1111
+ setEnvironment(environment) {
1112
+ this.environment = environment;
1113
+ }
1114
+ setAppUpdate(update) {
1115
+ this.appUpdate = update;
1116
+ }
1117
+ setFeatures(features) {
1118
+ this.features = features;
1119
+ }
1120
+ setEntitlements(entitlements) {
1121
+ this.entitlements = entitlements;
1122
+ }
1123
+ setAppMetadata(meta) {
1124
+ if ("environment" in meta) this.environment = meta.environment;
1125
+ if ("appVariant" in meta) this.appVariant = meta.appVariant;
1126
+ if ("appUpdate" in meta) this.appUpdate = meta.appUpdate;
1127
+ if ("features" in meta) this.features = meta.features;
1128
+ if ("entitlements" in meta) this.entitlements = meta.entitlements;
1129
+ }
1130
+ getAppMetadata() {
1131
+ return {
1132
+ environment: this.environment,
1133
+ appVariant: this.appVariant,
1134
+ appUpdate: this.appUpdate,
1135
+ features: this.features,
1136
+ entitlements: this.entitlements
1137
+ };
1138
+ }
1105
1139
  async reset() {
1106
1140
  await this.identity.reset();
1107
1141
  await this.queue.clear();
1108
1142
  await this.traitManager.clearTraits();
1109
1143
  this.groupId = null;
1144
+ this.entitlements = void 0;
1110
1145
  if (this.sessionManager) {
1111
1146
  await this.sessionManager.end();
1112
1147
  }
@@ -1224,6 +1259,7 @@ var SunglassesCore = class _SunglassesCore {
1224
1259
  await this.identity.reset();
1225
1260
  this.groupId = null;
1226
1261
  this.superProperties.clear();
1262
+ this.entitlements = void 0;
1227
1263
  if (options.resetConsent) {
1228
1264
  await this.consent.resetToUnknown(this.config.consentPolicyVersion);
1229
1265
  this.stopFlushTimer();
@@ -1309,13 +1345,24 @@ var SunglassesCore = class _SunglassesCore {
1309
1345
  library: { name: LIBRARY_NAME, version: LIBRARY_VERSION },
1310
1346
  platform: this.config.platform
1311
1347
  };
1312
- if (this.config.appName || this.config.appVersion) {
1348
+ if (this.config.appName || this.config.appVersion || this.appVariant || this.appUpdate) {
1313
1349
  ctx.app = {
1314
1350
  name: this.config.appName || void 0,
1315
1351
  version: this.config.appVersion || void 0,
1316
- build: this.config.appBuild || void 0
1352
+ build: this.config.appBuild || void 0,
1353
+ variant: this.appVariant || void 0,
1354
+ update: this.appUpdate || void 0
1317
1355
  };
1318
1356
  }
1357
+ if (this.environment) {
1358
+ ctx.environment = this.environment;
1359
+ }
1360
+ if (this.features && this.features.length > 0) {
1361
+ ctx.features = this.features;
1362
+ }
1363
+ if (this.entitlements && this.entitlements.length > 0) {
1364
+ ctx.entitlements = this.entitlements;
1365
+ }
1319
1366
  if (sessionId !== void 0) {
1320
1367
  ctx.sessionId = sessionId;
1321
1368
  }
@@ -1489,6 +1536,130 @@ function asTyped(client) {
1489
1536
  return client;
1490
1537
  }
1491
1538
 
1539
+ // src/captureException.ts
1540
+ function normalizeError(error) {
1541
+ if (error instanceof Error) {
1542
+ return { message: error.message, name: error.name || "Error", stack: error.stack };
1543
+ }
1544
+ if (typeof error === "string") {
1545
+ return { message: error, name: "Error" };
1546
+ }
1547
+ if (error && typeof error === "object") {
1548
+ const maybe = error;
1549
+ return {
1550
+ message: typeof maybe.message === "string" ? maybe.message : String(error),
1551
+ name: typeof maybe.name === "string" && maybe.name ? maybe.name : "Error",
1552
+ stack: typeof maybe.stack === "string" ? maybe.stack : void 0
1553
+ };
1554
+ }
1555
+ return { message: String(error), name: "Error" };
1556
+ }
1557
+ function extractStack(stack, maxFrames) {
1558
+ if (!stack) return void 0;
1559
+ const lines = stack.split("\n").map((l) => l.trim());
1560
+ const v8Frames = lines.filter((l) => l.startsWith("at "));
1561
+ if (v8Frames.length > 0) {
1562
+ return v8Frames.slice(0, maxFrames).join("\n");
1563
+ }
1564
+ const rnFrames = lines.filter((l) => l.includes("@") && !l.includes(" "));
1565
+ if (rnFrames.length > 0) {
1566
+ return rnFrames.slice(0, maxFrames).join("\n");
1567
+ }
1568
+ return void 0;
1569
+ }
1570
+ function captureException(client, error, options = {}) {
1571
+ const {
1572
+ handled = true,
1573
+ level = "error",
1574
+ includeStack = false,
1575
+ maxStackFrames = 5,
1576
+ maxMessageLength = 200,
1577
+ ignorePatterns = [],
1578
+ properties,
1579
+ beforeCapture
1580
+ } = options;
1581
+ const normalized = normalizeError(error);
1582
+ const rawMessage = normalized.message;
1583
+ if (ignorePatterns.some((p) => p.test(rawMessage))) return;
1584
+ let props = {
1585
+ ...properties,
1586
+ $error_message: rawMessage.slice(0, maxMessageLength),
1587
+ $error_type: normalized.name,
1588
+ $error_handled: handled,
1589
+ $error_level: level
1590
+ };
1591
+ if (includeStack) {
1592
+ const frames = extractStack(normalized.stack, maxStackFrames);
1593
+ if (frames) props = { ...props, $error_stack: frames };
1594
+ }
1595
+ if (beforeCapture) {
1596
+ const transformed = beforeCapture(props);
1597
+ if (!transformed) return;
1598
+ client.capture("$error", transformed);
1599
+ } else {
1600
+ client.capture("$error", props);
1601
+ }
1602
+ }
1603
+
1604
+ // src/patchConsole.ts
1605
+ var SELF_LOG_PREFIX = "[SunGlasses]";
1606
+ var LEVEL_TO_SEVERITY = {
1607
+ error: "error",
1608
+ warn: "warning"
1609
+ };
1610
+ function composeMessage(args) {
1611
+ return args.map((arg) => {
1612
+ if (typeof arg === "string") return arg;
1613
+ if (arg instanceof Error) return arg.message;
1614
+ try {
1615
+ return typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg);
1616
+ } catch {
1617
+ return "[object]";
1618
+ }
1619
+ }).join(" ");
1620
+ }
1621
+ function patchConsole(client, options = {}) {
1622
+ const {
1623
+ levels = ["error"],
1624
+ ignorePatterns = [],
1625
+ maxMessageLength = 200,
1626
+ includeStack = false,
1627
+ properties
1628
+ } = options;
1629
+ const uniqueLevels = Array.from(new Set(levels));
1630
+ const originals = /* @__PURE__ */ new Map();
1631
+ let isCapturing = false;
1632
+ for (const level of uniqueLevels) {
1633
+ const original = console[level];
1634
+ originals.set(level, original);
1635
+ console[level] = (...args) => {
1636
+ original.apply(console, args);
1637
+ if (isCapturing) return;
1638
+ if (typeof args[0] === "string" && args[0].startsWith(SELF_LOG_PREFIX)) return;
1639
+ const rawMessage = composeMessage(args);
1640
+ if (ignorePatterns.some((p) => p.test(rawMessage))) return;
1641
+ const errorArg = args.find((a) => a instanceof Error);
1642
+ isCapturing = true;
1643
+ try {
1644
+ captureException(client, errorArg ?? rawMessage, {
1645
+ handled: false,
1646
+ level: LEVEL_TO_SEVERITY[level],
1647
+ includeStack,
1648
+ maxMessageLength,
1649
+ properties: { ...properties, $error_source: "console" }
1650
+ });
1651
+ } finally {
1652
+ isCapturing = false;
1653
+ }
1654
+ };
1655
+ }
1656
+ return () => {
1657
+ for (const [level, original] of originals) {
1658
+ console[level] = original;
1659
+ }
1660
+ };
1661
+ }
1662
+
1492
1663
  // src/LazyClient.ts
1493
1664
  function createLazyClient() {
1494
1665
  let _inner = null;
@@ -1525,6 +1696,25 @@ function createLazyClient() {
1525
1696
  getRegisteredProperties() {
1526
1697
  return _inner?.getRegisteredProperties() ?? {};
1527
1698
  },
1699
+ // ── App metadata ──────────────────────────────────────────────────────────
1700
+ setEnvironment(environment) {
1701
+ _inner?.setEnvironment(environment);
1702
+ },
1703
+ setAppUpdate(update) {
1704
+ _inner?.setAppUpdate(update);
1705
+ },
1706
+ setFeatures(features) {
1707
+ _inner?.setFeatures(features);
1708
+ },
1709
+ setEntitlements(entitlements) {
1710
+ _inner?.setEntitlements(entitlements);
1711
+ },
1712
+ setAppMetadata(meta) {
1713
+ _inner?.setAppMetadata(meta);
1714
+ },
1715
+ getAppMetadata() {
1716
+ return _inner?.getAppMetadata() ?? {};
1717
+ },
1528
1718
  // ── Consent ───────────────────────────────────────────────────────────────
1529
1719
  async optIn() {
1530
1720
  await _inner?.optIn();
@@ -1601,9 +1791,11 @@ export {
1601
1791
  SunglassesCore,
1602
1792
  TraitManager,
1603
1793
  asTyped,
1794
+ captureException,
1604
1795
  createLazyClient,
1605
1796
  createLogger,
1606
1797
  generateUUID,
1607
1798
  nowISO,
1799
+ patchConsole,
1608
1800
  sha256Hex
1609
1801
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakkar.software/sunglasses-core",
3
- "version": "0.8.0",
3
+ "version": "0.11.0",
4
4
  "description": "Platform-agnostic event tracking engine for SunGlasses",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",