@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/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@logspace/sdk",
3
+ "version": "1.0.0",
4
+ "description": "LogSpace JavaScript SDK for session recording and logging",
5
+ "type": "module",
6
+ "main": "./logspace.umd.js",
7
+ "module": "./logspace.esm.js",
8
+ "types": "./index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./index.d.ts",
13
+ "default": "./logspace.esm.js"
14
+ },
15
+ "require": {
16
+ "types": "./index.d.ts",
17
+ "default": "./logspace.umd.js"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "logspace.esm.js",
23
+ "logspace.esm.js.map",
24
+ "logspace.umd.js",
25
+ "logspace.umd.js.map",
26
+ "logspace.iife.js",
27
+ "logspace.iife.js.map",
28
+ "index.d.ts",
29
+ "sdk",
30
+ "shared"
31
+ ],
32
+ "keywords": [
33
+ "logging",
34
+ "session-recording",
35
+ "rrweb",
36
+ "debugging",
37
+ "monitoring"
38
+ ],
39
+ "author": "LogSpace",
40
+ "license": "MIT",
41
+ "scripts": {
42
+ "preinstall": "npx only-allow bun && bun --version | grep -q '^1.2.21$' || (echo 'Error: Bun version 1.2.21 is required' && exit 1)"
43
+ }
44
+ }
@@ -0,0 +1,45 @@
1
+ import { LogEntry, LogType } from '../../shared/types';
2
+ import { CaptureHandler, PrivacyConfig } from '../types';
3
+ import { generateId, safeStringify, maskSensitiveData, maskHeaders } from '../../shared/utils';
4
+ export { generateId, safeStringify, maskSensitiveData, maskHeaders };
5
+ /**
6
+ * Check if running in browser environment (SSR safety)
7
+ */
8
+ export declare function isBrowser(): boolean;
9
+ /**
10
+ * Check if URL should be excluded from capture
11
+ */
12
+ export declare function shouldExcludeUrl(url: string, excludePatterns: string[]): boolean;
13
+ /**
14
+ * Get call stack for request initiator
15
+ */
16
+ export declare function getCallStack(): string;
17
+ /**
18
+ * Truncate large JSON objects while maintaining structure
19
+ */
20
+ export declare function truncateJSON(obj: any, maxLength: number): any;
21
+ /**
22
+ * Truncate message content
23
+ */
24
+ export declare function truncateMessage(message: any, isBinary: boolean, maxSize?: number): {
25
+ content: string;
26
+ size: number;
27
+ truncated: boolean;
28
+ };
29
+ /**
30
+ * Create a log entry with common fields
31
+ */
32
+ export declare function createLogEntry(type: LogType, data: any, severity?: 'info' | 'warn' | 'error'): Omit<LogEntry, 'tabId' | 'tabUrl'>;
33
+ /**
34
+ * Apply privacy settings to log data
35
+ */
36
+ export declare function applyPrivacy(log: LogEntry, privacy: PrivacyConfig): LogEntry;
37
+ /**
38
+ * Capture module interface
39
+ */
40
+ export interface CaptureModule {
41
+ name: string;
42
+ install(handler: CaptureHandler, privacy: PrivacyConfig): void;
43
+ uninstall(): void;
44
+ reset?(): void;
45
+ }
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * Console capture module
4
+ */
5
+ export declare const consoleCapture: CaptureModule;
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * Error capture module
4
+ */
5
+ export declare const errorCapture: CaptureModule;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Capture Module Index
3
+ * Re-exports all capture modules
4
+ */
5
+ export { consoleCapture } from './console';
6
+ export { networkCapture } from './network';
7
+ export { errorCapture } from './errors';
8
+ export { websocketCapture } from './websocket';
9
+ export { sseCapture } from './sse';
10
+ export { storageCapture } from './storage';
11
+ export { interactionCapture } from './interactions';
12
+ export { performanceCapture } from './performance';
13
+ export { resourceCapture } from './resources';
14
+ export { spaNavigationCapture } from './spa-navigation';
15
+ export type { CaptureModule } from './base';
16
+ export { applyPrivacy, createLogEntry, isBrowser } from './base';
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * Interaction capture module
4
+ */
5
+ export declare const interactionCapture: CaptureModule;
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * Network capture module
4
+ */
5
+ export declare const networkCapture: CaptureModule;
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * Performance capture module
4
+ */
5
+ export declare const performanceCapture: CaptureModule;
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * Resource capture module
4
+ */
5
+ export declare const resourceCapture: CaptureModule;
@@ -0,0 +1,101 @@
1
+ import { eventWithTime } from 'rrweb';
2
+ import { RecordPlugin } from '@rrweb/types';
3
+ export interface RRWebCaptureConfig {
4
+ /** Mask all text input values (default: true) */
5
+ maskAllInputs?: boolean;
6
+ /** Mask text content matching these CSS selectors */
7
+ maskTextSelector?: string;
8
+ /** Block elements matching these CSS selectors from being recorded */
9
+ blockSelector?: string;
10
+ /** Ignore elements matching these CSS selectors */
11
+ ignoreSelector?: string;
12
+ /** Inline stylesheets for accurate replay (default: true) */
13
+ inlineStylesheet?: boolean;
14
+ /** Sample mouse movements to reduce data (default: true) */
15
+ sampling?: {
16
+ mousemove?: boolean | number;
17
+ mouseInteraction?: boolean | Record<string, boolean | undefined>;
18
+ scroll?: number;
19
+ media?: number;
20
+ input?: 'last' | 'all';
21
+ canvas?: number | 'all';
22
+ };
23
+ /** Take a full DOM snapshot every N events (default: 200) */
24
+ checkoutEveryNth?: number;
25
+ /** Take a full DOM snapshot every N milliseconds */
26
+ checkoutEveryNms?: number;
27
+ /** Record canvas content (default: false - can be expensive) */
28
+ recordCanvas?: boolean;
29
+ /** Collect fonts for replay (default: false) */
30
+ collectFonts?: boolean;
31
+ /** Enable inline images for replay (default: true) */
32
+ inlineImages?: boolean;
33
+ /** Plugins to use */
34
+ plugins?: RecordPlugin[];
35
+ }
36
+ export interface RRWebCaptureState {
37
+ isRecording: boolean;
38
+ events: eventWithTime[];
39
+ stopFn: (() => void) | null;
40
+ startTime: number | null;
41
+ }
42
+ /**
43
+ * Extended rrweb event with tab information
44
+ */
45
+ export type TabAwareEvent = eventWithTime & {
46
+ tabId?: string;
47
+ };
48
+ /**
49
+ * Start rrweb DOM recording
50
+ * @param config - rrweb configuration options
51
+ * @param onEvent - callback for each recorded event
52
+ * @param tabId - optional tab identifier for multi-tab recording
53
+ */
54
+ export declare function startRRWebRecording(config?: RRWebCaptureConfig, onEvent?: (event: TabAwareEvent) => void, tabId?: string): boolean;
55
+ /**
56
+ * Stop rrweb DOM recording and return captured events
57
+ */
58
+ export declare function stopRRWebRecording(): eventWithTime[];
59
+ /**
60
+ * Get current recording state
61
+ */
62
+ export declare function getRRWebState(): Readonly<RRWebCaptureState>;
63
+ /**
64
+ * Get current events (for incremental sending if needed)
65
+ */
66
+ export declare function getRRWebEvents(): eventWithTime[];
67
+ /**
68
+ * Clear current events (useful for chunked sending)
69
+ */
70
+ export declare function clearRRWebEvents(): void;
71
+ /**
72
+ * Get recording duration in milliseconds
73
+ */
74
+ export declare function getRRWebDuration(): number;
75
+ /**
76
+ * Check if recording is active
77
+ */
78
+ export declare function isRRWebRecording(): boolean;
79
+ /**
80
+ * Get estimated size of recorded events in bytes
81
+ */
82
+ export declare function getRRWebEventsSize(): number;
83
+ /**
84
+ * Force a full DOM snapshot
85
+ * Call this when the tab becomes visible to ensure complete DOM context
86
+ */
87
+ export declare function forceFullSnapshot(): void;
88
+ /**
89
+ * Add a custom event to the rrweb recording
90
+ * Used for marking tab switches, navigation, etc.
91
+ */
92
+ export declare function addRRWebCustomEvent<T>(tag: string, payload: T): void;
93
+ /**
94
+ * Get current tab ID
95
+ */
96
+ export declare function getRRWebTabId(): string | null;
97
+ /**
98
+ * Set current tab ID (for when tab becomes leader)
99
+ */
100
+ export declare function setRRWebTabId(tabId: string): void;
101
+ export type { eventWithTime } from 'rrweb';
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * SPA navigation capture module
4
+ */
5
+ export declare const spaNavigationCapture: CaptureModule;
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * SSE capture module
4
+ */
5
+ export declare const sseCapture: CaptureModule;
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * Storage capture module
4
+ */
5
+ export declare const storageCapture: CaptureModule;
@@ -0,0 +1,5 @@
1
+ import { CaptureModule } from './base';
2
+ /**
3
+ * WebSocket capture module
4
+ */
5
+ export declare const websocketCapture: CaptureModule;
@@ -0,0 +1,26 @@
1
+ import { CaptureHandler } from '../types';
2
+ /**
3
+ * LogSpace console interface
4
+ */
5
+ export interface LogSpaceConsole {
6
+ /** Log a custom event */
7
+ (event: string, data?: any): void;
8
+ /** Log an error with context */
9
+ error: (name: string, error: Error | any, context?: Record<string, any>) => void;
10
+ /** Add a breadcrumb */
11
+ breadcrumb: (message: string, category?: string) => void;
12
+ /** Log debug info (only in development) */
13
+ debug: (message: string, data?: any) => void;
14
+ }
15
+ /**
16
+ * Install console.logspace on the window object
17
+ */
18
+ export declare function installConsoleAPI(handler: CaptureHandler, debug?: boolean): void;
19
+ /**
20
+ * Uninstall console.logspace
21
+ */
22
+ export declare function uninstallConsoleAPI(): void;
23
+ /**
24
+ * Get the logspace console (for direct use)
25
+ */
26
+ export declare function getLogSpaceConsole(): LogSpaceConsole;
@@ -0,0 +1,176 @@
1
+ import { LogEntry } from '../../shared/types';
2
+ import { eventWithTime } from 'rrweb';
3
+ type TabAwareRRWebEvent = eventWithTime & {
4
+ tabId?: string;
5
+ };
6
+ type MultiTabMessage = {
7
+ type: 'leader-announce';
8
+ tabId: string;
9
+ sessionId: string;
10
+ } | {
11
+ type: 'leader-leaving';
12
+ tabId: string;
13
+ } | {
14
+ type: 'log';
15
+ tabId: string;
16
+ log: LogEntry;
17
+ } | {
18
+ type: 'rrweb-event';
19
+ tabId: string;
20
+ event: TabAwareRRWebEvent;
21
+ } | {
22
+ type: 'request-session';
23
+ tabId: string;
24
+ } | {
25
+ type: 'session-info';
26
+ sessionId: string;
27
+ startTime: number;
28
+ };
29
+ interface LeaderState {
30
+ tabId: string;
31
+ sessionId: string;
32
+ heartbeat: number;
33
+ }
34
+ interface SessionState {
35
+ id: string;
36
+ startTime: number;
37
+ userId?: string;
38
+ userTraits?: Record<string, any>;
39
+ }
40
+ interface MultiTabCallbacks {
41
+ onBecomeLeader: (sessionId: string, isNewSession: boolean) => void;
42
+ onBecomeFollower: (sessionId: string) => void;
43
+ onReceiveLog: (log: LogEntry) => void;
44
+ onReceiveRRWebEvent: (event: TabAwareRRWebEvent) => void;
45
+ onLeaderChanged: (newLeaderTabId: string) => void;
46
+ }
47
+ declare class MultiTabCoordinator {
48
+ private tabId;
49
+ private channel;
50
+ private isLeader;
51
+ private callbacks;
52
+ private heartbeatTimer;
53
+ private leaderCheckTimer;
54
+ private currentSessionId;
55
+ private initialized;
56
+ private debug;
57
+ constructor();
58
+ /**
59
+ * Generate unique tab identifier
60
+ */
61
+ private generateTabId;
62
+ /**
63
+ * Check if BroadcastChannel is supported
64
+ */
65
+ isSupported(): boolean;
66
+ /**
67
+ * Initialize the multi-tab coordinator
68
+ */
69
+ init(callbacks: MultiTabCallbacks, debug?: boolean): void;
70
+ /**
71
+ * Attempt to become leader or join as follower
72
+ */
73
+ private electLeader;
74
+ /**
75
+ * Try to claim leadership
76
+ */
77
+ private tryBecomeLeader;
78
+ /**
79
+ * Become the leader tab
80
+ */
81
+ private becomeLeader;
82
+ /**
83
+ * Become a follower tab
84
+ */
85
+ private becomeFollower;
86
+ /**
87
+ * Handle incoming broadcast messages
88
+ */
89
+ private handleMessage;
90
+ /**
91
+ * Start heartbeat for leader
92
+ */
93
+ private startHeartbeat;
94
+ /**
95
+ * Stop heartbeat timer
96
+ */
97
+ private stopHeartbeat;
98
+ /**
99
+ * Update heartbeat in localStorage
100
+ */
101
+ private updateHeartbeat;
102
+ /**
103
+ * Start checking if leader is alive (for followers)
104
+ */
105
+ private startLeaderCheck;
106
+ /**
107
+ * Stop leader check timer
108
+ */
109
+ private stopLeaderCheck;
110
+ /**
111
+ * Check if leader heartbeat is stale
112
+ */
113
+ private isLeaderStale;
114
+ /**
115
+ * Get leader state from localStorage
116
+ */
117
+ private getLeaderState;
118
+ /**
119
+ * Get session state from localStorage
120
+ */
121
+ private getSessionState;
122
+ /**
123
+ * Generate session ID
124
+ */
125
+ private generateSessionId;
126
+ /**
127
+ * Broadcast message to other tabs
128
+ */
129
+ private broadcast;
130
+ /**
131
+ * Handle page unload - clean leader handoff
132
+ */
133
+ private handleUnload;
134
+ /**
135
+ * Send log to leader (for follower tabs)
136
+ */
137
+ sendLog(log: LogEntry): void;
138
+ /**
139
+ * Send rrweb event to leader (for follower tabs)
140
+ */
141
+ sendRRWebEvent(event: TabAwareRRWebEvent): void;
142
+ /**
143
+ * Update session user info (stored in localStorage for all tabs)
144
+ */
145
+ updateSessionUser(userId: string, traits?: Record<string, any>): void;
146
+ /**
147
+ * End session and clear shared state
148
+ */
149
+ endSession(): void;
150
+ /**
151
+ * Get current tab ID
152
+ */
153
+ getTabId(): string;
154
+ /**
155
+ * Get current session ID
156
+ */
157
+ getSessionId(): string | null;
158
+ /**
159
+ * Check if this tab is the leader
160
+ */
161
+ isLeaderTab(): boolean;
162
+ /**
163
+ * Check if multi-tab mode is active
164
+ */
165
+ isActive(): boolean;
166
+ /**
167
+ * Clean up resources
168
+ */
169
+ cleanup(): void;
170
+ /**
171
+ * Force become leader (for testing or recovery)
172
+ */
173
+ forceLeadership(): void;
174
+ }
175
+ export declare const multiTabCoordinator: MultiTabCoordinator;
176
+ export type { MultiTabCallbacks, LeaderState, SessionState, MultiTabMessage };
package/sdk/index.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { LogEntry } from '../shared/types';
2
+ import { LogSpaceConfig, SDKSession, SessionPayload } from './types';
3
+ import { eventWithTime } from './capture/rrweb';
4
+ export declare const LogSpace: {
5
+ /**
6
+ * Initialize the SDK
7
+ */
8
+ init(userConfig: LogSpaceConfig): void;
9
+ /**
10
+ * Start a new recording session
11
+ */
12
+ startSession(metadata?: Record<string, any>): void;
13
+ /**
14
+ * Stop recording and send session to server
15
+ */
16
+ stopSession(): Promise<void>;
17
+ /**
18
+ * Identify user
19
+ */
20
+ identify(userId: string, traits?: Record<string, any>): void;
21
+ /**
22
+ * Track custom event
23
+ */
24
+ track(event: string, properties?: Record<string, any>): void;
25
+ /**
26
+ * Add breadcrumb
27
+ */
28
+ breadcrumb(message: string, category?: string): void;
29
+ /**
30
+ * Pause recording
31
+ */
32
+ pauseRecording(): void;
33
+ /**
34
+ * Resume recording
35
+ */
36
+ resumeRecording(): void;
37
+ /**
38
+ * Manually trigger sampling (when sampling mode is enabled)
39
+ * Use this to mark a session as important even without an error
40
+ */
41
+ trigger(reason?: string): void;
42
+ /**
43
+ * Get current session
44
+ */
45
+ getSession(): SDKSession | null;
46
+ /**
47
+ * Get all logs for current session
48
+ */
49
+ getSessionLogs(): LogEntry[];
50
+ /**
51
+ * Get session data (for local export without server)
52
+ */
53
+ getSessionData(): SessionPayload | null;
54
+ /**
55
+ * Get rrweb events for current session
56
+ */
57
+ getRRWebEvents(): eventWithTime[];
58
+ /**
59
+ * Destroy SDK
60
+ */
61
+ destroy(): void;
62
+ /**
63
+ * SDK version
64
+ */
65
+ version: string;
66
+ };
67
+ export type { LogSpaceConfig, NormalizedConfig, SDKSession, SessionPayload, CaptureConfig, PrivacyConfig, SessionLimits, SessionEndConfig, SamplingConfig, HooksConfig, LogSpacePlugin, } from './types';
68
+ export type { LogEntry, LogType, LogSeverity } from '../shared/types';
69
+ export default LogSpace;
@@ -0,0 +1,72 @@
1
+ import { LogEntry } from '../../shared/types';
2
+ import { SessionPayload } from '../types';
3
+ import { eventWithTime } from 'rrweb';
4
+ interface StoredSession {
5
+ id: string;
6
+ data: SessionPayload;
7
+ createdAt: number;
8
+ retryCount: number;
9
+ lastRetryAt?: number;
10
+ }
11
+ interface CurrentSessionBackup {
12
+ id: string;
13
+ sessionId: string;
14
+ logs: LogEntry[];
15
+ rrwebEvents: eventWithTime[];
16
+ startTime: number;
17
+ url: string;
18
+ title: string;
19
+ userId?: string;
20
+ userTraits?: Record<string, any>;
21
+ metadata?: Record<string, any>;
22
+ lastCheckpoint: number;
23
+ }
24
+ /**
25
+ * Check if IndexedDB is available
26
+ */
27
+ export declare function isIndexedDBAvailable(): boolean;
28
+ /**
29
+ * Save current session checkpoint to IndexedDB
30
+ * Called periodically to backup session data
31
+ */
32
+ export declare function saveSessionCheckpoint(sessionId: string, logs: LogEntry[], rrwebEvents: eventWithTime[], sessionData: {
33
+ startTime: number;
34
+ url: string;
35
+ title: string;
36
+ userId?: string;
37
+ userTraits?: Record<string, any>;
38
+ metadata?: Record<string, any>;
39
+ }): Promise<void>;
40
+ /**
41
+ * Get current session backup from IndexedDB
42
+ */
43
+ export declare function getCurrentSessionBackup(): Promise<CurrentSessionBackup | null>;
44
+ /**
45
+ * Clear current session backup (called after successful send)
46
+ */
47
+ export declare function clearCurrentSessionBackup(): Promise<void>;
48
+ /**
49
+ * Add a session to pending queue (for retry later)
50
+ */
51
+ export declare function addPendingSession(payload: SessionPayload): Promise<void>;
52
+ /**
53
+ * Get all pending sessions
54
+ */
55
+ export declare function getPendingSessions(): Promise<StoredSession[]>;
56
+ /**
57
+ * Remove a pending session (after successful send)
58
+ */
59
+ export declare function removePendingSession(sessionId: string): Promise<void>;
60
+ /**
61
+ * Update retry count for a pending session
62
+ */
63
+ export declare function updatePendingSessionRetry(sessionId: string): Promise<void>;
64
+ /**
65
+ * Clean up old pending sessions (older than 7 days or too many retries)
66
+ */
67
+ export declare function cleanupOldPendingSessions(): Promise<void>;
68
+ /**
69
+ * Close database connection
70
+ */
71
+ export declare function closeDB(): void;
72
+ export {};