rusty-replay 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/dist/index.cjs ADDED
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __esm = (fn, res) => function __init() {
23
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
24
+ };
25
+ var __export = (target, all) => {
26
+ for (var name in all)
27
+ __defProp(target, name, { get: all[name], enumerable: true });
28
+ };
29
+ var __copyProps = (to, from, except, desc) => {
30
+ if (from && typeof from === "object" || typeof from === "function") {
31
+ for (let key of __getOwnPropNames(from))
32
+ if (!__hasOwnProp.call(to, key) && key !== except)
33
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
38
+ // If the importer is in node compatibility mode or this is not an ESM
39
+ // file that has been converted to a CommonJS file using a Babel-
40
+ // compatible transform (i.e. "__esModule" has not been set), then set
41
+ // "default" to the CommonJS "module.exports" for node compatibility.
42
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
43
+ mod
44
+ ));
45
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
46
+
47
+ // ../../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
48
+ var init_cjs_shims = __esm({
49
+ "../../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"() {
50
+ "use strict";
51
+ }
52
+ });
53
+
54
+ // src/recorder.ts
55
+ function startRecording() {
56
+ events = [];
57
+ (0, import_rrweb.record)({
58
+ emit(event) {
59
+ events.push(event);
60
+ if (events.length > MAX_EVENTS) {
61
+ events = events.slice(-MAX_EVENTS);
62
+ }
63
+ }
64
+ });
65
+ }
66
+ function getRecordedEvents(beforeErrorSec = 30) {
67
+ const now = Date.now();
68
+ return events.filter((e) => now - e.timestamp < beforeErrorSec * 1e3);
69
+ }
70
+ var import_rrweb, events, MAX_EVENTS;
71
+ var init_recorder = __esm({
72
+ "src/recorder.ts"() {
73
+ "use strict";
74
+ init_cjs_shims();
75
+ import_rrweb = require("rrweb");
76
+ events = [];
77
+ MAX_EVENTS = 1e3;
78
+ }
79
+ });
80
+
81
+ // src/environment.ts
82
+ function getBrowserInfo() {
83
+ const ua = navigator.userAgent;
84
+ let browser = "unknown", os = "unknown";
85
+ if (ua.includes("Firefox")) browser = "Firefox";
86
+ else if (ua.includes("SamsungBrowser")) browser = "Samsung Browser";
87
+ else if (ua.includes("Opera") || ua.includes("OPR")) browser = "Opera";
88
+ else if (ua.includes("Trident")) browser = "IE";
89
+ else if (ua.includes("Edge")) browser = "Edge (Legacy)";
90
+ else if (ua.includes("Edg")) browser = "Edge";
91
+ else if (ua.includes("Chrome")) browser = "Chrome";
92
+ else if (ua.includes("Safari")) browser = "Safari";
93
+ if (ua.includes("Windows")) os = "Windows";
94
+ else if (ua.includes("Mac")) os = "macOS";
95
+ else if (ua.includes("Linux")) os = "Linux";
96
+ else if (ua.includes("Android")) os = "Android";
97
+ else if (ua.includes("like Mac")) os = "iOS";
98
+ return { browser, os, userAgent: ua };
99
+ }
100
+ function getEnvironment() {
101
+ if (process.env.NODE_ENV === "development") return "development";
102
+ if (process.env.NEXT_PUBLIC_VERCEL_ENV === "preview") return "staging";
103
+ return "production";
104
+ }
105
+ var init_environment = __esm({
106
+ "src/environment.ts"() {
107
+ "use strict";
108
+ init_cjs_shims();
109
+ }
110
+ });
111
+
112
+ // src/error-batcher.ts
113
+ var import_axios, ErrorBatcher;
114
+ var init_error_batcher = __esm({
115
+ "src/error-batcher.ts"() {
116
+ "use strict";
117
+ init_cjs_shims();
118
+ import_axios = __toESM(require("axios"), 1);
119
+ ErrorBatcher = class {
120
+ constructor(opts) {
121
+ this.opts = opts;
122
+ this.queue = [];
123
+ this.isFlushing = false;
124
+ var _a;
125
+ const interval = (_a = opts.flushIntervalMs) != null ? _a : 3e3;
126
+ this.flushTimer = window.setInterval(() => this.flush(), interval);
127
+ window.addEventListener("beforeunload", () => this.flushOnUnload());
128
+ }
129
+ capture(evt) {
130
+ var _a;
131
+ const id = this.makeId();
132
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
133
+ const record2 = __spreadValues({ id, timestamp }, evt);
134
+ if (this.queue.length >= ((_a = this.opts.maxBufferSize) != null ? _a : 64)) {
135
+ this.queue.shift();
136
+ }
137
+ this.queue.push(record2);
138
+ return id;
139
+ }
140
+ async flush() {
141
+ if (this.isFlushing || this.queue.length === 0) return;
142
+ this.isFlushing = true;
143
+ const batch = this.queue.splice(0, this.queue.length);
144
+ try {
145
+ await import_axios.default.post(
146
+ this.opts.endpoint,
147
+ { events: batch },
148
+ {
149
+ headers: {
150
+ "Content-Type": "application/json",
151
+ Authorization: `Bearer ${this.opts.apiKey}`
152
+ }
153
+ }
154
+ );
155
+ } catch (e) {
156
+ this.queue.unshift(...batch);
157
+ } finally {
158
+ this.isFlushing = false;
159
+ }
160
+ }
161
+ flushOnUnload() {
162
+ if (!navigator.sendBeacon || this.queue.length === 0) return;
163
+ const payload = JSON.stringify({ events: this.queue });
164
+ navigator.sendBeacon(this.opts.endpoint, payload);
165
+ }
166
+ makeId() {
167
+ return "xxxx-xxxx-4xxx-yxxx".replace(/[xy]/g, (c) => {
168
+ const r = Math.random() * 16 | 0;
169
+ const v = c === "x" ? r : r & 3 | 8;
170
+ return v.toString(16);
171
+ });
172
+ }
173
+ destroy() {
174
+ clearInterval(this.flushTimer);
175
+ }
176
+ };
177
+ }
178
+ });
179
+
180
+ // src/handler.ts
181
+ var handler_exports = {};
182
+ __export(handler_exports, {
183
+ setupGlobalErrorHandler: () => setupGlobalErrorHandler
184
+ });
185
+ function setupGlobalErrorHandler() {
186
+ if (window.__errorHandlerSetup) return;
187
+ const origOnError = window.onerror;
188
+ window.onerror = function thisWindowOnError(message, source, lineno, colno, error) {
189
+ origOnError == null ? void 0 : origOnError.call(this, message, source, lineno, colno, error);
190
+ captureException(
191
+ error != null ? error : new Error(typeof message === "string" ? message : "Unknown error")
192
+ );
193
+ return false;
194
+ };
195
+ const origOnUnhandledRejection = window.onunhandledrejection;
196
+ window.onunhandledrejection = function thisWindowOnRejection(event) {
197
+ origOnUnhandledRejection == null ? void 0 : origOnUnhandledRejection.call(this, event);
198
+ const err = event.reason instanceof Error ? event.reason : new Error(JSON.stringify(event.reason));
199
+ captureException(err);
200
+ };
201
+ window.__errorHandlerSetup = true;
202
+ }
203
+ var init_handler = __esm({
204
+ "src/handler.ts"() {
205
+ "use strict";
206
+ init_cjs_shims();
207
+ init_reporter();
208
+ }
209
+ });
210
+
211
+ // src/reporter.ts
212
+ function init(options) {
213
+ var _a;
214
+ startRecording();
215
+ globalOpts.beforeErrorSec = (_a = options.beforeErrorSec) != null ? _a : 30;
216
+ batcher = new ErrorBatcher({
217
+ endpoint: options.endpoint,
218
+ apiKey: options.apiKey,
219
+ flushIntervalMs: options.flushIntervalMs,
220
+ maxBufferSize: options.maxBufferSize
221
+ });
222
+ Promise.resolve().then(() => (init_handler(), handler_exports)).then((mod) => mod.setupGlobalErrorHandler());
223
+ }
224
+ function captureException(error, additionalInfo, userId) {
225
+ var _a;
226
+ const { browser, os, userAgent } = getBrowserInfo();
227
+ const replay = getRecordedEvents(globalOpts.beforeErrorSec);
228
+ return batcher.capture({
229
+ message: error.message,
230
+ stack: (_a = error.stack) != null ? _a : "",
231
+ replay,
232
+ environment: getEnvironment(),
233
+ browser,
234
+ os,
235
+ userAgent,
236
+ userId,
237
+ additionalInfo
238
+ });
239
+ }
240
+ function wrap(fn, info) {
241
+ return (...args) => {
242
+ try {
243
+ return fn(...args);
244
+ } catch (err) {
245
+ if (err instanceof Error) {
246
+ captureException(err, info == null ? void 0 : info.additionalInfo, info == null ? void 0 : info.userId);
247
+ } else {
248
+ captureException(
249
+ new Error(String(err)),
250
+ info == null ? void 0 : info.additionalInfo,
251
+ info == null ? void 0 : info.userId
252
+ );
253
+ }
254
+ throw err;
255
+ }
256
+ };
257
+ }
258
+ var batcher, globalOpts;
259
+ var init_reporter = __esm({
260
+ "src/reporter.ts"() {
261
+ "use strict";
262
+ init_cjs_shims();
263
+ init_recorder();
264
+ init_environment();
265
+ init_error_batcher();
266
+ globalOpts = { beforeErrorSec: 30 };
267
+ }
268
+ });
269
+
270
+ // src/index.ts
271
+ var index_exports = {};
272
+ __export(index_exports, {
273
+ ErrorBatcher: () => ErrorBatcher,
274
+ captureException: () => captureException,
275
+ getBrowserInfo: () => getBrowserInfo,
276
+ getEnvironment: () => getEnvironment,
277
+ getRecordedEvents: () => getRecordedEvents,
278
+ init: () => init,
279
+ setupGlobalErrorHandler: () => setupGlobalErrorHandler,
280
+ startRecording: () => startRecording,
281
+ wrap: () => wrap
282
+ });
283
+ module.exports = __toCommonJS(index_exports);
284
+ init_cjs_shims();
285
+ init_reporter();
286
+ init_handler();
287
+ init_recorder();
288
+ init_environment();
289
+ init_error_batcher();
290
+ // Annotate the CommonJS export names for ESM import in node:
291
+ 0 && (module.exports = {
292
+ ErrorBatcher,
293
+ captureException,
294
+ getBrowserInfo,
295
+ getEnvironment,
296
+ getRecordedEvents,
297
+ init,
298
+ setupGlobalErrorHandler,
299
+ startRecording,
300
+ wrap
301
+ });
302
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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/recorder.ts","../src/environment.ts","../src/error-batcher.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","import type { eventWithTime } from '@rrweb/types';\nimport { record } from 'rrweb';\n\nlet events: eventWithTime[] = [];\nconst MAX_EVENTS = 1000;\n\nexport function startRecording() {\n events = [];\n record({\n emit(event) {\n events.push(event);\n if (events.length > MAX_EVENTS) {\n events = events.slice(-MAX_EVENTS);\n }\n },\n });\n}\n\nexport function getRecordedEvents(beforeErrorSec = 30): eventWithTime[] {\n const now = Date.now();\n return events.filter((e) => now - e.timestamp < beforeErrorSec * 1000);\n}\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\n constructor(private opts: BatcherOptions) {\n const interval = opts.flushIntervalMs ?? 3000;\n this.flushTimer = window.setInterval(() => this.flush(), interval);\n window.addEventListener('beforeunload', () => this.flushOnUnload());\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 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 { 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 { startRecording, getRecordedEvents } from './recorder';\nimport { getBrowserInfo, getEnvironment } from './environment';\nimport { ErrorBatcher } from './error-batcher';\n\nexport interface BatchedEvent {\n id: string;\n timestamp: string;\n message: string;\n stack: 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}\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 startRecording();\n globalOpts.beforeErrorSec = options.beforeErrorSec ?? 30;\n batcher = new ErrorBatcher({\n endpoint: options.endpoint,\n apiKey: options.apiKey,\n flushIntervalMs: options.flushIntervalMs,\n maxBufferSize: options.maxBufferSize,\n });\n import('./handler.js').then((mod) => mod.setupGlobalErrorHandler());\n}\n\nexport function captureException(\n error: Error,\n additionalInfo?: Record<string, any>,\n userId?: number\n): string {\n const { browser, os, userAgent } = getBrowserInfo();\n const replay = getRecordedEvents(globalOpts.beforeErrorSec);\n return batcher.capture({\n message: error.message,\n stack: error.stack ?? '',\n replay,\n environment: getEnvironment(),\n browser,\n os,\n userAgent,\n userId,\n additionalInfo,\n });\n}\n\n/** 기존 함수 래핑 */\nexport function wrap<T extends (...args: any[]) => any>(\n fn: T,\n info?: { additionalInfo?: Record<string, any>; userId?: number }\n): (...args: Parameters<T>) => ReturnType<T> {\n return (...args) => {\n try {\n return fn(...args);\n } catch (err) {\n if (err instanceof Error) {\n captureException(err, info?.additionalInfo, info?.userId);\n } else {\n captureException(\n new Error(String(err)),\n info?.additionalInfo,\n info?.userId\n );\n }\n throw err;\n }\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"]}
@@ -0,0 +1,62 @@
1
+ import { eventWithTime } from '@rrweb/types';
2
+
3
+ interface BatchedEvent {
4
+ id: string;
5
+ timestamp: string;
6
+ message: string;
7
+ stack: string;
8
+ replay: any[];
9
+ environment: string;
10
+ browser: string;
11
+ os: string;
12
+ userAgent: string;
13
+ userId?: number;
14
+ additionalInfo?: Record<string, any>;
15
+ }
16
+ interface BatcherOptions {
17
+ endpoint: string;
18
+ apiKey: string;
19
+ flushIntervalMs?: number;
20
+ maxBufferSize?: number;
21
+ }
22
+ interface InitOptions {
23
+ endpoint: string;
24
+ apiKey: string;
25
+ flushIntervalMs?: number;
26
+ maxBufferSize?: number;
27
+ beforeErrorSec?: number;
28
+ }
29
+ declare function init(options: InitOptions): void;
30
+ declare function captureException(error: Error, additionalInfo?: Record<string, any>, userId?: number): string;
31
+ /** 기존 함수 래핑 */
32
+ declare function wrap<T extends (...args: any[]) => any>(fn: T, info?: {
33
+ additionalInfo?: Record<string, any>;
34
+ userId?: number;
35
+ }): (...args: Parameters<T>) => ReturnType<T>;
36
+
37
+ declare function setupGlobalErrorHandler(): void;
38
+
39
+ declare function startRecording(): void;
40
+ declare function getRecordedEvents(beforeErrorSec?: number): eventWithTime[];
41
+
42
+ declare function getBrowserInfo(): {
43
+ browser: string;
44
+ os: string;
45
+ userAgent: string;
46
+ };
47
+ declare function getEnvironment(): 'development' | 'staging' | 'production';
48
+
49
+ declare class ErrorBatcher {
50
+ private opts;
51
+ private queue;
52
+ private isFlushing;
53
+ private flushTimer;
54
+ constructor(opts: BatcherOptions);
55
+ capture(evt: Omit<BatchedEvent, 'id' | 'timestamp'>): string;
56
+ private flush;
57
+ private flushOnUnload;
58
+ private makeId;
59
+ destroy(): void;
60
+ }
61
+
62
+ export { type BatchedEvent, type BatcherOptions, ErrorBatcher, type InitOptions, captureException, getBrowserInfo, getEnvironment, getRecordedEvents, init, setupGlobalErrorHandler, startRecording, wrap };
@@ -0,0 +1,62 @@
1
+ import { eventWithTime } from '@rrweb/types';
2
+
3
+ interface BatchedEvent {
4
+ id: string;
5
+ timestamp: string;
6
+ message: string;
7
+ stack: string;
8
+ replay: any[];
9
+ environment: string;
10
+ browser: string;
11
+ os: string;
12
+ userAgent: string;
13
+ userId?: number;
14
+ additionalInfo?: Record<string, any>;
15
+ }
16
+ interface BatcherOptions {
17
+ endpoint: string;
18
+ apiKey: string;
19
+ flushIntervalMs?: number;
20
+ maxBufferSize?: number;
21
+ }
22
+ interface InitOptions {
23
+ endpoint: string;
24
+ apiKey: string;
25
+ flushIntervalMs?: number;
26
+ maxBufferSize?: number;
27
+ beforeErrorSec?: number;
28
+ }
29
+ declare function init(options: InitOptions): void;
30
+ declare function captureException(error: Error, additionalInfo?: Record<string, any>, userId?: number): string;
31
+ /** 기존 함수 래핑 */
32
+ declare function wrap<T extends (...args: any[]) => any>(fn: T, info?: {
33
+ additionalInfo?: Record<string, any>;
34
+ userId?: number;
35
+ }): (...args: Parameters<T>) => ReturnType<T>;
36
+
37
+ declare function setupGlobalErrorHandler(): void;
38
+
39
+ declare function startRecording(): void;
40
+ declare function getRecordedEvents(beforeErrorSec?: number): eventWithTime[];
41
+
42
+ declare function getBrowserInfo(): {
43
+ browser: string;
44
+ os: string;
45
+ userAgent: string;
46
+ };
47
+ declare function getEnvironment(): 'development' | 'staging' | 'production';
48
+
49
+ declare class ErrorBatcher {
50
+ private opts;
51
+ private queue;
52
+ private isFlushing;
53
+ private flushTimer;
54
+ constructor(opts: BatcherOptions);
55
+ capture(evt: Omit<BatchedEvent, 'id' | 'timestamp'>): string;
56
+ private flush;
57
+ private flushOnUnload;
58
+ private makeId;
59
+ destroy(): void;
60
+ }
61
+
62
+ export { type BatchedEvent, type BatcherOptions, ErrorBatcher, type InitOptions, captureException, getBrowserInfo, getEnvironment, getRecordedEvents, init, setupGlobalErrorHandler, startRecording, wrap };
package/dist/index.js ADDED
@@ -0,0 +1,267 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __spreadValues = (a, b) => {
8
+ for (var prop in b || (b = {}))
9
+ if (__hasOwnProp.call(b, prop))
10
+ __defNormalProp(a, prop, b[prop]);
11
+ if (__getOwnPropSymbols)
12
+ for (var prop of __getOwnPropSymbols(b)) {
13
+ if (__propIsEnum.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ }
16
+ return a;
17
+ };
18
+ var __esm = (fn, res) => function __init() {
19
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
20
+ };
21
+ var __export = (target, all) => {
22
+ for (var name in all)
23
+ __defProp(target, name, { get: all[name], enumerable: true });
24
+ };
25
+
26
+ // ../../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
27
+ var init_esm_shims = __esm({
28
+ "../../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"() {
29
+ "use strict";
30
+ }
31
+ });
32
+
33
+ // src/recorder.ts
34
+ import { record } from "rrweb";
35
+ function startRecording() {
36
+ events = [];
37
+ record({
38
+ emit(event) {
39
+ events.push(event);
40
+ if (events.length > MAX_EVENTS) {
41
+ events = events.slice(-MAX_EVENTS);
42
+ }
43
+ }
44
+ });
45
+ }
46
+ function getRecordedEvents(beforeErrorSec = 30) {
47
+ const now = Date.now();
48
+ return events.filter((e) => now - e.timestamp < beforeErrorSec * 1e3);
49
+ }
50
+ var events, MAX_EVENTS;
51
+ var init_recorder = __esm({
52
+ "src/recorder.ts"() {
53
+ "use strict";
54
+ init_esm_shims();
55
+ events = [];
56
+ MAX_EVENTS = 1e3;
57
+ }
58
+ });
59
+
60
+ // src/environment.ts
61
+ function getBrowserInfo() {
62
+ const ua = navigator.userAgent;
63
+ let browser = "unknown", os = "unknown";
64
+ if (ua.includes("Firefox")) browser = "Firefox";
65
+ else if (ua.includes("SamsungBrowser")) browser = "Samsung Browser";
66
+ else if (ua.includes("Opera") || ua.includes("OPR")) browser = "Opera";
67
+ else if (ua.includes("Trident")) browser = "IE";
68
+ else if (ua.includes("Edge")) browser = "Edge (Legacy)";
69
+ else if (ua.includes("Edg")) browser = "Edge";
70
+ else if (ua.includes("Chrome")) browser = "Chrome";
71
+ else if (ua.includes("Safari")) browser = "Safari";
72
+ if (ua.includes("Windows")) os = "Windows";
73
+ else if (ua.includes("Mac")) os = "macOS";
74
+ else if (ua.includes("Linux")) os = "Linux";
75
+ else if (ua.includes("Android")) os = "Android";
76
+ else if (ua.includes("like Mac")) os = "iOS";
77
+ return { browser, os, userAgent: ua };
78
+ }
79
+ function getEnvironment() {
80
+ if (process.env.NODE_ENV === "development") return "development";
81
+ if (process.env.NEXT_PUBLIC_VERCEL_ENV === "preview") return "staging";
82
+ return "production";
83
+ }
84
+ var init_environment = __esm({
85
+ "src/environment.ts"() {
86
+ "use strict";
87
+ init_esm_shims();
88
+ }
89
+ });
90
+
91
+ // src/error-batcher.ts
92
+ import axios from "axios";
93
+ var ErrorBatcher;
94
+ var init_error_batcher = __esm({
95
+ "src/error-batcher.ts"() {
96
+ "use strict";
97
+ init_esm_shims();
98
+ ErrorBatcher = class {
99
+ constructor(opts) {
100
+ this.opts = opts;
101
+ this.queue = [];
102
+ this.isFlushing = false;
103
+ var _a;
104
+ const interval = (_a = opts.flushIntervalMs) != null ? _a : 3e3;
105
+ this.flushTimer = window.setInterval(() => this.flush(), interval);
106
+ window.addEventListener("beforeunload", () => this.flushOnUnload());
107
+ }
108
+ capture(evt) {
109
+ var _a;
110
+ const id = this.makeId();
111
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
112
+ const record2 = __spreadValues({ id, timestamp }, evt);
113
+ if (this.queue.length >= ((_a = this.opts.maxBufferSize) != null ? _a : 64)) {
114
+ this.queue.shift();
115
+ }
116
+ this.queue.push(record2);
117
+ return id;
118
+ }
119
+ async flush() {
120
+ if (this.isFlushing || this.queue.length === 0) return;
121
+ this.isFlushing = true;
122
+ const batch = this.queue.splice(0, this.queue.length);
123
+ try {
124
+ await axios.post(
125
+ this.opts.endpoint,
126
+ { events: batch },
127
+ {
128
+ headers: {
129
+ "Content-Type": "application/json",
130
+ Authorization: `Bearer ${this.opts.apiKey}`
131
+ }
132
+ }
133
+ );
134
+ } catch (e) {
135
+ this.queue.unshift(...batch);
136
+ } finally {
137
+ this.isFlushing = false;
138
+ }
139
+ }
140
+ flushOnUnload() {
141
+ if (!navigator.sendBeacon || this.queue.length === 0) return;
142
+ const payload = JSON.stringify({ events: this.queue });
143
+ navigator.sendBeacon(this.opts.endpoint, payload);
144
+ }
145
+ makeId() {
146
+ return "xxxx-xxxx-4xxx-yxxx".replace(/[xy]/g, (c) => {
147
+ const r = Math.random() * 16 | 0;
148
+ const v = c === "x" ? r : r & 3 | 8;
149
+ return v.toString(16);
150
+ });
151
+ }
152
+ destroy() {
153
+ clearInterval(this.flushTimer);
154
+ }
155
+ };
156
+ }
157
+ });
158
+
159
+ // src/handler.ts
160
+ var handler_exports = {};
161
+ __export(handler_exports, {
162
+ setupGlobalErrorHandler: () => setupGlobalErrorHandler
163
+ });
164
+ function setupGlobalErrorHandler() {
165
+ if (window.__errorHandlerSetup) return;
166
+ const origOnError = window.onerror;
167
+ window.onerror = function thisWindowOnError(message, source, lineno, colno, error) {
168
+ origOnError == null ? void 0 : origOnError.call(this, message, source, lineno, colno, error);
169
+ captureException(
170
+ error != null ? error : new Error(typeof message === "string" ? message : "Unknown error")
171
+ );
172
+ return false;
173
+ };
174
+ const origOnUnhandledRejection = window.onunhandledrejection;
175
+ window.onunhandledrejection = function thisWindowOnRejection(event) {
176
+ origOnUnhandledRejection == null ? void 0 : origOnUnhandledRejection.call(this, event);
177
+ const err = event.reason instanceof Error ? event.reason : new Error(JSON.stringify(event.reason));
178
+ captureException(err);
179
+ };
180
+ window.__errorHandlerSetup = true;
181
+ }
182
+ var init_handler = __esm({
183
+ "src/handler.ts"() {
184
+ "use strict";
185
+ init_esm_shims();
186
+ init_reporter();
187
+ }
188
+ });
189
+
190
+ // src/reporter.ts
191
+ function init(options) {
192
+ var _a;
193
+ startRecording();
194
+ globalOpts.beforeErrorSec = (_a = options.beforeErrorSec) != null ? _a : 30;
195
+ batcher = new ErrorBatcher({
196
+ endpoint: options.endpoint,
197
+ apiKey: options.apiKey,
198
+ flushIntervalMs: options.flushIntervalMs,
199
+ maxBufferSize: options.maxBufferSize
200
+ });
201
+ Promise.resolve().then(() => (init_handler(), handler_exports)).then((mod) => mod.setupGlobalErrorHandler());
202
+ }
203
+ function captureException(error, additionalInfo, userId) {
204
+ var _a;
205
+ const { browser, os, userAgent } = getBrowserInfo();
206
+ const replay = getRecordedEvents(globalOpts.beforeErrorSec);
207
+ return batcher.capture({
208
+ message: error.message,
209
+ stack: (_a = error.stack) != null ? _a : "",
210
+ replay,
211
+ environment: getEnvironment(),
212
+ browser,
213
+ os,
214
+ userAgent,
215
+ userId,
216
+ additionalInfo
217
+ });
218
+ }
219
+ function wrap(fn, info) {
220
+ return (...args) => {
221
+ try {
222
+ return fn(...args);
223
+ } catch (err) {
224
+ if (err instanceof Error) {
225
+ captureException(err, info == null ? void 0 : info.additionalInfo, info == null ? void 0 : info.userId);
226
+ } else {
227
+ captureException(
228
+ new Error(String(err)),
229
+ info == null ? void 0 : info.additionalInfo,
230
+ info == null ? void 0 : info.userId
231
+ );
232
+ }
233
+ throw err;
234
+ }
235
+ };
236
+ }
237
+ var batcher, globalOpts;
238
+ var init_reporter = __esm({
239
+ "src/reporter.ts"() {
240
+ "use strict";
241
+ init_esm_shims();
242
+ init_recorder();
243
+ init_environment();
244
+ init_error_batcher();
245
+ globalOpts = { beforeErrorSec: 30 };
246
+ }
247
+ });
248
+
249
+ // src/index.ts
250
+ init_esm_shims();
251
+ init_reporter();
252
+ init_handler();
253
+ init_recorder();
254
+ init_environment();
255
+ init_error_batcher();
256
+ export {
257
+ ErrorBatcher,
258
+ captureException,
259
+ getBrowserInfo,
260
+ getEnvironment,
261
+ getRecordedEvents,
262
+ init,
263
+ setupGlobalErrorHandler,
264
+ startRecording,
265
+ wrap
266
+ };
267
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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/recorder.ts","../src/environment.ts","../src/error-batcher.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","import type { eventWithTime } from '@rrweb/types';\nimport { record } from 'rrweb';\n\nlet events: eventWithTime[] = [];\nconst MAX_EVENTS = 1000;\n\nexport function startRecording() {\n events = [];\n record({\n emit(event) {\n events.push(event);\n if (events.length > MAX_EVENTS) {\n events = events.slice(-MAX_EVENTS);\n }\n },\n });\n}\n\nexport function getRecordedEvents(beforeErrorSec = 30): eventWithTime[] {\n const now = Date.now();\n return events.filter((e) => now - e.timestamp < beforeErrorSec * 1000);\n}\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\n constructor(private opts: BatcherOptions) {\n const interval = opts.flushIntervalMs ?? 3000;\n this.flushTimer = window.setInterval(() => this.flush(), interval);\n window.addEventListener('beforeunload', () => this.flushOnUnload());\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 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 { 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 { startRecording, getRecordedEvents } from './recorder';\nimport { getBrowserInfo, getEnvironment } from './environment';\nimport { ErrorBatcher } from './error-batcher';\n\nexport interface BatchedEvent {\n id: string;\n timestamp: string;\n message: string;\n stack: 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}\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 startRecording();\n globalOpts.beforeErrorSec = options.beforeErrorSec ?? 30;\n batcher = new ErrorBatcher({\n endpoint: options.endpoint,\n apiKey: options.apiKey,\n flushIntervalMs: options.flushIntervalMs,\n maxBufferSize: options.maxBufferSize,\n });\n import('./handler.js').then((mod) => mod.setupGlobalErrorHandler());\n}\n\nexport function captureException(\n error: Error,\n additionalInfo?: Record<string, any>,\n userId?: number\n): string {\n const { browser, os, userAgent } = getBrowserInfo();\n const replay = getRecordedEvents(globalOpts.beforeErrorSec);\n return batcher.capture({\n message: error.message,\n stack: error.stack ?? '',\n replay,\n environment: getEnvironment(),\n browser,\n os,\n userAgent,\n userId,\n additionalInfo,\n });\n}\n\n/** 기존 함수 래핑 */\nexport function wrap<T extends (...args: any[]) => any>(\n fn: T,\n info?: { additionalInfo?: Record<string, any>; userId?: number }\n): (...args: Parameters<T>) => ReturnType<T> {\n return (...args) => {\n try {\n return fn(...args);\n } catch (err) {\n if (err instanceof Error) {\n captureException(err, info?.additionalInfo, info?.userId);\n } else {\n captureException(\n new Error(String(err)),\n info?.additionalInfo,\n info?.userId\n );\n }\n throw err;\n }\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"]}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "rusty-replay",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "dist/index.cjs.js",
6
+ "type": "module",
7
+ "module": "dist/index.esm.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "peerDependencies": {
17
+ "@rrweb/types": "^2.0.0-alpha.4",
18
+ "react": "^16.8 || ^17 || ^18 || ^19",
19
+ "rrweb": "^2.0.0-alpha.4",
20
+ "rrweb-player": "^1.0.0-alpha.4"
21
+ },
22
+ "dependencies": {
23
+ "axios": "^1.8.4"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20",
27
+ "tsup": "^8.4.0",
28
+ "typescript": "^5.8.3"
29
+ }
30
+ }