rusty-replay 1.0.8 → 1.0.9
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.cjs.map +1 -1
- package/dist/index.d.cts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.4.0_jiti@2.4.2_postcss@8.5.3_typescript@5.8.3/node_modules/tsup/assets/cjs_shims.js","../src/environment.ts","../src/error-batcher.ts","../src/recorder.ts","../src/handler.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","export function getBrowserInfo() {\n const ua = navigator.userAgent;\n let browser = 'unknown',\n os = 'unknown';\n\n if (ua.includes('Firefox')) browser = 'Firefox';\n else if (ua.includes('SamsungBrowser')) browser = 'Samsung Browser';\n else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'Opera';\n else if (ua.includes('Trident')) browser = 'IE';\n else if (ua.includes('Edge')) browser = 'Edge (Legacy)';\n else if (ua.includes('Edg')) browser = 'Edge';\n else if (ua.includes('Chrome')) browser = 'Chrome';\n else if (ua.includes('Safari')) browser = 'Safari';\n\n if (ua.includes('Windows')) os = 'Windows';\n else if (ua.includes('Mac')) os = 'macOS';\n else if (ua.includes('Linux')) os = 'Linux';\n else if (ua.includes('Android')) os = 'Android';\n else if (ua.includes('like Mac')) os = 'iOS';\n\n return { browser, os, userAgent: ua };\n}\n\nexport function getEnvironment(): 'development' | 'staging' | 'production' {\n if (process.env.NODE_ENV === 'development') return 'development';\n if (process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') return 'staging';\n return 'production';\n}\n","import axios from 'axios';\nimport type { BatcherOptions, BatchedEvent } from './reporter';\n\nexport class ErrorBatcher {\n private queue: BatchedEvent[] = [];\n private isFlushing = false;\n private flushTimer: number;\n private readonly apiKey: string;\n\n constructor(private opts: BatcherOptions) {\n this.apiKey = opts.apiKey;\n const interval = opts.flushIntervalMs ?? 3000;\n this.flushTimer = window.setInterval(() => this.flush(), interval);\n window.addEventListener('beforeunload', () => this.flushOnUnload());\n }\n\n public getApiKey(): string {\n return this.apiKey;\n }\n\n public capture(evt: Omit<BatchedEvent, 'id' | 'timestamp'>): string {\n const id = this.makeId();\n const timestamp = new Date().toISOString();\n const record: BatchedEvent = { id, timestamp, ...evt };\n\n if (this.queue.length >= (this.opts.maxBufferSize ?? 64)) {\n this.queue.shift();\n }\n this.queue.push(record);\n return id;\n }\n\n private async flush() {\n if (this.isFlushing || this.queue.length === 0) return;\n this.isFlushing = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n try {\n await axios.post(\n this.opts.endpoint,\n { events: batch },\n {\n maxBodyLength: 1000 * 1024 * 1024, // 10MB\n maxContentLength: 1000 * 1024 * 1024, // 10MB\n timeout: 30000,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.opts.apiKey}`,\n },\n }\n );\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.isFlushing = false;\n }\n }\n\n private flushOnUnload() {\n if (!navigator.sendBeacon || this.queue.length === 0) return;\n const payload = JSON.stringify({ events: this.queue });\n navigator.sendBeacon(this.opts.endpoint, payload);\n }\n\n private makeId() {\n return 'xxxx-xxxx-4xxx-yxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n public destroy() {\n clearInterval(this.flushTimer);\n }\n}\n","import type { eventWithTime, listenerHandler } from '@rrweb/types';\nimport { record } from 'rrweb';\nimport { pack } from '@rrweb/packer';\n\nlet events: eventWithTime[] = [];\nconst MAX_EVENTS = 1000;\nlet stopFn: listenerHandler | undefined = undefined;\n\nexport function startRecording() {\n events = [];\n stopFn?.();\n stopFn = record({\n emit(event) {\n if (event.type === 2) console.log('[rrweb] FullSnapshot 기록됨:', event);\n\n events.push(event);\n if (events.length > MAX_EVENTS) {\n events = events.slice(-MAX_EVENTS);\n }\n },\n // checkoutEveryNms: 1000, // 1초마다 체크아웃\n checkoutEveryNms: 15000, // 15초마다 한 번\n checkoutEveryNth: 100, // 100개 이벤트마다 한 번\n // packFn: pack,\n });\n}\n\nexport function getRecordedEvents(\n beforeErrorSec = 10,\n errorTime = Date.now(),\n source = events\n): eventWithTime[] {\n const sliced = source.filter(\n (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n );\n\n const snapshotCandidates = source.filter((e) => e.type === 2);\n const lastSnapshot = [...snapshotCandidates]\n .reverse()\n .find((e) => e.timestamp <= errorTime);\n\n if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n return [lastSnapshot, ...sliced];\n }\n\n if (!sliced.some((e) => e.type === 2)) {\n console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n }\n\n return sliced;\n}\n\n// export function getRecordedEvents(\n// beforeErrorSec = 10,\n// errorTime = Date.now(),\n// source = events\n// ): eventWithTime[] {\n// const sliced = source.filter(\n// (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n// );\n\n// const fullSnapshots = source.filter((e) => e.type === 2);\n// const lastSnapshot = fullSnapshots.reverse().find(\n// (e) => e.timestamp <= sliced[0]?.timestamp\n// );\n// // const lastSnapshot = fullSnapshots\n// // .reverse()\n// // .find((e) => e.timestamp <= (sliced[0]?.timestamp ?? errorTime));\n\n// if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n// return [lastSnapshot, ...sliced];\n// }\n\n// if (!sliced.some((e) => e.type === 2)) {\n// console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n// }\n\n// return sliced;\n// }\n\nexport function clearEvents() {\n events = [];\n}\n\nexport function getCurrentEvents(): eventWithTime[] {\n return events.slice();\n}\n","import { captureException } from './reporter';\n\nexport function setupGlobalErrorHandler() {\n if ((window as any).__errorHandlerSetup) return;\n\n const origOnError = window.onerror;\n window.onerror = function thisWindowOnError(\n this: Window & WindowEventHandlers,\n message: string | Event,\n source?: string,\n lineno?: number,\n colno?: number,\n error?: Error\n ): boolean {\n origOnError?.call(this, message, source, lineno, colno, error);\n captureException(\n error ??\n new Error(typeof message === 'string' ? message : 'Unknown error')\n );\n return false;\n };\n\n const origOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = function thisWindowOnRejection(\n this: Window & WindowEventHandlers,\n event: PromiseRejectionEvent\n ): any {\n origOnUnhandledRejection?.call(this, event);\n const err =\n event.reason instanceof Error\n ? event.reason\n : new Error(JSON.stringify(event.reason));\n captureException(err);\n } as typeof window.onunhandledrejection;\n\n (window as any).__errorHandlerSetup = true;\n}\n","import { getBrowserInfo, getEnvironment } from './environment';\nimport { ErrorBatcher } from './error-batcher';\nimport {\n startRecording,\n getRecordedEvents,\n getCurrentEvents,\n clearEvents,\n} from './recorder';\n\nexport interface BatchedEvent {\n id: string;\n timestamp: string;\n message: string;\n stacktrace: string;\n replay: any[];\n environment: string;\n browser: string;\n os: string;\n userAgent: string;\n userId?: number;\n additionalInfo?: Record<string, any>;\n appVersion: string;\n apiKey: string;\n}\n\nexport interface BatcherOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n}\n\nexport interface InitOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n beforeErrorSec?: number;\n}\n\nlet batcher: ErrorBatcher;\nlet globalOpts: { beforeErrorSec: number } = { beforeErrorSec: 30 };\n\nexport function init(options: InitOptions) {\n globalOpts.beforeErrorSec = options.beforeErrorSec ?? 10;\n\n batcher = new ErrorBatcher({\n endpoint: options.endpoint,\n apiKey: options.apiKey,\n flushIntervalMs: options.flushIntervalMs,\n maxBufferSize: options.maxBufferSize,\n });\n\n if (typeof window !== 'undefined') {\n const start = () => {\n startRecording(); // DOM 렌더링 후에 시작되도록 지연 실행\n import('./handler.js').then((mod) => mod.setupGlobalErrorHandler());\n };\n\n if (document.readyState === 'complete') {\n requestAnimationFrame(() => startRecording());\n } else {\n window.addEventListener('load', () => {\n requestAnimationFrame(() => startRecording());\n });\n }\n\n // if ('requestIdleCallback' in window) {\n // window.requestIdleCallback(start);\n // } else {\n // setTimeout(start, 100);\n // }\n }\n}\n\nexport function captureException(\n error: Error,\n additionalInfo?: Record<string, any>,\n userId?: number\n): string {\n const errorTime = Date.now();\n const eventsSnapshot = getCurrentEvents();\n const replay = getRecordedEvents(\n globalOpts.beforeErrorSec,\n errorTime,\n eventsSnapshot\n );\n\n clearEvents(); // 다음 에러 기록을 위해 초기화\n\n const { browser, os, userAgent } = getBrowserInfo();\n\n return batcher.capture({\n message: error.message ?? '',\n stacktrace: error.stack ?? '',\n replay,\n environment: getEnvironment(),\n browser,\n os,\n userAgent,\n userId,\n additionalInfo,\n appVersion: '1.0.0',\n apiKey: batcher.getApiKey(),\n });\n}\n","export * from './reporter';\nexport { setupGlobalErrorHandler } from './handler';\nexport { startRecording, getRecordedEvents } from './recorder';\nexport { getBrowserInfo, getEnvironment } from './environment';\nexport { ErrorBatcher } from './error-batcher';\nexport type { BatchedEvent, InitOptions } from './reporter';\n"],"mappings":"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","names":["record","axios"]}
|
1
|
+
{"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.4.0_jiti@2.4.2_postcss@8.5.3_typescript@5.8.3/node_modules/tsup/assets/cjs_shims.js","../src/environment.ts","../src/error-batcher.ts","../src/recorder.ts","../src/handler.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","export function getBrowserInfo() {\n const ua = navigator.userAgent;\n let browser = 'unknown',\n os = 'unknown';\n\n if (ua.includes('Firefox')) browser = 'Firefox';\n else if (ua.includes('SamsungBrowser')) browser = 'Samsung Browser';\n else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'Opera';\n else if (ua.includes('Trident')) browser = 'IE';\n else if (ua.includes('Edge')) browser = 'Edge (Legacy)';\n else if (ua.includes('Edg')) browser = 'Edge';\n else if (ua.includes('Chrome')) browser = 'Chrome';\n else if (ua.includes('Safari')) browser = 'Safari';\n\n if (ua.includes('Windows')) os = 'Windows';\n else if (ua.includes('Mac')) os = 'macOS';\n else if (ua.includes('Linux')) os = 'Linux';\n else if (ua.includes('Android')) os = 'Android';\n else if (ua.includes('like Mac')) os = 'iOS';\n\n return { browser, os, userAgent: ua };\n}\n\nexport function getEnvironment(): 'development' | 'staging' | 'production' {\n if (process.env.NODE_ENV === 'development') return 'development';\n if (process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') return 'staging';\n return 'production';\n}\n","import axios from 'axios';\nimport type { BatcherOptions, BatchedEvent } from './reporter';\n\nexport class ErrorBatcher {\n private queue: BatchedEvent[] = [];\n private isFlushing = false;\n private flushTimer: number;\n private readonly apiKey: string;\n\n constructor(private opts: BatcherOptions) {\n this.apiKey = opts.apiKey;\n const interval = opts.flushIntervalMs ?? 3000;\n this.flushTimer = window.setInterval(() => this.flush(), interval);\n window.addEventListener('beforeunload', () => this.flushOnUnload());\n }\n\n public getApiKey(): string {\n return this.apiKey;\n }\n\n public capture(evt: Omit<BatchedEvent, 'id' | 'timestamp'>): string {\n const id = this.makeId();\n const timestamp = new Date().toISOString();\n const record: BatchedEvent = { id, timestamp, ...evt };\n\n if (this.queue.length >= (this.opts.maxBufferSize ?? 64)) {\n this.queue.shift();\n }\n this.queue.push(record);\n return id;\n }\n\n private async flush() {\n if (this.isFlushing || this.queue.length === 0) return;\n this.isFlushing = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n try {\n await axios.post(\n this.opts.endpoint,\n { events: batch },\n {\n maxBodyLength: 1000 * 1024 * 1024, // 10MB\n maxContentLength: 1000 * 1024 * 1024, // 10MB\n timeout: 30000,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.opts.apiKey}`,\n },\n }\n );\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.isFlushing = false;\n }\n }\n\n private flushOnUnload() {\n if (!navigator.sendBeacon || this.queue.length === 0) return;\n const payload = JSON.stringify({ events: this.queue });\n navigator.sendBeacon(this.opts.endpoint, payload);\n }\n\n private makeId() {\n return 'xxxx-xxxx-4xxx-yxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n public destroy() {\n clearInterval(this.flushTimer);\n }\n}\n","import type { eventWithTime, listenerHandler } from '@rrweb/types';\nimport { record } from 'rrweb';\nimport { pack } from '@rrweb/packer';\n\nlet events: eventWithTime[] = [];\nconst MAX_EVENTS = 1000;\nlet stopFn: listenerHandler | undefined = undefined;\n\nexport function startRecording() {\n events = [];\n stopFn?.();\n stopFn = record({\n emit(event) {\n if (event.type === 2) console.log('[rrweb] FullSnapshot 기록됨:', event);\n\n events.push(event);\n if (events.length > MAX_EVENTS) {\n events = events.slice(-MAX_EVENTS);\n }\n },\n // checkoutEveryNms: 1000, // 1초마다 체크아웃\n checkoutEveryNms: 15000, // 15초마다 한 번\n checkoutEveryNth: 100, // 100개 이벤트마다 한 번\n // packFn: pack,\n });\n}\n\nexport function getRecordedEvents(\n beforeErrorSec = 10,\n errorTime = Date.now(),\n source = events\n): eventWithTime[] {\n const sliced = source.filter(\n (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n );\n\n const snapshotCandidates = source.filter((e) => e.type === 2);\n const lastSnapshot = [...snapshotCandidates]\n .reverse()\n .find((e) => e.timestamp <= errorTime);\n\n if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n return [lastSnapshot, ...sliced];\n }\n\n if (!sliced.some((e) => e.type === 2)) {\n console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n }\n\n return sliced;\n}\n\n// export function getRecordedEvents(\n// beforeErrorSec = 10,\n// errorTime = Date.now(),\n// source = events\n// ): eventWithTime[] {\n// const sliced = source.filter(\n// (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n// );\n\n// const fullSnapshots = source.filter((e) => e.type === 2);\n// const lastSnapshot = fullSnapshots.reverse().find(\n// (e) => e.timestamp <= sliced[0]?.timestamp\n// );\n// // const lastSnapshot = fullSnapshots\n// // .reverse()\n// // .find((e) => e.timestamp <= (sliced[0]?.timestamp ?? errorTime));\n\n// if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n// return [lastSnapshot, ...sliced];\n// }\n\n// if (!sliced.some((e) => e.type === 2)) {\n// console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n// }\n\n// return sliced;\n// }\n\nexport function clearEvents() {\n events = [];\n}\n\nexport function getCurrentEvents(): eventWithTime[] {\n return events.slice();\n}\n","import { captureException } from './reporter';\n\nexport function setupGlobalErrorHandler() {\n if ((window as any).__errorHandlerSetup) return;\n\n const origOnError = window.onerror;\n window.onerror = function thisWindowOnError(\n this: Window & WindowEventHandlers,\n message: string | Event,\n source?: string,\n lineno?: number,\n colno?: number,\n error?: Error\n ): boolean {\n origOnError?.call(this, message, source, lineno, colno, error);\n captureException(\n error ??\n new Error(typeof message === 'string' ? message : 'Unknown error')\n );\n return false;\n };\n\n const origOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = function thisWindowOnRejection(\n this: Window & WindowEventHandlers,\n event: PromiseRejectionEvent\n ): any {\n origOnUnhandledRejection?.call(this, event);\n const err =\n event.reason instanceof Error\n ? event.reason\n : new Error(JSON.stringify(event.reason));\n captureException(err);\n } as typeof window.onunhandledrejection;\n\n (window as any).__errorHandlerSetup = true;\n}\n","import { getBrowserInfo, getEnvironment } from './environment';\nimport { ErrorBatcher } from './error-batcher';\nimport {\n startRecording,\n getRecordedEvents,\n getCurrentEvents,\n clearEvents,\n} from './recorder';\n\nexport interface AdditionalInfo {\n pageUrl: string;\n request: {\n url: string;\n method: string;\n headers: Record<string, string>;\n };\n response: {\n data: {\n message: string;\n errorCode: string;\n };\n status: number;\n statusText: string;\n };\n}\n\nexport interface BatchedEvent {\n id: string;\n timestamp: string;\n message: string;\n stacktrace: string;\n replay: any[];\n environment: string;\n browser: string;\n os: string;\n userAgent: string;\n userId?: number;\n additionalInfo?: AdditionalInfo;\n appVersion: string;\n apiKey: string;\n}\n\nexport interface BatcherOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n}\n\nexport interface InitOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n beforeErrorSec?: number;\n}\n\nlet batcher: ErrorBatcher;\nlet globalOpts: { beforeErrorSec: number } = { beforeErrorSec: 30 };\n\nexport function init(options: InitOptions) {\n globalOpts.beforeErrorSec = options.beforeErrorSec ?? 10;\n\n batcher = new ErrorBatcher({\n endpoint: options.endpoint,\n apiKey: options.apiKey,\n flushIntervalMs: options.flushIntervalMs,\n maxBufferSize: options.maxBufferSize,\n });\n\n if (typeof window !== 'undefined') {\n const start = () => {\n startRecording(); // DOM 렌더링 후에 시작되도록 지연 실행\n import('./handler.js').then((mod) => mod.setupGlobalErrorHandler());\n };\n\n if (document.readyState === 'complete') {\n requestAnimationFrame(() => startRecording());\n } else {\n window.addEventListener('load', () => {\n requestAnimationFrame(() => startRecording());\n });\n }\n\n // if ('requestIdleCallback' in window) {\n // window.requestIdleCallback(start);\n // } else {\n // setTimeout(start, 100);\n // }\n }\n}\n\nexport function captureException(\n error: Error,\n additionalInfo?: AdditionalInfo,\n userId?: number\n): string {\n const errorTime = Date.now();\n const eventsSnapshot = getCurrentEvents();\n const replay = getRecordedEvents(\n globalOpts.beforeErrorSec,\n errorTime,\n eventsSnapshot\n );\n\n clearEvents(); // 다음 에러 기록을 위해 초기화\n\n const { browser, os, userAgent } = getBrowserInfo();\n\n return batcher.capture({\n message: error.message ?? '',\n stacktrace: error.stack ?? '',\n replay,\n environment: getEnvironment(),\n browser,\n os,\n userAgent,\n userId,\n additionalInfo,\n appVersion: '1.0.0',\n apiKey: batcher.getApiKey(),\n });\n}\n","export * from './reporter';\nexport { setupGlobalErrorHandler } from './handler';\nexport { startRecording, getRecordedEvents } from './recorder';\nexport { getBrowserInfo, getEnvironment } from './environment';\nexport { ErrorBatcher } from './error-batcher';\nexport type { BatchedEvent, InitOptions, AdditionalInfo } from './reporter';\n"],"mappings":"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","names":["record","axios"]}
|
package/dist/index.d.cts
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
import { eventWithTime } from '@rrweb/types';
|
2
2
|
|
3
|
+
interface AdditionalInfo {
|
4
|
+
pageUrl: string;
|
5
|
+
request: {
|
6
|
+
url: string;
|
7
|
+
method: string;
|
8
|
+
headers: Record<string, string>;
|
9
|
+
};
|
10
|
+
response: {
|
11
|
+
data: {
|
12
|
+
message: string;
|
13
|
+
errorCode: string;
|
14
|
+
};
|
15
|
+
status: number;
|
16
|
+
statusText: string;
|
17
|
+
};
|
18
|
+
}
|
3
19
|
interface BatchedEvent {
|
4
20
|
id: string;
|
5
21
|
timestamp: string;
|
@@ -11,7 +27,7 @@ interface BatchedEvent {
|
|
11
27
|
os: string;
|
12
28
|
userAgent: string;
|
13
29
|
userId?: number;
|
14
|
-
additionalInfo?:
|
30
|
+
additionalInfo?: AdditionalInfo;
|
15
31
|
appVersion: string;
|
16
32
|
apiKey: string;
|
17
33
|
}
|
@@ -29,7 +45,7 @@ interface InitOptions {
|
|
29
45
|
beforeErrorSec?: number;
|
30
46
|
}
|
31
47
|
declare function init(options: InitOptions): void;
|
32
|
-
declare function captureException(error: Error, additionalInfo?:
|
48
|
+
declare function captureException(error: Error, additionalInfo?: AdditionalInfo, userId?: number): string;
|
33
49
|
|
34
50
|
declare function setupGlobalErrorHandler(): void;
|
35
51
|
|
@@ -58,4 +74,4 @@ declare class ErrorBatcher {
|
|
58
74
|
destroy(): void;
|
59
75
|
}
|
60
76
|
|
61
|
-
export { type BatchedEvent, type BatcherOptions, ErrorBatcher, type InitOptions, captureException, getBrowserInfo, getEnvironment, getRecordedEvents, init, setupGlobalErrorHandler, startRecording };
|
77
|
+
export { type AdditionalInfo, type BatchedEvent, type BatcherOptions, ErrorBatcher, type InitOptions, captureException, getBrowserInfo, getEnvironment, getRecordedEvents, init, setupGlobalErrorHandler, startRecording };
|
package/dist/index.d.ts
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
import { eventWithTime } from '@rrweb/types';
|
2
2
|
|
3
|
+
interface AdditionalInfo {
|
4
|
+
pageUrl: string;
|
5
|
+
request: {
|
6
|
+
url: string;
|
7
|
+
method: string;
|
8
|
+
headers: Record<string, string>;
|
9
|
+
};
|
10
|
+
response: {
|
11
|
+
data: {
|
12
|
+
message: string;
|
13
|
+
errorCode: string;
|
14
|
+
};
|
15
|
+
status: number;
|
16
|
+
statusText: string;
|
17
|
+
};
|
18
|
+
}
|
3
19
|
interface BatchedEvent {
|
4
20
|
id: string;
|
5
21
|
timestamp: string;
|
@@ -11,7 +27,7 @@ interface BatchedEvent {
|
|
11
27
|
os: string;
|
12
28
|
userAgent: string;
|
13
29
|
userId?: number;
|
14
|
-
additionalInfo?:
|
30
|
+
additionalInfo?: AdditionalInfo;
|
15
31
|
appVersion: string;
|
16
32
|
apiKey: string;
|
17
33
|
}
|
@@ -29,7 +45,7 @@ interface InitOptions {
|
|
29
45
|
beforeErrorSec?: number;
|
30
46
|
}
|
31
47
|
declare function init(options: InitOptions): void;
|
32
|
-
declare function captureException(error: Error, additionalInfo?:
|
48
|
+
declare function captureException(error: Error, additionalInfo?: AdditionalInfo, userId?: number): string;
|
33
49
|
|
34
50
|
declare function setupGlobalErrorHandler(): void;
|
35
51
|
|
@@ -58,4 +74,4 @@ declare class ErrorBatcher {
|
|
58
74
|
destroy(): void;
|
59
75
|
}
|
60
76
|
|
61
|
-
export { type BatchedEvent, type BatcherOptions, ErrorBatcher, type InitOptions, captureException, getBrowserInfo, getEnvironment, getRecordedEvents, init, setupGlobalErrorHandler, startRecording };
|
77
|
+
export { type AdditionalInfo, type BatchedEvent, type BatcherOptions, ErrorBatcher, type InitOptions, captureException, getBrowserInfo, getEnvironment, getRecordedEvents, init, setupGlobalErrorHandler, startRecording };
|
package/dist/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.4.0_jiti@2.4.2_postcss@8.5.3_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js","../src/environment.ts","../src/error-batcher.ts","../src/recorder.ts","../src/handler.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport { fileURLToPath } from 'url'\nimport path from 'path'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","export function getBrowserInfo() {\n const ua = navigator.userAgent;\n let browser = 'unknown',\n os = 'unknown';\n\n if (ua.includes('Firefox')) browser = 'Firefox';\n else if (ua.includes('SamsungBrowser')) browser = 'Samsung Browser';\n else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'Opera';\n else if (ua.includes('Trident')) browser = 'IE';\n else if (ua.includes('Edge')) browser = 'Edge (Legacy)';\n else if (ua.includes('Edg')) browser = 'Edge';\n else if (ua.includes('Chrome')) browser = 'Chrome';\n else if (ua.includes('Safari')) browser = 'Safari';\n\n if (ua.includes('Windows')) os = 'Windows';\n else if (ua.includes('Mac')) os = 'macOS';\n else if (ua.includes('Linux')) os = 'Linux';\n else if (ua.includes('Android')) os = 'Android';\n else if (ua.includes('like Mac')) os = 'iOS';\n\n return { browser, os, userAgent: ua };\n}\n\nexport function getEnvironment(): 'development' | 'staging' | 'production' {\n if (process.env.NODE_ENV === 'development') return 'development';\n if (process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') return 'staging';\n return 'production';\n}\n","import axios from 'axios';\nimport type { BatcherOptions, BatchedEvent } from './reporter';\n\nexport class ErrorBatcher {\n private queue: BatchedEvent[] = [];\n private isFlushing = false;\n private flushTimer: number;\n private readonly apiKey: string;\n\n constructor(private opts: BatcherOptions) {\n this.apiKey = opts.apiKey;\n const interval = opts.flushIntervalMs ?? 3000;\n this.flushTimer = window.setInterval(() => this.flush(), interval);\n window.addEventListener('beforeunload', () => this.flushOnUnload());\n }\n\n public getApiKey(): string {\n return this.apiKey;\n }\n\n public capture(evt: Omit<BatchedEvent, 'id' | 'timestamp'>): string {\n const id = this.makeId();\n const timestamp = new Date().toISOString();\n const record: BatchedEvent = { id, timestamp, ...evt };\n\n if (this.queue.length >= (this.opts.maxBufferSize ?? 64)) {\n this.queue.shift();\n }\n this.queue.push(record);\n return id;\n }\n\n private async flush() {\n if (this.isFlushing || this.queue.length === 0) return;\n this.isFlushing = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n try {\n await axios.post(\n this.opts.endpoint,\n { events: batch },\n {\n maxBodyLength: 1000 * 1024 * 1024, // 10MB\n maxContentLength: 1000 * 1024 * 1024, // 10MB\n timeout: 30000,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.opts.apiKey}`,\n },\n }\n );\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.isFlushing = false;\n }\n }\n\n private flushOnUnload() {\n if (!navigator.sendBeacon || this.queue.length === 0) return;\n const payload = JSON.stringify({ events: this.queue });\n navigator.sendBeacon(this.opts.endpoint, payload);\n }\n\n private makeId() {\n return 'xxxx-xxxx-4xxx-yxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n public destroy() {\n clearInterval(this.flushTimer);\n }\n}\n","import type { eventWithTime, listenerHandler } from '@rrweb/types';\nimport { record } from 'rrweb';\nimport { pack } from '@rrweb/packer';\n\nlet events: eventWithTime[] = [];\nconst MAX_EVENTS = 1000;\nlet stopFn: listenerHandler | undefined = undefined;\n\nexport function startRecording() {\n events = [];\n stopFn?.();\n stopFn = record({\n emit(event) {\n if (event.type === 2) console.log('[rrweb] FullSnapshot 기록됨:', event);\n\n events.push(event);\n if (events.length > MAX_EVENTS) {\n events = events.slice(-MAX_EVENTS);\n }\n },\n // checkoutEveryNms: 1000, // 1초마다 체크아웃\n checkoutEveryNms: 15000, // 15초마다 한 번\n checkoutEveryNth: 100, // 100개 이벤트마다 한 번\n // packFn: pack,\n });\n}\n\nexport function getRecordedEvents(\n beforeErrorSec = 10,\n errorTime = Date.now(),\n source = events\n): eventWithTime[] {\n const sliced = source.filter(\n (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n );\n\n const snapshotCandidates = source.filter((e) => e.type === 2);\n const lastSnapshot = [...snapshotCandidates]\n .reverse()\n .find((e) => e.timestamp <= errorTime);\n\n if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n return [lastSnapshot, ...sliced];\n }\n\n if (!sliced.some((e) => e.type === 2)) {\n console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n }\n\n return sliced;\n}\n\n// export function getRecordedEvents(\n// beforeErrorSec = 10,\n// errorTime = Date.now(),\n// source = events\n// ): eventWithTime[] {\n// const sliced = source.filter(\n// (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n// );\n\n// const fullSnapshots = source.filter((e) => e.type === 2);\n// const lastSnapshot = fullSnapshots.reverse().find(\n// (e) => e.timestamp <= sliced[0]?.timestamp\n// );\n// // const lastSnapshot = fullSnapshots\n// // .reverse()\n// // .find((e) => e.timestamp <= (sliced[0]?.timestamp ?? errorTime));\n\n// if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n// return [lastSnapshot, ...sliced];\n// }\n\n// if (!sliced.some((e) => e.type === 2)) {\n// console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n// }\n\n// return sliced;\n// }\n\nexport function clearEvents() {\n events = [];\n}\n\nexport function getCurrentEvents(): eventWithTime[] {\n return events.slice();\n}\n","import { captureException } from './reporter';\n\nexport function setupGlobalErrorHandler() {\n if ((window as any).__errorHandlerSetup) return;\n\n const origOnError = window.onerror;\n window.onerror = function thisWindowOnError(\n this: Window & WindowEventHandlers,\n message: string | Event,\n source?: string,\n lineno?: number,\n colno?: number,\n error?: Error\n ): boolean {\n origOnError?.call(this, message, source, lineno, colno, error);\n captureException(\n error ??\n new Error(typeof message === 'string' ? message : 'Unknown error')\n );\n return false;\n };\n\n const origOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = function thisWindowOnRejection(\n this: Window & WindowEventHandlers,\n event: PromiseRejectionEvent\n ): any {\n origOnUnhandledRejection?.call(this, event);\n const err =\n event.reason instanceof Error\n ? event.reason\n : new Error(JSON.stringify(event.reason));\n captureException(err);\n } as typeof window.onunhandledrejection;\n\n (window as any).__errorHandlerSetup = true;\n}\n","import { getBrowserInfo, getEnvironment } from './environment';\nimport { ErrorBatcher } from './error-batcher';\nimport {\n startRecording,\n getRecordedEvents,\n getCurrentEvents,\n clearEvents,\n} from './recorder';\n\nexport interface BatchedEvent {\n id: string;\n timestamp: string;\n message: string;\n stacktrace: string;\n replay: any[];\n environment: string;\n browser: string;\n os: string;\n userAgent: string;\n userId?: number;\n additionalInfo?: Record<string, any>;\n appVersion: string;\n apiKey: string;\n}\n\nexport interface BatcherOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n}\n\nexport interface InitOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n beforeErrorSec?: number;\n}\n\nlet batcher: ErrorBatcher;\nlet globalOpts: { beforeErrorSec: number } = { beforeErrorSec: 30 };\n\nexport function init(options: InitOptions) {\n globalOpts.beforeErrorSec = options.beforeErrorSec ?? 10;\n\n batcher = new ErrorBatcher({\n endpoint: options.endpoint,\n apiKey: options.apiKey,\n flushIntervalMs: options.flushIntervalMs,\n maxBufferSize: options.maxBufferSize,\n });\n\n if (typeof window !== 'undefined') {\n const start = () => {\n startRecording(); // DOM 렌더링 후에 시작되도록 지연 실행\n import('./handler.js').then((mod) => mod.setupGlobalErrorHandler());\n };\n\n if (document.readyState === 'complete') {\n requestAnimationFrame(() => startRecording());\n } else {\n window.addEventListener('load', () => {\n requestAnimationFrame(() => startRecording());\n });\n }\n\n // if ('requestIdleCallback' in window) {\n // window.requestIdleCallback(start);\n // } else {\n // setTimeout(start, 100);\n // }\n }\n}\n\nexport function captureException(\n error: Error,\n additionalInfo?: Record<string, any>,\n userId?: number\n): string {\n const errorTime = Date.now();\n const eventsSnapshot = getCurrentEvents();\n const replay = getRecordedEvents(\n globalOpts.beforeErrorSec,\n errorTime,\n eventsSnapshot\n );\n\n clearEvents(); // 다음 에러 기록을 위해 초기화\n\n const { browser, os, userAgent } = getBrowserInfo();\n\n return batcher.capture({\n message: error.message ?? '',\n stacktrace: error.stack ?? '',\n replay,\n environment: getEnvironment(),\n browser,\n os,\n userAgent,\n userId,\n additionalInfo,\n appVersion: '1.0.0',\n apiKey: batcher.getApiKey(),\n });\n}\n","export * from './reporter';\nexport { setupGlobalErrorHandler } from './handler';\nexport { startRecording, getRecordedEvents } from './recorder';\nexport { getBrowserInfo, getEnvironment } from './environment';\nexport { ErrorBatcher } from './error-batcher';\nexport type { BatchedEvent, InitOptions } from './reporter';\n"],"mappings":"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","names":["record"]}
|
1
|
+
{"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.4.0_jiti@2.4.2_postcss@8.5.3_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js","../src/environment.ts","../src/error-batcher.ts","../src/recorder.ts","../src/handler.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport { fileURLToPath } from 'url'\nimport path from 'path'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","export function getBrowserInfo() {\n const ua = navigator.userAgent;\n let browser = 'unknown',\n os = 'unknown';\n\n if (ua.includes('Firefox')) browser = 'Firefox';\n else if (ua.includes('SamsungBrowser')) browser = 'Samsung Browser';\n else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'Opera';\n else if (ua.includes('Trident')) browser = 'IE';\n else if (ua.includes('Edge')) browser = 'Edge (Legacy)';\n else if (ua.includes('Edg')) browser = 'Edge';\n else if (ua.includes('Chrome')) browser = 'Chrome';\n else if (ua.includes('Safari')) browser = 'Safari';\n\n if (ua.includes('Windows')) os = 'Windows';\n else if (ua.includes('Mac')) os = 'macOS';\n else if (ua.includes('Linux')) os = 'Linux';\n else if (ua.includes('Android')) os = 'Android';\n else if (ua.includes('like Mac')) os = 'iOS';\n\n return { browser, os, userAgent: ua };\n}\n\nexport function getEnvironment(): 'development' | 'staging' | 'production' {\n if (process.env.NODE_ENV === 'development') return 'development';\n if (process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') return 'staging';\n return 'production';\n}\n","import axios from 'axios';\nimport type { BatcherOptions, BatchedEvent } from './reporter';\n\nexport class ErrorBatcher {\n private queue: BatchedEvent[] = [];\n private isFlushing = false;\n private flushTimer: number;\n private readonly apiKey: string;\n\n constructor(private opts: BatcherOptions) {\n this.apiKey = opts.apiKey;\n const interval = opts.flushIntervalMs ?? 3000;\n this.flushTimer = window.setInterval(() => this.flush(), interval);\n window.addEventListener('beforeunload', () => this.flushOnUnload());\n }\n\n public getApiKey(): string {\n return this.apiKey;\n }\n\n public capture(evt: Omit<BatchedEvent, 'id' | 'timestamp'>): string {\n const id = this.makeId();\n const timestamp = new Date().toISOString();\n const record: BatchedEvent = { id, timestamp, ...evt };\n\n if (this.queue.length >= (this.opts.maxBufferSize ?? 64)) {\n this.queue.shift();\n }\n this.queue.push(record);\n return id;\n }\n\n private async flush() {\n if (this.isFlushing || this.queue.length === 0) return;\n this.isFlushing = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n try {\n await axios.post(\n this.opts.endpoint,\n { events: batch },\n {\n maxBodyLength: 1000 * 1024 * 1024, // 10MB\n maxContentLength: 1000 * 1024 * 1024, // 10MB\n timeout: 30000,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.opts.apiKey}`,\n },\n }\n );\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.isFlushing = false;\n }\n }\n\n private flushOnUnload() {\n if (!navigator.sendBeacon || this.queue.length === 0) return;\n const payload = JSON.stringify({ events: this.queue });\n navigator.sendBeacon(this.opts.endpoint, payload);\n }\n\n private makeId() {\n return 'xxxx-xxxx-4xxx-yxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n public destroy() {\n clearInterval(this.flushTimer);\n }\n}\n","import type { eventWithTime, listenerHandler } from '@rrweb/types';\nimport { record } from 'rrweb';\nimport { pack } from '@rrweb/packer';\n\nlet events: eventWithTime[] = [];\nconst MAX_EVENTS = 1000;\nlet stopFn: listenerHandler | undefined = undefined;\n\nexport function startRecording() {\n events = [];\n stopFn?.();\n stopFn = record({\n emit(event) {\n if (event.type === 2) console.log('[rrweb] FullSnapshot 기록됨:', event);\n\n events.push(event);\n if (events.length > MAX_EVENTS) {\n events = events.slice(-MAX_EVENTS);\n }\n },\n // checkoutEveryNms: 1000, // 1초마다 체크아웃\n checkoutEveryNms: 15000, // 15초마다 한 번\n checkoutEveryNth: 100, // 100개 이벤트마다 한 번\n // packFn: pack,\n });\n}\n\nexport function getRecordedEvents(\n beforeErrorSec = 10,\n errorTime = Date.now(),\n source = events\n): eventWithTime[] {\n const sliced = source.filter(\n (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n );\n\n const snapshotCandidates = source.filter((e) => e.type === 2);\n const lastSnapshot = [...snapshotCandidates]\n .reverse()\n .find((e) => e.timestamp <= errorTime);\n\n if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n return [lastSnapshot, ...sliced];\n }\n\n if (!sliced.some((e) => e.type === 2)) {\n console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n }\n\n return sliced;\n}\n\n// export function getRecordedEvents(\n// beforeErrorSec = 10,\n// errorTime = Date.now(),\n// source = events\n// ): eventWithTime[] {\n// const sliced = source.filter(\n// (e) => errorTime - e.timestamp < beforeErrorSec * 1000\n// );\n\n// const fullSnapshots = source.filter((e) => e.type === 2);\n// const lastSnapshot = fullSnapshots.reverse().find(\n// (e) => e.timestamp <= sliced[0]?.timestamp\n// );\n// // const lastSnapshot = fullSnapshots\n// // .reverse()\n// // .find((e) => e.timestamp <= (sliced[0]?.timestamp ?? errorTime));\n\n// if (lastSnapshot && !sliced.includes(lastSnapshot)) {\n// return [lastSnapshot, ...sliced];\n// }\n\n// if (!sliced.some((e) => e.type === 2)) {\n// console.warn('⚠️ Snapshot 없이 잘린 replay입니다. 복원 불가능할 수 있음.');\n// }\n\n// return sliced;\n// }\n\nexport function clearEvents() {\n events = [];\n}\n\nexport function getCurrentEvents(): eventWithTime[] {\n return events.slice();\n}\n","import { captureException } from './reporter';\n\nexport function setupGlobalErrorHandler() {\n if ((window as any).__errorHandlerSetup) return;\n\n const origOnError = window.onerror;\n window.onerror = function thisWindowOnError(\n this: Window & WindowEventHandlers,\n message: string | Event,\n source?: string,\n lineno?: number,\n colno?: number,\n error?: Error\n ): boolean {\n origOnError?.call(this, message, source, lineno, colno, error);\n captureException(\n error ??\n new Error(typeof message === 'string' ? message : 'Unknown error')\n );\n return false;\n };\n\n const origOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = function thisWindowOnRejection(\n this: Window & WindowEventHandlers,\n event: PromiseRejectionEvent\n ): any {\n origOnUnhandledRejection?.call(this, event);\n const err =\n event.reason instanceof Error\n ? event.reason\n : new Error(JSON.stringify(event.reason));\n captureException(err);\n } as typeof window.onunhandledrejection;\n\n (window as any).__errorHandlerSetup = true;\n}\n","import { getBrowserInfo, getEnvironment } from './environment';\nimport { ErrorBatcher } from './error-batcher';\nimport {\n startRecording,\n getRecordedEvents,\n getCurrentEvents,\n clearEvents,\n} from './recorder';\n\nexport interface AdditionalInfo {\n pageUrl: string;\n request: {\n url: string;\n method: string;\n headers: Record<string, string>;\n };\n response: {\n data: {\n message: string;\n errorCode: string;\n };\n status: number;\n statusText: string;\n };\n}\n\nexport interface BatchedEvent {\n id: string;\n timestamp: string;\n message: string;\n stacktrace: string;\n replay: any[];\n environment: string;\n browser: string;\n os: string;\n userAgent: string;\n userId?: number;\n additionalInfo?: AdditionalInfo;\n appVersion: string;\n apiKey: string;\n}\n\nexport interface BatcherOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n}\n\nexport interface InitOptions {\n endpoint: string;\n apiKey: string;\n flushIntervalMs?: number;\n maxBufferSize?: number;\n beforeErrorSec?: number;\n}\n\nlet batcher: ErrorBatcher;\nlet globalOpts: { beforeErrorSec: number } = { beforeErrorSec: 30 };\n\nexport function init(options: InitOptions) {\n globalOpts.beforeErrorSec = options.beforeErrorSec ?? 10;\n\n batcher = new ErrorBatcher({\n endpoint: options.endpoint,\n apiKey: options.apiKey,\n flushIntervalMs: options.flushIntervalMs,\n maxBufferSize: options.maxBufferSize,\n });\n\n if (typeof window !== 'undefined') {\n const start = () => {\n startRecording(); // DOM 렌더링 후에 시작되도록 지연 실행\n import('./handler.js').then((mod) => mod.setupGlobalErrorHandler());\n };\n\n if (document.readyState === 'complete') {\n requestAnimationFrame(() => startRecording());\n } else {\n window.addEventListener('load', () => {\n requestAnimationFrame(() => startRecording());\n });\n }\n\n // if ('requestIdleCallback' in window) {\n // window.requestIdleCallback(start);\n // } else {\n // setTimeout(start, 100);\n // }\n }\n}\n\nexport function captureException(\n error: Error,\n additionalInfo?: AdditionalInfo,\n userId?: number\n): string {\n const errorTime = Date.now();\n const eventsSnapshot = getCurrentEvents();\n const replay = getRecordedEvents(\n globalOpts.beforeErrorSec,\n errorTime,\n eventsSnapshot\n );\n\n clearEvents(); // 다음 에러 기록을 위해 초기화\n\n const { browser, os, userAgent } = getBrowserInfo();\n\n return batcher.capture({\n message: error.message ?? '',\n stacktrace: error.stack ?? '',\n replay,\n environment: getEnvironment(),\n browser,\n os,\n userAgent,\n userId,\n additionalInfo,\n appVersion: '1.0.0',\n apiKey: batcher.getApiKey(),\n });\n}\n","export * from './reporter';\nexport { setupGlobalErrorHandler } from './handler';\nexport { startRecording, getRecordedEvents } from './recorder';\nexport { getBrowserInfo, getEnvironment } from './environment';\nexport { ErrorBatcher } from './error-batcher';\nexport type { BatchedEvent, InitOptions, AdditionalInfo } from './reporter';\n"],"mappings":"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","names":["record"]}
|