@logspace/sdk 1.0.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/sdk/types.d.ts ADDED
@@ -0,0 +1,411 @@
1
+ import { LogEntry, LogType, LogSeverity, SessionStats } from '../shared/types';
2
+ import { eventWithTime } from 'rrweb';
3
+ export type { LogEntry, LogType, LogSeverity, SessionStats };
4
+ export type { NetworkLog, ConsoleLog, ErrorLog, InteractionLog, PerformanceLog, WebSocketLog, SSELog, StorageLog, AnnotationLog, } from '../shared/types';
5
+ export type { eventWithTime };
6
+ /**
7
+ * Recording type - determines replay format
8
+ */
9
+ export type RecordingType = 'video' | 'rrweb';
10
+ /**
11
+ * SDK Session state
12
+ */
13
+ export interface SDKSession {
14
+ id: string;
15
+ startTime: number;
16
+ endTime?: number;
17
+ status: 'idle' | 'recording' | 'paused' | 'stopped';
18
+ userId?: string;
19
+ userTraits?: Record<string, any>;
20
+ metadata?: Record<string, any>;
21
+ stats: SessionStats;
22
+ url: string;
23
+ title: string;
24
+ }
25
+ /**
26
+ * Capture configuration - what to record
27
+ */
28
+ export interface CaptureConfig {
29
+ /** Enable rrweb DOM recording for visual replay
30
+ * @default true
31
+ */
32
+ rrweb?: boolean;
33
+ /** Capture console logs (log, info, warn, error, debug)
34
+ * @default true
35
+ */
36
+ console?: boolean;
37
+ /** Capture network requests (XHR/Fetch)
38
+ * @default true
39
+ */
40
+ network?: boolean;
41
+ /** Capture JavaScript errors
42
+ * @default true
43
+ */
44
+ errors?: boolean;
45
+ /** Capture user interactions (clicks, inputs)
46
+ * @default true
47
+ */
48
+ interactions?: boolean;
49
+ /** Capture performance metrics
50
+ * @default true
51
+ */
52
+ performance?: boolean;
53
+ /** Capture WebSocket connections
54
+ * @default true
55
+ */
56
+ websocket?: boolean;
57
+ /** Capture Server-Sent Events
58
+ * @default true
59
+ */
60
+ sse?: boolean;
61
+ /** Capture localStorage/sessionStorage changes
62
+ * @default true
63
+ */
64
+ storage?: boolean;
65
+ /** Capture resource loading (CSS, JS, images, fonts)
66
+ * @default true
67
+ */
68
+ resources?: boolean;
69
+ /** Capture SPA route changes (History API, hash changes)
70
+ * @default true
71
+ */
72
+ spaNavigation?: boolean;
73
+ }
74
+ /**
75
+ * rrweb specific configuration
76
+ */
77
+ export interface RRWebConfig {
78
+ /** Mask all text input values for privacy
79
+ * @default false
80
+ */
81
+ maskAllInputs?: boolean;
82
+ /** Mask text content matching these CSS selectors
83
+ * @default undefined
84
+ */
85
+ maskTextSelector?: string;
86
+ /** Block elements matching these CSS selectors from being recorded
87
+ * @default undefined
88
+ */
89
+ blockSelector?: string;
90
+ /** Ignore elements matching these CSS selectors
91
+ * @default undefined
92
+ */
93
+ ignoreSelector?: string;
94
+ /** Record canvas content (can be expensive)
95
+ * @default false
96
+ */
97
+ recordCanvas?: boolean;
98
+ /** Take a full DOM snapshot every N events
99
+ * @default 200
100
+ */
101
+ checkoutEveryNth?: number;
102
+ }
103
+ /**
104
+ * Privacy configuration
105
+ */
106
+ export interface PrivacyConfig {
107
+ /** Mask PII patterns (emails, phones, SSN, credit cards)
108
+ * @default true
109
+ */
110
+ maskSensitiveData?: boolean;
111
+ /** CSS selectors to always mask
112
+ * @default []
113
+ */
114
+ maskSelectors?: string[];
115
+ /** URL patterns to exclude from logging (regex strings)
116
+ * @default []
117
+ */
118
+ excludeUrls?: string[];
119
+ /** Network URL patterns to block request/response bodies
120
+ * @default []
121
+ */
122
+ blockNetworkBodies?: string[];
123
+ /** Header names to always redact
124
+ * @default []
125
+ */
126
+ redactHeaders?: string[];
127
+ /** Console log levels to capture
128
+ * @default ['log', 'info', 'warn', 'error', 'debug']
129
+ */
130
+ logLevels?: LogSeverity[];
131
+ }
132
+ /**
133
+ * Session limits - protection against infinite loops and memory issues
134
+ */
135
+ export interface SessionLimits {
136
+ /** Maximum logs per session before auto-ending
137
+ * @default 10000
138
+ */
139
+ maxLogs?: number;
140
+ /** Maximum session size in bytes before auto-ending
141
+ * @default 10485760 (10MB)
142
+ */
143
+ maxSize?: number;
144
+ /** Maximum session duration in seconds before auto-ending
145
+ * @default 300 (5 minutes)
146
+ */
147
+ maxDuration?: number;
148
+ /** Idle timeout in seconds - auto-end if no activity
149
+ * @default 30
150
+ */
151
+ idleTimeout?: number;
152
+ /** Navigate away timeout in seconds - grace period when tab becomes hidden
153
+ * Session only ends after this period of being hidden (like switching tabs)
154
+ * @default 120 (2 minutes)
155
+ */
156
+ navigateAwayTimeout?: number;
157
+ /** Rate limit - max logs per second (excess logs are dropped)
158
+ * @default 100
159
+ */
160
+ rateLimit?: number;
161
+ /** Deduplicate consecutive identical logs
162
+ * @default true
163
+ */
164
+ deduplicate?: boolean;
165
+ }
166
+ /**
167
+ * Session end triggers
168
+ */
169
+ export interface SessionEndConfig {
170
+ /** Continue session across page refreshes instead of creating new sessions.
171
+ * When true, refreshing the page merges data into the same session.
172
+ * When false, each page load creates a new session.
173
+ * Note: Data is always saved to storage on page unload for reliability.
174
+ * @default true
175
+ */
176
+ continueOnRefresh?: boolean;
177
+ /** Auto-end session on idle timeout
178
+ * @default true
179
+ */
180
+ onIdle?: boolean;
181
+ /** Auto-end session when limits are reached
182
+ * @default true
183
+ */
184
+ onLimitReached?: boolean;
185
+ }
186
+ /**
187
+ * Sampling configuration - only record sessions when specific events occur
188
+ * When enabled, the SDK keeps a rolling buffer and only sends sessions when triggered
189
+ */
190
+ export interface SamplingConfig {
191
+ /** Enable sampling mode - only send sessions when triggered by errors or custom events
192
+ * @default false
193
+ */
194
+ enabled?: boolean;
195
+ /** Seconds of logs to keep before the trigger event
196
+ * @default 15
197
+ */
198
+ bufferBefore?: number;
199
+ /** Seconds to continue recording after the trigger event
200
+ * @default 15
201
+ */
202
+ recordAfter?: number;
203
+ /** What triggers a session to be saved */
204
+ triggers?: {
205
+ /** Trigger on uncaught JavaScript errors
206
+ * @default true
207
+ */
208
+ onError?: boolean;
209
+ /** Trigger on console.error calls
210
+ * @default true
211
+ */
212
+ onConsoleError?: boolean;
213
+ /** Trigger on specific HTTP status codes (e.g., [500, 502, 503])
214
+ * @default [500, 502, 503, 504]
215
+ */
216
+ onNetworkStatus?: number[];
217
+ };
218
+ }
219
+ /**
220
+ * Lifecycle hooks
221
+ */
222
+ export interface HooksConfig {
223
+ /** Called on every captured action
224
+ * @default undefined
225
+ */
226
+ onAction?: (log: LogEntry, session: SDKSession) => void;
227
+ /** Transform/filter logs before sending - return null to drop all logs
228
+ * @default undefined
229
+ */
230
+ beforeSend?: (logs: LogEntry[]) => LogEntry[] | null;
231
+ /** Called when session starts
232
+ * @default undefined
233
+ */
234
+ onSessionStart?: (session: SDKSession) => void;
235
+ /** Called when session ends
236
+ * @default undefined
237
+ */
238
+ onSessionEnd?: (session: SDKSession, logs: LogEntry[]) => void;
239
+ /** Called when session auto-ends due to limits
240
+ * @default undefined
241
+ */
242
+ onLimitReached?: (reason: 'maxLogs' | 'maxSize' | 'maxDuration' | 'idle') => void;
243
+ /** Called when sampling is triggered (error detected)
244
+ * @default undefined
245
+ */
246
+ onSamplingTrigger?: (trigger: 'error' | 'consoleError' | 'networkError' | 'manual', log?: LogEntry) => void;
247
+ /** Called on transport/network errors
248
+ * @default undefined
249
+ */
250
+ onError?: (error: Error) => void;
251
+ }
252
+ /**
253
+ * Main SDK configuration - all fields are optional
254
+ */
255
+ export interface LogSpaceConfig {
256
+ /** Server URL for sending sessions. If not provided, sessions are stored locally only.
257
+ * @default undefined
258
+ */
259
+ serverUrl?: string;
260
+ /** API key in format 'project_id:environment_id' for authentication
261
+ * @default undefined
262
+ */
263
+ apiKey?: string;
264
+ /** Configure what to capture (console, network, errors, etc.)
265
+ * @default All capture options enabled (true)
266
+ */
267
+ capture?: CaptureConfig;
268
+ /** rrweb DOM recording settings
269
+ * @default { maskAllInputs: false, recordCanvas: false, checkoutEveryNth: 200 }
270
+ */
271
+ rrweb?: RRWebConfig;
272
+ /** Privacy settings for masking sensitive data
273
+ * @default { maskSensitiveData: true, maskSelectors: [], excludeUrls: [], blockNetworkBodies: [], redactHeaders: [], logLevels: ['log', 'info', 'warn', 'error', 'debug'] }
274
+ */
275
+ privacy?: PrivacyConfig;
276
+ /** Session limits for protection against memory issues
277
+ * @default { maxLogs: 10000, maxSize: 10485760, maxDuration: 300, idleTimeout: 30, rateLimit: 100, deduplicate: true }
278
+ */
279
+ limits?: SessionLimits;
280
+ /** Configure when to auto-end sessions
281
+ * @default { continueOnRefresh: true, onIdle: true, onLimitReached: true }
282
+ */
283
+ autoEnd?: SessionEndConfig;
284
+ /** Sampling configuration - only save sessions when errors occur
285
+ * When enabled, keeps a rolling buffer and only sends when triggered by errors
286
+ * @default { enabled: false }
287
+ */
288
+ sampling?: SamplingConfig;
289
+ /** Lifecycle hooks for customizing behavior
290
+ * @default All hooks undefined
291
+ */
292
+ hooks?: HooksConfig;
293
+ /** Custom headers to include in API requests
294
+ * @default {}
295
+ */
296
+ headers?: Record<string, string>;
297
+ /** Enable debug logging to console
298
+ * @default false
299
+ */
300
+ debug?: boolean;
301
+ /** Dry run mode - log payload instead of sending (for testing)
302
+ * @default false
303
+ */
304
+ dryRun?: boolean;
305
+ }
306
+ /**
307
+ * Normalized config with all defaults applied
308
+ */
309
+ export interface NormalizedConfig {
310
+ serverUrl?: string;
311
+ apiKey?: string;
312
+ capture: Required<CaptureConfig>;
313
+ rrweb: RRWebConfig;
314
+ privacy: Required<PrivacyConfig>;
315
+ limits: Required<SessionLimits>;
316
+ autoEnd: Required<SessionEndConfig>;
317
+ sampling: Required<Omit<SamplingConfig, 'triggers'>> & {
318
+ triggers: Required<NonNullable<SamplingConfig['triggers']>>;
319
+ };
320
+ hooks: HooksConfig;
321
+ headers: Record<string, string>;
322
+ debug: boolean;
323
+ dryRun: boolean;
324
+ }
325
+ /**
326
+ * Session payload sent to server
327
+ */
328
+ export interface SessionPayload {
329
+ id: string;
330
+ startTime: number;
331
+ endTime: number;
332
+ duration: number;
333
+ url: string;
334
+ title: string;
335
+ userId?: string;
336
+ userTraits?: Record<string, any>;
337
+ metadata?: Record<string, any>;
338
+ logs: LogEntry[];
339
+ stats: SessionStats;
340
+ endReason: 'manual' | 'unload' | 'idle' | 'navigateAway' | 'maxLogs' | 'maxSize' | 'maxDuration' | 'samplingTriggered' | 'crash';
341
+ /** Recording type - 'rrweb' for SDK, 'video' for extension */
342
+ recordingType: RecordingType;
343
+ /** rrweb DOM events for visual replay (SDK only) */
344
+ rrwebEvents?: eventWithTime[];
345
+ /** What triggered the sampling (only present when sampling is enabled) */
346
+ samplingTrigger?: 'error' | 'consoleError' | 'networkError' | 'manual';
347
+ /** User agent string */
348
+ userAgent?: string;
349
+ /** SDK version */
350
+ sdkVersion?: string;
351
+ }
352
+ /**
353
+ * Public SDK interface
354
+ */
355
+ export interface LogSpaceSDK {
356
+ /** Initialize the SDK */
357
+ init(config: LogSpaceConfig): void;
358
+ /** Identify user */
359
+ identify(userId: string, traits?: Record<string, any>): void;
360
+ /** Track custom event */
361
+ track(event: string, properties?: Record<string, any>): void;
362
+ /** Add breadcrumb */
363
+ breadcrumb(message: string, category?: string): void;
364
+ /** Start recording session */
365
+ startSession(metadata?: Record<string, any>): void;
366
+ /** Stop recording session and send to server */
367
+ stopSession(): Promise<void>;
368
+ /** Pause recording */
369
+ pauseRecording(): void;
370
+ /** Resume recording */
371
+ resumeRecording(): void;
372
+ /** Get current session */
373
+ getSession(): SDKSession | null;
374
+ /** Get all logs for current session */
375
+ getSessionLogs(): LogEntry[];
376
+ /** Get session data (for local export without server) */
377
+ getSessionData(): SessionPayload | null;
378
+ /** Destroy SDK and cleanup */
379
+ destroy(): void;
380
+ }
381
+ /**
382
+ * Plugin interface for extending SDK
383
+ */
384
+ export interface LogSpacePlugin {
385
+ name: string;
386
+ setup(sdk: LogSpaceSDK, config: NormalizedConfig): void;
387
+ teardown?(): void;
388
+ }
389
+ /**
390
+ * Internal capture handler type
391
+ * Accepts partial log data, SDK adds id/timestamp/tabId/tabUrl
392
+ */
393
+ export type CaptureHandler = (log: {
394
+ type: LogEntry['type'];
395
+ data: any;
396
+ severity?: LogEntry['severity'];
397
+ }) => void;
398
+ /**
399
+ * Rate limiter state
400
+ */
401
+ export interface RateLimiterState {
402
+ count: number;
403
+ windowStart: number;
404
+ }
405
+ /**
406
+ * Deduplication state
407
+ */
408
+ export interface DeduplicationState {
409
+ lastLogHash: string | null;
410
+ lastLogCount: number;
411
+ }
@@ -0,0 +1,38 @@
1
+ import { default as Browser } from 'webextension-polyfill';
2
+ /**
3
+ * Cross-browser API namespace
4
+ * Uses webextension-polyfill for Firefox/Safari compatibility
5
+ * Falls back to chrome namespace if polyfill not available
6
+ */
7
+ export declare const browser: typeof Browser;
8
+ export interface BrowserCapabilities {
9
+ hasTabCapture: boolean;
10
+ hasDisplayMedia: boolean;
11
+ hasOffscreenDocuments: boolean;
12
+ hasMediaRecorder: boolean;
13
+ recommendedMode: 'auto' | 'fullscreen' | 'custom';
14
+ browserName: string;
15
+ isChromium: boolean;
16
+ }
17
+ /**
18
+ * Detect browser capabilities
19
+ */
20
+ export declare function detectBrowserCapabilities(): BrowserCapabilities;
21
+ /**
22
+ * Get user-friendly description for capture mode
23
+ */
24
+ export declare function getCaptureModeDescription(mode: 'auto' | 'fullscreen' | 'custom'): string;
25
+ /**
26
+ * Check if a capture mode is available in current browser
27
+ */
28
+ export declare function isCaptureModeAvailable(mode: 'auto' | 'fullscreen' | 'custom'): boolean;
29
+ /**
30
+ * Get list of available capture modes for UI
31
+ */
32
+ export declare function getAvailableCaptureModes(): Array<{
33
+ mode: 'auto' | 'fullscreen' | 'custom';
34
+ label: string;
35
+ description: string;
36
+ icon: string;
37
+ available: boolean;
38
+ }>;
@@ -0,0 +1,23 @@
1
+ import { CaptureMode } from './types';
2
+ export interface DisplayMediaOptions {
3
+ mode: 'fullscreen' | 'tab' | 'custom';
4
+ preferCurrentTab?: boolean;
5
+ frameRate?: number;
6
+ videoQuality?: 'low' | 'medium' | 'high';
7
+ }
8
+ /**
9
+ * Request display media capture from user
10
+ * This must be called from a user gesture (e.g., button click)
11
+ */
12
+ export declare function requestDisplayMedia(options: DisplayMediaOptions): Promise<MediaStream>;
13
+ /**
14
+ * Transfer MediaStream to offscreen for recording
15
+ * Since we can't serialize MediaStream, we'll use a different approach:
16
+ * Store the stream globally and let offscreen access it
17
+ */
18
+ export declare function transferStreamToBackground(stream: MediaStream, sessionId: string, mode: CaptureMode): Promise<void>;
19
+ /**
20
+ * Alternative: Create offscreen document to handle getDisplayMedia
21
+ * This avoids stream transfer issues by letting offscreen handle everything
22
+ */
23
+ export declare function requestOffscreenDisplayMedia(sessionId: string, mode: 'fullscreen' | 'tab' | 'custom'): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { RecordingSession } from './types';
2
+ /**
3
+ * Export session data as a .zip file containing:
4
+ * - session.json: Session metadata and logs (compressed)
5
+ * - video.webm: Recorded video (if available)
6
+ */
7
+ export declare function exportSession(session: RecordingSession): Promise<void>;
8
+ /**
9
+ * Export session as separate JSON file (no video)
10
+ */
11
+ export declare function exportSessionJSON(session: RecordingSession): Promise<void>;
12
+ /**
13
+ * Export video only
14
+ */
15
+ export declare function exportSessionVideo(sessionId: string): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import { LogEntry } from './types';
2
+ /**
3
+ * Optimized log entry for storage (normalized)
4
+ */
5
+ export interface OptimizedLogEntry {
6
+ id: string;
7
+ ts: number;
8
+ type: string;
9
+ data: any;
10
+ sev?: string;
11
+ }
12
+ /**
13
+ * Session context stored once per session
14
+ */
15
+ export interface SessionContext {
16
+ id: string;
17
+ startTime: number;
18
+ tabs: Record<number, TabContext>;
19
+ }
20
+ /**
21
+ * Tab context stored once per tab
22
+ */
23
+ export interface TabContext {
24
+ url: string;
25
+ title: string;
26
+ startTime: number;
27
+ }
28
+ /**
29
+ * Optimize log entry for storage
30
+ * Removes redundant fields that can be inferred from context
31
+ */
32
+ export declare function optimizeLog(log: LogEntry, sessionId: string): OptimizedLogEntry;
33
+ /**
34
+ * Denormalize log entry for export/usage
35
+ * Restores the full LogEntry structure with context
36
+ */
37
+ export declare function denormalizeLog(optimized: OptimizedLogEntry | any, // Accept any to handle stored format
38
+ sessionId: string, tabId: number, tabUrl: string): LogEntry;
39
+ /**
40
+ * Calculate storage savings
41
+ */
42
+ export declare function calculateSavings(original: LogEntry[], optimized: OptimizedLogEntry[]): {
43
+ originalSize: number;
44
+ optimizedSize: number;
45
+ savingsBytes: number;
46
+ savingsPercent: number;
47
+ };