@tracewayapp/frontend 0.2.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/README.md +89 -0
- package/dist/index.d.mts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +303 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @tracewayapp/frontend
|
|
2
|
+
|
|
3
|
+
Traceway SDK for browser environments. Reports exceptions and messages only (no traces or metrics).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import * as traceway from "@tracewayapp/frontend";
|
|
9
|
+
|
|
10
|
+
traceway.init("your-token@https://your-traceway-server.com/api/report");
|
|
11
|
+
|
|
12
|
+
// Capture an error
|
|
13
|
+
traceway.captureException(new Error("something broke"));
|
|
14
|
+
|
|
15
|
+
// Capture a message
|
|
16
|
+
traceway.captureMessage("User completed onboarding");
|
|
17
|
+
|
|
18
|
+
// Flush pending exceptions immediately
|
|
19
|
+
await traceway.flush();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## API
|
|
23
|
+
|
|
24
|
+
| Function | Description |
|
|
25
|
+
|----------|-------------|
|
|
26
|
+
| `init(connectionString, options?)` | Initialize the SDK and install global error handlers |
|
|
27
|
+
| `captureException(error)` | Capture an error with browser stack trace |
|
|
28
|
+
| `captureExceptionWithAttributes(error, attributes?)` | Capture with key-value attributes |
|
|
29
|
+
| `captureMessage(msg)` | Capture an informational message |
|
|
30
|
+
| `flush()` | Force-send all pending exceptions immediately |
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
| Option | Type | Default | Description |
|
|
35
|
+
|--------|------|---------|-------------|
|
|
36
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
37
|
+
| `debounceMs` | `number` | `1500` | Debounce interval before sending |
|
|
38
|
+
| `version` | `string` | `""` | Application version |
|
|
39
|
+
|
|
40
|
+
## Sync Queue Behavior
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
captureException()
|
|
44
|
+
|
|
|
45
|
+
v
|
|
46
|
+
pendingExceptions[] --> scheduleSync() (1.5s debounce)
|
|
47
|
+
|
|
|
48
|
+
v (debounce fires)
|
|
49
|
+
doSync()
|
|
50
|
+
|
|
|
51
|
+
+---------+---------+
|
|
52
|
+
| |
|
|
53
|
+
isSyncing? send batch
|
|
54
|
+
| |
|
|
55
|
+
return +-----+-----+
|
|
56
|
+
success failure
|
|
57
|
+
| |
|
|
58
|
+
done re-queue batch
|
|
59
|
+
|
|
|
60
|
+
if pending > 0
|
|
61
|
+
|
|
|
62
|
+
doSync() again
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- Only one sync at a time (no concurrent fetches)
|
|
66
|
+
- New exceptions during an in-flight request are queued for the next batch
|
|
67
|
+
- On failure, the batch is re-queued at the front of the pending list
|
|
68
|
+
- On success after re-queue, any new pending items trigger an immediate sync
|
|
69
|
+
|
|
70
|
+
## Global Error Handlers
|
|
71
|
+
|
|
72
|
+
`init()` automatically installs `window.onerror` and `window.onunhandledrejection` handlers. Previous handlers are chained and called after Traceway captures the error.
|
|
73
|
+
|
|
74
|
+
## Browser Requirements
|
|
75
|
+
|
|
76
|
+
- `fetch` API
|
|
77
|
+
- `CompressionStream` API (gzip) — available in Chrome 80+, Firefox 113+, Safari 16.4+
|
|
78
|
+
|
|
79
|
+
## Stack Trace Format
|
|
80
|
+
|
|
81
|
+
Handles both V8 format (`at func (file:line:col)`) and Firefox format (`func@file:line:col`), producing Go-like output:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
TypeError: Cannot read properties of null
|
|
85
|
+
handleClick()
|
|
86
|
+
app.js:42
|
|
87
|
+
render()
|
|
88
|
+
component.js:15
|
|
89
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ExceptionStackTrace } from '@tracewayapp/core';
|
|
2
|
+
export { CollectionFrame, ExceptionStackTrace, ReportRequest } from '@tracewayapp/core';
|
|
3
|
+
|
|
4
|
+
interface TracewayFrontendOptions {
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
debounceMs?: number;
|
|
7
|
+
retryDelayMs?: number;
|
|
8
|
+
version?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class TracewayFrontendClient {
|
|
11
|
+
private apiUrl;
|
|
12
|
+
private token;
|
|
13
|
+
private debug;
|
|
14
|
+
private debounceMs;
|
|
15
|
+
private retryDelayMs;
|
|
16
|
+
private version;
|
|
17
|
+
private pendingExceptions;
|
|
18
|
+
private isSyncing;
|
|
19
|
+
private debounceTimer;
|
|
20
|
+
private retryTimer;
|
|
21
|
+
constructor(connectionString: string, options?: TracewayFrontendOptions);
|
|
22
|
+
addException(exception: ExceptionStackTrace): void;
|
|
23
|
+
private scheduleSync;
|
|
24
|
+
private doSync;
|
|
25
|
+
private scheduleRetry;
|
|
26
|
+
flush(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare function formatBrowserStackTrace(error: Error): string;
|
|
30
|
+
|
|
31
|
+
declare function installGlobalHandlers(client: TracewayFrontendClient): void;
|
|
32
|
+
|
|
33
|
+
declare function init(connectionString: string, options?: TracewayFrontendOptions): void;
|
|
34
|
+
declare function captureException(error: Error): void;
|
|
35
|
+
declare function captureExceptionWithAttributes(error: Error, attributes?: Record<string, string>): void;
|
|
36
|
+
declare function captureMessage(msg: string): void;
|
|
37
|
+
declare function flush(): Promise<void>;
|
|
38
|
+
|
|
39
|
+
export { TracewayFrontendClient, type TracewayFrontendOptions, captureException, captureExceptionWithAttributes, captureMessage, flush, formatBrowserStackTrace, init, installGlobalHandlers };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ExceptionStackTrace } from '@tracewayapp/core';
|
|
2
|
+
export { CollectionFrame, ExceptionStackTrace, ReportRequest } from '@tracewayapp/core';
|
|
3
|
+
|
|
4
|
+
interface TracewayFrontendOptions {
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
debounceMs?: number;
|
|
7
|
+
retryDelayMs?: number;
|
|
8
|
+
version?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class TracewayFrontendClient {
|
|
11
|
+
private apiUrl;
|
|
12
|
+
private token;
|
|
13
|
+
private debug;
|
|
14
|
+
private debounceMs;
|
|
15
|
+
private retryDelayMs;
|
|
16
|
+
private version;
|
|
17
|
+
private pendingExceptions;
|
|
18
|
+
private isSyncing;
|
|
19
|
+
private debounceTimer;
|
|
20
|
+
private retryTimer;
|
|
21
|
+
constructor(connectionString: string, options?: TracewayFrontendOptions);
|
|
22
|
+
addException(exception: ExceptionStackTrace): void;
|
|
23
|
+
private scheduleSync;
|
|
24
|
+
private doSync;
|
|
25
|
+
private scheduleRetry;
|
|
26
|
+
flush(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare function formatBrowserStackTrace(error: Error): string;
|
|
30
|
+
|
|
31
|
+
declare function installGlobalHandlers(client: TracewayFrontendClient): void;
|
|
32
|
+
|
|
33
|
+
declare function init(connectionString: string, options?: TracewayFrontendOptions): void;
|
|
34
|
+
declare function captureException(error: Error): void;
|
|
35
|
+
declare function captureExceptionWithAttributes(error: Error, attributes?: Record<string, string>): void;
|
|
36
|
+
declare function captureMessage(msg: string): void;
|
|
37
|
+
declare function flush(): Promise<void>;
|
|
38
|
+
|
|
39
|
+
export { TracewayFrontendClient, type TracewayFrontendOptions, captureException, captureExceptionWithAttributes, captureMessage, flush, formatBrowserStackTrace, init, installGlobalHandlers };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
TracewayFrontendClient: () => TracewayFrontendClient,
|
|
24
|
+
captureException: () => captureException,
|
|
25
|
+
captureExceptionWithAttributes: () => captureExceptionWithAttributes,
|
|
26
|
+
captureMessage: () => captureMessage,
|
|
27
|
+
flush: () => flush,
|
|
28
|
+
formatBrowserStackTrace: () => formatBrowserStackTrace,
|
|
29
|
+
init: () => init,
|
|
30
|
+
installGlobalHandlers: () => installGlobalHandlers
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
var import_core3 = require("@tracewayapp/core");
|
|
34
|
+
|
|
35
|
+
// src/client.ts
|
|
36
|
+
var import_core = require("@tracewayapp/core");
|
|
37
|
+
|
|
38
|
+
// src/transport.ts
|
|
39
|
+
async function compressGzip(data) {
|
|
40
|
+
const encoder = new TextEncoder();
|
|
41
|
+
const inputBytes = encoder.encode(data);
|
|
42
|
+
const cs = new CompressionStream("gzip");
|
|
43
|
+
const writer = cs.writable.getWriter();
|
|
44
|
+
writer.write(inputBytes);
|
|
45
|
+
writer.close();
|
|
46
|
+
const reader = cs.readable.getReader();
|
|
47
|
+
const chunks = [];
|
|
48
|
+
let totalLength = 0;
|
|
49
|
+
while (true) {
|
|
50
|
+
const { done, value } = await reader.read();
|
|
51
|
+
if (done) break;
|
|
52
|
+
chunks.push(value);
|
|
53
|
+
totalLength += value.length;
|
|
54
|
+
}
|
|
55
|
+
const result = new Uint8Array(totalLength);
|
|
56
|
+
let offset = 0;
|
|
57
|
+
for (const chunk of chunks) {
|
|
58
|
+
result.set(chunk, offset);
|
|
59
|
+
offset += chunk.length;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
async function sendReport(apiUrl, token, body) {
|
|
64
|
+
const compressed = await compressGzip(body);
|
|
65
|
+
const resp = await fetch(apiUrl, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
"Content-Encoding": "gzip",
|
|
70
|
+
Authorization: `Bearer ${token}`
|
|
71
|
+
},
|
|
72
|
+
body: compressed
|
|
73
|
+
});
|
|
74
|
+
return resp.status === 200;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/client.ts
|
|
78
|
+
var TracewayFrontendClient = class {
|
|
79
|
+
apiUrl;
|
|
80
|
+
token;
|
|
81
|
+
debug;
|
|
82
|
+
debounceMs;
|
|
83
|
+
retryDelayMs;
|
|
84
|
+
version;
|
|
85
|
+
pendingExceptions = [];
|
|
86
|
+
isSyncing = false;
|
|
87
|
+
debounceTimer = null;
|
|
88
|
+
retryTimer = null;
|
|
89
|
+
constructor(connectionString, options = {}) {
|
|
90
|
+
const { token, apiUrl } = (0, import_core.parseConnectionString)(connectionString);
|
|
91
|
+
this.apiUrl = apiUrl;
|
|
92
|
+
this.token = token;
|
|
93
|
+
this.debug = options.debug ?? false;
|
|
94
|
+
this.debounceMs = options.debounceMs ?? 1500;
|
|
95
|
+
this.retryDelayMs = options.retryDelayMs ?? 1e4;
|
|
96
|
+
this.version = options.version ?? "";
|
|
97
|
+
}
|
|
98
|
+
addException(exception) {
|
|
99
|
+
this.pendingExceptions.push(exception);
|
|
100
|
+
this.scheduleSync();
|
|
101
|
+
}
|
|
102
|
+
scheduleSync() {
|
|
103
|
+
if (this.debounceTimer !== null) {
|
|
104
|
+
clearTimeout(this.debounceTimer);
|
|
105
|
+
}
|
|
106
|
+
this.debounceTimer = setTimeout(() => {
|
|
107
|
+
this.debounceTimer = null;
|
|
108
|
+
this.doSync();
|
|
109
|
+
}, this.debounceMs);
|
|
110
|
+
}
|
|
111
|
+
async doSync() {
|
|
112
|
+
if (this.isSyncing) return;
|
|
113
|
+
if (this.pendingExceptions.length === 0) return;
|
|
114
|
+
this.isSyncing = true;
|
|
115
|
+
const batch = this.pendingExceptions.splice(0);
|
|
116
|
+
const frame = {
|
|
117
|
+
stackTraces: batch,
|
|
118
|
+
metrics: [],
|
|
119
|
+
traces: []
|
|
120
|
+
};
|
|
121
|
+
const payload = {
|
|
122
|
+
collectionFrames: [frame],
|
|
123
|
+
appVersion: this.version,
|
|
124
|
+
serverName: ""
|
|
125
|
+
};
|
|
126
|
+
let failed = false;
|
|
127
|
+
try {
|
|
128
|
+
const success = await sendReport(
|
|
129
|
+
this.apiUrl,
|
|
130
|
+
this.token,
|
|
131
|
+
JSON.stringify(payload)
|
|
132
|
+
);
|
|
133
|
+
if (!success) {
|
|
134
|
+
failed = true;
|
|
135
|
+
this.pendingExceptions.unshift(...batch);
|
|
136
|
+
if (this.debug) {
|
|
137
|
+
console.error("Traceway: sync failed, re-queued exceptions");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
failed = true;
|
|
142
|
+
this.pendingExceptions.unshift(...batch);
|
|
143
|
+
if (this.debug) {
|
|
144
|
+
console.error("Traceway: sync error:", err);
|
|
145
|
+
}
|
|
146
|
+
} finally {
|
|
147
|
+
this.isSyncing = false;
|
|
148
|
+
if (this.pendingExceptions.length > 0) {
|
|
149
|
+
if (failed) {
|
|
150
|
+
this.scheduleRetry();
|
|
151
|
+
} else {
|
|
152
|
+
this.doSync();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
scheduleRetry() {
|
|
158
|
+
if (this.retryTimer !== null) return;
|
|
159
|
+
this.retryTimer = setTimeout(() => {
|
|
160
|
+
this.retryTimer = null;
|
|
161
|
+
this.doSync();
|
|
162
|
+
}, this.retryDelayMs);
|
|
163
|
+
}
|
|
164
|
+
async flush() {
|
|
165
|
+
if (this.debounceTimer !== null) {
|
|
166
|
+
clearTimeout(this.debounceTimer);
|
|
167
|
+
this.debounceTimer = null;
|
|
168
|
+
}
|
|
169
|
+
if (this.retryTimer !== null) {
|
|
170
|
+
clearTimeout(this.retryTimer);
|
|
171
|
+
this.retryTimer = null;
|
|
172
|
+
}
|
|
173
|
+
await this.doSync();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/stack-trace.ts
|
|
178
|
+
function formatBrowserStackTrace(error) {
|
|
179
|
+
const lines = [];
|
|
180
|
+
const typeName = error.constructor?.name || "Error";
|
|
181
|
+
lines.push(`${typeName}: ${error.message}`);
|
|
182
|
+
if (error.stack) {
|
|
183
|
+
const stackLines = error.stack.split("\n");
|
|
184
|
+
for (const line of stackLines) {
|
|
185
|
+
const v8Match = line.match(/^\s+at\s+(.+?)\s+\((.+):(\d+):\d+\)$/);
|
|
186
|
+
if (v8Match) {
|
|
187
|
+
const funcName = shortenFunctionName(v8Match[1]);
|
|
188
|
+
const file = shortenFilePath(v8Match[2]);
|
|
189
|
+
lines.push(`${funcName}()`);
|
|
190
|
+
lines.push(` ${file}:${v8Match[3]}`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const v8AnonMatch = line.match(/^\s+at\s+(.+):(\d+):\d+$/);
|
|
194
|
+
if (v8AnonMatch) {
|
|
195
|
+
const file = shortenFilePath(v8AnonMatch[1]);
|
|
196
|
+
lines.push(`<anonymous>()`);
|
|
197
|
+
lines.push(` ${file}:${v8AnonMatch[2]}`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const ffMatch = line.match(/^(.+)@(.+):(\d+):\d+$/);
|
|
201
|
+
if (ffMatch) {
|
|
202
|
+
const funcName = shortenFunctionName(ffMatch[1]) || "<anonymous>";
|
|
203
|
+
const file = shortenFilePath(ffMatch[2]);
|
|
204
|
+
lines.push(`${funcName}()`);
|
|
205
|
+
lines.push(` ${file}:${ffMatch[3]}`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const ffAnonMatch = line.match(/^@(.+):(\d+):\d+$/);
|
|
209
|
+
if (ffAnonMatch) {
|
|
210
|
+
const file = shortenFilePath(ffAnonMatch[1]);
|
|
211
|
+
lines.push(`<anonymous>()`);
|
|
212
|
+
lines.push(` ${file}:${ffAnonMatch[2]}`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return lines.join("\n") + "\n";
|
|
218
|
+
}
|
|
219
|
+
function shortenFunctionName(fn) {
|
|
220
|
+
const slashIdx = fn.lastIndexOf("/");
|
|
221
|
+
if (slashIdx >= 0) {
|
|
222
|
+
fn = fn.slice(slashIdx + 1);
|
|
223
|
+
}
|
|
224
|
+
const dotIdx = fn.indexOf(".");
|
|
225
|
+
if (dotIdx >= 0) {
|
|
226
|
+
fn = fn.slice(dotIdx + 1);
|
|
227
|
+
}
|
|
228
|
+
return fn;
|
|
229
|
+
}
|
|
230
|
+
function shortenFilePath(filePath) {
|
|
231
|
+
const parts = filePath.split("/");
|
|
232
|
+
return parts[parts.length - 1];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/global-handlers.ts
|
|
236
|
+
var import_core2 = require("@tracewayapp/core");
|
|
237
|
+
function installGlobalHandlers(client2) {
|
|
238
|
+
const prevOnError = window.onerror;
|
|
239
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
240
|
+
if (error) {
|
|
241
|
+
client2.addException({
|
|
242
|
+
traceId: null,
|
|
243
|
+
stackTrace: formatBrowserStackTrace(error),
|
|
244
|
+
recordedAt: (0, import_core2.nowISO)(),
|
|
245
|
+
isMessage: false
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
client2.addException({
|
|
249
|
+
traceId: null,
|
|
250
|
+
stackTrace: String(message),
|
|
251
|
+
recordedAt: (0, import_core2.nowISO)(),
|
|
252
|
+
isMessage: false
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (typeof prevOnError === "function") {
|
|
256
|
+
return prevOnError(message, source, lineno, colno, error);
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
};
|
|
260
|
+
const prevOnUnhandledRejection = window.onunhandledrejection;
|
|
261
|
+
window.onunhandledrejection = (event) => {
|
|
262
|
+
const reason = event.reason;
|
|
263
|
+
if (reason instanceof Error) {
|
|
264
|
+
client2.addException({
|
|
265
|
+
traceId: null,
|
|
266
|
+
stackTrace: formatBrowserStackTrace(reason),
|
|
267
|
+
recordedAt: (0, import_core2.nowISO)(),
|
|
268
|
+
isMessage: false
|
|
269
|
+
});
|
|
270
|
+
} else {
|
|
271
|
+
client2.addException({
|
|
272
|
+
traceId: null,
|
|
273
|
+
stackTrace: String(reason),
|
|
274
|
+
recordedAt: (0, import_core2.nowISO)(),
|
|
275
|
+
isMessage: false
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (typeof prevOnUnhandledRejection === "function") {
|
|
279
|
+
prevOnUnhandledRejection.call(window, event);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/index.ts
|
|
285
|
+
var client = null;
|
|
286
|
+
function init(connectionString, options = {}) {
|
|
287
|
+
client = new TracewayFrontendClient(connectionString, options);
|
|
288
|
+
if (typeof window !== "undefined") {
|
|
289
|
+
installGlobalHandlers(client);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function captureException(error) {
|
|
293
|
+
if (!client) return;
|
|
294
|
+
client.addException({
|
|
295
|
+
traceId: null,
|
|
296
|
+
stackTrace: formatBrowserStackTrace(error),
|
|
297
|
+
recordedAt: (0, import_core3.nowISO)(),
|
|
298
|
+
isMessage: false
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function captureExceptionWithAttributes(error, attributes) {
|
|
302
|
+
if (!client) return;
|
|
303
|
+
client.addException({
|
|
304
|
+
traceId: null,
|
|
305
|
+
stackTrace: formatBrowserStackTrace(error),
|
|
306
|
+
recordedAt: (0, import_core3.nowISO)(),
|
|
307
|
+
attributes,
|
|
308
|
+
isMessage: false
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
function captureMessage(msg) {
|
|
312
|
+
if (!client) return;
|
|
313
|
+
client.addException({
|
|
314
|
+
traceId: null,
|
|
315
|
+
stackTrace: msg,
|
|
316
|
+
recordedAt: (0, import_core3.nowISO)(),
|
|
317
|
+
isMessage: true
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async function flush() {
|
|
321
|
+
if (!client) return;
|
|
322
|
+
await client.flush();
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/transport.ts","../src/stack-trace.ts","../src/global-handlers.ts"],"sourcesContent":["import { nowISO } from \"@tracewayapp/core\";\nimport type { ExceptionStackTrace } from \"@tracewayapp/core\";\nimport {\n TracewayFrontendClient,\n type TracewayFrontendOptions,\n} from \"./client.js\";\nimport { formatBrowserStackTrace } from \"./stack-trace.js\";\nimport { installGlobalHandlers } from \"./global-handlers.js\";\n\nlet client: TracewayFrontendClient | null = null;\n\nexport function init(\n connectionString: string,\n options: TracewayFrontendOptions = {},\n): void {\n client = new TracewayFrontendClient(connectionString, options);\n if (typeof window !== \"undefined\") {\n installGlobalHandlers(client);\n }\n}\n\nexport function captureException(error: Error): void {\n if (!client) return;\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(error),\n recordedAt: nowISO(),\n isMessage: false,\n });\n}\n\nexport function captureExceptionWithAttributes(\n error: Error,\n attributes?: Record<string, string>,\n): void {\n if (!client) return;\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(error),\n recordedAt: nowISO(),\n attributes,\n isMessage: false,\n });\n}\n\nexport function captureMessage(msg: string): void {\n if (!client) return;\n client.addException({\n traceId: null,\n stackTrace: msg,\n recordedAt: nowISO(),\n isMessage: true,\n });\n}\n\nexport async function flush(): Promise<void> {\n if (!client) return;\n await client.flush();\n}\n\nexport { TracewayFrontendClient } from \"./client.js\";\nexport type { TracewayFrontendOptions } from \"./client.js\";\nexport { formatBrowserStackTrace } from \"./stack-trace.js\";\nexport { installGlobalHandlers } from \"./global-handlers.js\";\n\nexport type {\n ExceptionStackTrace,\n CollectionFrame,\n ReportRequest,\n} from \"@tracewayapp/core\";\n","import type {\n ExceptionStackTrace,\n ReportRequest,\n CollectionFrame,\n} from \"@tracewayapp/core\";\nimport { parseConnectionString, nowISO } from \"@tracewayapp/core\";\nimport { sendReport } from \"./transport.js\";\n\nexport interface TracewayFrontendOptions {\n debug?: boolean;\n debounceMs?: number;\n retryDelayMs?: number;\n version?: string;\n}\n\nexport class TracewayFrontendClient {\n private apiUrl: string;\n private token: string;\n private debug: boolean;\n private debounceMs: number;\n private retryDelayMs: number;\n private version: string;\n\n private pendingExceptions: ExceptionStackTrace[] = [];\n private isSyncing = false;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(connectionString: string, options: TracewayFrontendOptions = {}) {\n const { token, apiUrl } = parseConnectionString(connectionString);\n this.apiUrl = apiUrl;\n this.token = token;\n this.debug = options.debug ?? false;\n this.debounceMs = options.debounceMs ?? 1500;\n this.retryDelayMs = options.retryDelayMs ?? 10000;\n this.version = options.version ?? \"\";\n }\n\n addException(exception: ExceptionStackTrace): void {\n this.pendingExceptions.push(exception);\n this.scheduleSync();\n }\n\n private scheduleSync(): void {\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer);\n }\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n this.doSync();\n }, this.debounceMs);\n }\n\n private async doSync(): Promise<void> {\n if (this.isSyncing) return;\n if (this.pendingExceptions.length === 0) return;\n\n this.isSyncing = true;\n const batch = this.pendingExceptions.splice(0);\n\n const frame: CollectionFrame = {\n stackTraces: batch,\n metrics: [],\n traces: [],\n };\n\n const payload: ReportRequest = {\n collectionFrames: [frame],\n appVersion: this.version,\n serverName: \"\",\n };\n\n let failed = false;\n try {\n const success = await sendReport(\n this.apiUrl,\n this.token,\n JSON.stringify(payload),\n );\n if (!success) {\n failed = true;\n this.pendingExceptions.unshift(...batch);\n if (this.debug) {\n console.error(\"Traceway: sync failed, re-queued exceptions\");\n }\n }\n } catch (err) {\n failed = true;\n this.pendingExceptions.unshift(...batch);\n if (this.debug) {\n console.error(\"Traceway: sync error:\", err);\n }\n } finally {\n this.isSyncing = false;\n if (this.pendingExceptions.length > 0) {\n if (failed) {\n // On failure, wait before retrying to avoid hammering the server\n this.scheduleRetry();\n } else {\n // On success, process remaining items immediately\n this.doSync();\n }\n }\n }\n }\n\n private scheduleRetry(): void {\n if (this.retryTimer !== null) return; // Already scheduled\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.doSync();\n }, this.retryDelayMs);\n }\n\n async flush(): Promise<void> {\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n await this.doSync();\n }\n}\n","export async function compressGzip(data: string): Promise<Uint8Array> {\n const encoder = new TextEncoder();\n const inputBytes = encoder.encode(data);\n\n const cs = new CompressionStream(\"gzip\");\n const writer = cs.writable.getWriter();\n writer.write(inputBytes);\n writer.close();\n\n const reader = cs.readable.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n totalLength += value.length;\n }\n\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\nexport async function sendReport(\n apiUrl: string,\n token: string,\n body: string,\n): Promise<boolean> {\n const compressed = await compressGzip(body);\n\n const resp = await fetch(apiUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Encoding\": \"gzip\",\n Authorization: `Bearer ${token}`,\n },\n body: compressed,\n });\n\n return resp.status === 200;\n}\n","export function formatBrowserStackTrace(error: Error): string {\n const lines: string[] = [];\n const typeName = error.constructor?.name || \"Error\";\n lines.push(`${typeName}: ${error.message}`);\n\n if (error.stack) {\n const stackLines = error.stack.split(\"\\n\");\n for (const line of stackLines) {\n // V8 format: \" at funcName (file:line:col)\"\n const v8Match = line.match(/^\\s+at\\s+(.+?)\\s+\\((.+):(\\d+):\\d+\\)$/);\n if (v8Match) {\n const funcName = shortenFunctionName(v8Match[1]);\n const file = shortenFilePath(v8Match[2]);\n lines.push(`${funcName}()`);\n lines.push(` ${file}:${v8Match[3]}`);\n continue;\n }\n\n // V8 anonymous: \" at file:line:col\"\n const v8AnonMatch = line.match(/^\\s+at\\s+(.+):(\\d+):\\d+$/);\n if (v8AnonMatch) {\n const file = shortenFilePath(v8AnonMatch[1]);\n lines.push(`<anonymous>()`);\n lines.push(` ${file}:${v8AnonMatch[2]}`);\n continue;\n }\n\n // Firefox format: \"funcName@file:line:col\"\n const ffMatch = line.match(/^(.+)@(.+):(\\d+):\\d+$/);\n if (ffMatch) {\n const funcName = shortenFunctionName(ffMatch[1]) || \"<anonymous>\";\n const file = shortenFilePath(ffMatch[2]);\n lines.push(`${funcName}()`);\n lines.push(` ${file}:${ffMatch[3]}`);\n continue;\n }\n\n // Firefox anonymous: \"@file:line:col\"\n const ffAnonMatch = line.match(/^@(.+):(\\d+):\\d+$/);\n if (ffAnonMatch) {\n const file = shortenFilePath(ffAnonMatch[1]);\n lines.push(`<anonymous>()`);\n lines.push(` ${file}:${ffAnonMatch[2]}`);\n continue;\n }\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction shortenFunctionName(fn: string): string {\n const slashIdx = fn.lastIndexOf(\"/\");\n if (slashIdx >= 0) {\n fn = fn.slice(slashIdx + 1);\n }\n const dotIdx = fn.indexOf(\".\");\n if (dotIdx >= 0) {\n fn = fn.slice(dotIdx + 1);\n }\n return fn;\n}\n\nfunction shortenFilePath(filePath: string): string {\n const parts = filePath.split(\"/\");\n return parts[parts.length - 1];\n}\n","import type { TracewayFrontendClient } from \"./client.js\";\nimport { formatBrowserStackTrace } from \"./stack-trace.js\";\nimport { nowISO } from \"@tracewayapp/core\";\n\nexport function installGlobalHandlers(client: TracewayFrontendClient): void {\n const prevOnError = window.onerror;\n window.onerror = (message, source, lineno, colno, error) => {\n if (error) {\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(error),\n recordedAt: nowISO(),\n isMessage: false,\n });\n } else {\n client.addException({\n traceId: null,\n stackTrace: String(message),\n recordedAt: nowISO(),\n isMessage: false,\n });\n }\n if (typeof prevOnError === \"function\") {\n return prevOnError(message, source, lineno, colno, error);\n }\n return false;\n };\n\n const prevOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n if (reason instanceof Error) {\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(reason),\n recordedAt: nowISO(),\n isMessage: false,\n });\n } else {\n client.addException({\n traceId: null,\n stackTrace: String(reason),\n recordedAt: nowISO(),\n isMessage: false,\n });\n }\n if (typeof prevOnUnhandledRejection === \"function\") {\n prevOnUnhandledRejection.call(window, event);\n }\n };\n}\n"],"mappings":"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","names":["import_core","import_core","client"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { nowISO as nowISO3 } from "@tracewayapp/core";
|
|
3
|
+
|
|
4
|
+
// src/client.ts
|
|
5
|
+
import { parseConnectionString } from "@tracewayapp/core";
|
|
6
|
+
|
|
7
|
+
// src/transport.ts
|
|
8
|
+
async function compressGzip(data) {
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const inputBytes = encoder.encode(data);
|
|
11
|
+
const cs = new CompressionStream("gzip");
|
|
12
|
+
const writer = cs.writable.getWriter();
|
|
13
|
+
writer.write(inputBytes);
|
|
14
|
+
writer.close();
|
|
15
|
+
const reader = cs.readable.getReader();
|
|
16
|
+
const chunks = [];
|
|
17
|
+
let totalLength = 0;
|
|
18
|
+
while (true) {
|
|
19
|
+
const { done, value } = await reader.read();
|
|
20
|
+
if (done) break;
|
|
21
|
+
chunks.push(value);
|
|
22
|
+
totalLength += value.length;
|
|
23
|
+
}
|
|
24
|
+
const result = new Uint8Array(totalLength);
|
|
25
|
+
let offset = 0;
|
|
26
|
+
for (const chunk of chunks) {
|
|
27
|
+
result.set(chunk, offset);
|
|
28
|
+
offset += chunk.length;
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
async function sendReport(apiUrl, token, body) {
|
|
33
|
+
const compressed = await compressGzip(body);
|
|
34
|
+
const resp = await fetch(apiUrl, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"Content-Encoding": "gzip",
|
|
39
|
+
Authorization: `Bearer ${token}`
|
|
40
|
+
},
|
|
41
|
+
body: compressed
|
|
42
|
+
});
|
|
43
|
+
return resp.status === 200;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/client.ts
|
|
47
|
+
var TracewayFrontendClient = class {
|
|
48
|
+
apiUrl;
|
|
49
|
+
token;
|
|
50
|
+
debug;
|
|
51
|
+
debounceMs;
|
|
52
|
+
retryDelayMs;
|
|
53
|
+
version;
|
|
54
|
+
pendingExceptions = [];
|
|
55
|
+
isSyncing = false;
|
|
56
|
+
debounceTimer = null;
|
|
57
|
+
retryTimer = null;
|
|
58
|
+
constructor(connectionString, options = {}) {
|
|
59
|
+
const { token, apiUrl } = parseConnectionString(connectionString);
|
|
60
|
+
this.apiUrl = apiUrl;
|
|
61
|
+
this.token = token;
|
|
62
|
+
this.debug = options.debug ?? false;
|
|
63
|
+
this.debounceMs = options.debounceMs ?? 1500;
|
|
64
|
+
this.retryDelayMs = options.retryDelayMs ?? 1e4;
|
|
65
|
+
this.version = options.version ?? "";
|
|
66
|
+
}
|
|
67
|
+
addException(exception) {
|
|
68
|
+
this.pendingExceptions.push(exception);
|
|
69
|
+
this.scheduleSync();
|
|
70
|
+
}
|
|
71
|
+
scheduleSync() {
|
|
72
|
+
if (this.debounceTimer !== null) {
|
|
73
|
+
clearTimeout(this.debounceTimer);
|
|
74
|
+
}
|
|
75
|
+
this.debounceTimer = setTimeout(() => {
|
|
76
|
+
this.debounceTimer = null;
|
|
77
|
+
this.doSync();
|
|
78
|
+
}, this.debounceMs);
|
|
79
|
+
}
|
|
80
|
+
async doSync() {
|
|
81
|
+
if (this.isSyncing) return;
|
|
82
|
+
if (this.pendingExceptions.length === 0) return;
|
|
83
|
+
this.isSyncing = true;
|
|
84
|
+
const batch = this.pendingExceptions.splice(0);
|
|
85
|
+
const frame = {
|
|
86
|
+
stackTraces: batch,
|
|
87
|
+
metrics: [],
|
|
88
|
+
traces: []
|
|
89
|
+
};
|
|
90
|
+
const payload = {
|
|
91
|
+
collectionFrames: [frame],
|
|
92
|
+
appVersion: this.version,
|
|
93
|
+
serverName: ""
|
|
94
|
+
};
|
|
95
|
+
let failed = false;
|
|
96
|
+
try {
|
|
97
|
+
const success = await sendReport(
|
|
98
|
+
this.apiUrl,
|
|
99
|
+
this.token,
|
|
100
|
+
JSON.stringify(payload)
|
|
101
|
+
);
|
|
102
|
+
if (!success) {
|
|
103
|
+
failed = true;
|
|
104
|
+
this.pendingExceptions.unshift(...batch);
|
|
105
|
+
if (this.debug) {
|
|
106
|
+
console.error("Traceway: sync failed, re-queued exceptions");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
failed = true;
|
|
111
|
+
this.pendingExceptions.unshift(...batch);
|
|
112
|
+
if (this.debug) {
|
|
113
|
+
console.error("Traceway: sync error:", err);
|
|
114
|
+
}
|
|
115
|
+
} finally {
|
|
116
|
+
this.isSyncing = false;
|
|
117
|
+
if (this.pendingExceptions.length > 0) {
|
|
118
|
+
if (failed) {
|
|
119
|
+
this.scheduleRetry();
|
|
120
|
+
} else {
|
|
121
|
+
this.doSync();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
scheduleRetry() {
|
|
127
|
+
if (this.retryTimer !== null) return;
|
|
128
|
+
this.retryTimer = setTimeout(() => {
|
|
129
|
+
this.retryTimer = null;
|
|
130
|
+
this.doSync();
|
|
131
|
+
}, this.retryDelayMs);
|
|
132
|
+
}
|
|
133
|
+
async flush() {
|
|
134
|
+
if (this.debounceTimer !== null) {
|
|
135
|
+
clearTimeout(this.debounceTimer);
|
|
136
|
+
this.debounceTimer = null;
|
|
137
|
+
}
|
|
138
|
+
if (this.retryTimer !== null) {
|
|
139
|
+
clearTimeout(this.retryTimer);
|
|
140
|
+
this.retryTimer = null;
|
|
141
|
+
}
|
|
142
|
+
await this.doSync();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/stack-trace.ts
|
|
147
|
+
function formatBrowserStackTrace(error) {
|
|
148
|
+
const lines = [];
|
|
149
|
+
const typeName = error.constructor?.name || "Error";
|
|
150
|
+
lines.push(`${typeName}: ${error.message}`);
|
|
151
|
+
if (error.stack) {
|
|
152
|
+
const stackLines = error.stack.split("\n");
|
|
153
|
+
for (const line of stackLines) {
|
|
154
|
+
const v8Match = line.match(/^\s+at\s+(.+?)\s+\((.+):(\d+):\d+\)$/);
|
|
155
|
+
if (v8Match) {
|
|
156
|
+
const funcName = shortenFunctionName(v8Match[1]);
|
|
157
|
+
const file = shortenFilePath(v8Match[2]);
|
|
158
|
+
lines.push(`${funcName}()`);
|
|
159
|
+
lines.push(` ${file}:${v8Match[3]}`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const v8AnonMatch = line.match(/^\s+at\s+(.+):(\d+):\d+$/);
|
|
163
|
+
if (v8AnonMatch) {
|
|
164
|
+
const file = shortenFilePath(v8AnonMatch[1]);
|
|
165
|
+
lines.push(`<anonymous>()`);
|
|
166
|
+
lines.push(` ${file}:${v8AnonMatch[2]}`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const ffMatch = line.match(/^(.+)@(.+):(\d+):\d+$/);
|
|
170
|
+
if (ffMatch) {
|
|
171
|
+
const funcName = shortenFunctionName(ffMatch[1]) || "<anonymous>";
|
|
172
|
+
const file = shortenFilePath(ffMatch[2]);
|
|
173
|
+
lines.push(`${funcName}()`);
|
|
174
|
+
lines.push(` ${file}:${ffMatch[3]}`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const ffAnonMatch = line.match(/^@(.+):(\d+):\d+$/);
|
|
178
|
+
if (ffAnonMatch) {
|
|
179
|
+
const file = shortenFilePath(ffAnonMatch[1]);
|
|
180
|
+
lines.push(`<anonymous>()`);
|
|
181
|
+
lines.push(` ${file}:${ffAnonMatch[2]}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return lines.join("\n") + "\n";
|
|
187
|
+
}
|
|
188
|
+
function shortenFunctionName(fn) {
|
|
189
|
+
const slashIdx = fn.lastIndexOf("/");
|
|
190
|
+
if (slashIdx >= 0) {
|
|
191
|
+
fn = fn.slice(slashIdx + 1);
|
|
192
|
+
}
|
|
193
|
+
const dotIdx = fn.indexOf(".");
|
|
194
|
+
if (dotIdx >= 0) {
|
|
195
|
+
fn = fn.slice(dotIdx + 1);
|
|
196
|
+
}
|
|
197
|
+
return fn;
|
|
198
|
+
}
|
|
199
|
+
function shortenFilePath(filePath) {
|
|
200
|
+
const parts = filePath.split("/");
|
|
201
|
+
return parts[parts.length - 1];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/global-handlers.ts
|
|
205
|
+
import { nowISO as nowISO2 } from "@tracewayapp/core";
|
|
206
|
+
function installGlobalHandlers(client2) {
|
|
207
|
+
const prevOnError = window.onerror;
|
|
208
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
209
|
+
if (error) {
|
|
210
|
+
client2.addException({
|
|
211
|
+
traceId: null,
|
|
212
|
+
stackTrace: formatBrowserStackTrace(error),
|
|
213
|
+
recordedAt: nowISO2(),
|
|
214
|
+
isMessage: false
|
|
215
|
+
});
|
|
216
|
+
} else {
|
|
217
|
+
client2.addException({
|
|
218
|
+
traceId: null,
|
|
219
|
+
stackTrace: String(message),
|
|
220
|
+
recordedAt: nowISO2(),
|
|
221
|
+
isMessage: false
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (typeof prevOnError === "function") {
|
|
225
|
+
return prevOnError(message, source, lineno, colno, error);
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
};
|
|
229
|
+
const prevOnUnhandledRejection = window.onunhandledrejection;
|
|
230
|
+
window.onunhandledrejection = (event) => {
|
|
231
|
+
const reason = event.reason;
|
|
232
|
+
if (reason instanceof Error) {
|
|
233
|
+
client2.addException({
|
|
234
|
+
traceId: null,
|
|
235
|
+
stackTrace: formatBrowserStackTrace(reason),
|
|
236
|
+
recordedAt: nowISO2(),
|
|
237
|
+
isMessage: false
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
client2.addException({
|
|
241
|
+
traceId: null,
|
|
242
|
+
stackTrace: String(reason),
|
|
243
|
+
recordedAt: nowISO2(),
|
|
244
|
+
isMessage: false
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (typeof prevOnUnhandledRejection === "function") {
|
|
248
|
+
prevOnUnhandledRejection.call(window, event);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/index.ts
|
|
254
|
+
var client = null;
|
|
255
|
+
function init(connectionString, options = {}) {
|
|
256
|
+
client = new TracewayFrontendClient(connectionString, options);
|
|
257
|
+
if (typeof window !== "undefined") {
|
|
258
|
+
installGlobalHandlers(client);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function captureException(error) {
|
|
262
|
+
if (!client) return;
|
|
263
|
+
client.addException({
|
|
264
|
+
traceId: null,
|
|
265
|
+
stackTrace: formatBrowserStackTrace(error),
|
|
266
|
+
recordedAt: nowISO3(),
|
|
267
|
+
isMessage: false
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function captureExceptionWithAttributes(error, attributes) {
|
|
271
|
+
if (!client) return;
|
|
272
|
+
client.addException({
|
|
273
|
+
traceId: null,
|
|
274
|
+
stackTrace: formatBrowserStackTrace(error),
|
|
275
|
+
recordedAt: nowISO3(),
|
|
276
|
+
attributes,
|
|
277
|
+
isMessage: false
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function captureMessage(msg) {
|
|
281
|
+
if (!client) return;
|
|
282
|
+
client.addException({
|
|
283
|
+
traceId: null,
|
|
284
|
+
stackTrace: msg,
|
|
285
|
+
recordedAt: nowISO3(),
|
|
286
|
+
isMessage: true
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
async function flush() {
|
|
290
|
+
if (!client) return;
|
|
291
|
+
await client.flush();
|
|
292
|
+
}
|
|
293
|
+
export {
|
|
294
|
+
TracewayFrontendClient,
|
|
295
|
+
captureException,
|
|
296
|
+
captureExceptionWithAttributes,
|
|
297
|
+
captureMessage,
|
|
298
|
+
flush,
|
|
299
|
+
formatBrowserStackTrace,
|
|
300
|
+
init,
|
|
301
|
+
installGlobalHandlers
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/transport.ts","../src/stack-trace.ts","../src/global-handlers.ts"],"sourcesContent":["import { nowISO } from \"@tracewayapp/core\";\nimport type { ExceptionStackTrace } from \"@tracewayapp/core\";\nimport {\n TracewayFrontendClient,\n type TracewayFrontendOptions,\n} from \"./client.js\";\nimport { formatBrowserStackTrace } from \"./stack-trace.js\";\nimport { installGlobalHandlers } from \"./global-handlers.js\";\n\nlet client: TracewayFrontendClient | null = null;\n\nexport function init(\n connectionString: string,\n options: TracewayFrontendOptions = {},\n): void {\n client = new TracewayFrontendClient(connectionString, options);\n if (typeof window !== \"undefined\") {\n installGlobalHandlers(client);\n }\n}\n\nexport function captureException(error: Error): void {\n if (!client) return;\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(error),\n recordedAt: nowISO(),\n isMessage: false,\n });\n}\n\nexport function captureExceptionWithAttributes(\n error: Error,\n attributes?: Record<string, string>,\n): void {\n if (!client) return;\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(error),\n recordedAt: nowISO(),\n attributes,\n isMessage: false,\n });\n}\n\nexport function captureMessage(msg: string): void {\n if (!client) return;\n client.addException({\n traceId: null,\n stackTrace: msg,\n recordedAt: nowISO(),\n isMessage: true,\n });\n}\n\nexport async function flush(): Promise<void> {\n if (!client) return;\n await client.flush();\n}\n\nexport { TracewayFrontendClient } from \"./client.js\";\nexport type { TracewayFrontendOptions } from \"./client.js\";\nexport { formatBrowserStackTrace } from \"./stack-trace.js\";\nexport { installGlobalHandlers } from \"./global-handlers.js\";\n\nexport type {\n ExceptionStackTrace,\n CollectionFrame,\n ReportRequest,\n} from \"@tracewayapp/core\";\n","import type {\n ExceptionStackTrace,\n ReportRequest,\n CollectionFrame,\n} from \"@tracewayapp/core\";\nimport { parseConnectionString, nowISO } from \"@tracewayapp/core\";\nimport { sendReport } from \"./transport.js\";\n\nexport interface TracewayFrontendOptions {\n debug?: boolean;\n debounceMs?: number;\n retryDelayMs?: number;\n version?: string;\n}\n\nexport class TracewayFrontendClient {\n private apiUrl: string;\n private token: string;\n private debug: boolean;\n private debounceMs: number;\n private retryDelayMs: number;\n private version: string;\n\n private pendingExceptions: ExceptionStackTrace[] = [];\n private isSyncing = false;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(connectionString: string, options: TracewayFrontendOptions = {}) {\n const { token, apiUrl } = parseConnectionString(connectionString);\n this.apiUrl = apiUrl;\n this.token = token;\n this.debug = options.debug ?? false;\n this.debounceMs = options.debounceMs ?? 1500;\n this.retryDelayMs = options.retryDelayMs ?? 10000;\n this.version = options.version ?? \"\";\n }\n\n addException(exception: ExceptionStackTrace): void {\n this.pendingExceptions.push(exception);\n this.scheduleSync();\n }\n\n private scheduleSync(): void {\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer);\n }\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n this.doSync();\n }, this.debounceMs);\n }\n\n private async doSync(): Promise<void> {\n if (this.isSyncing) return;\n if (this.pendingExceptions.length === 0) return;\n\n this.isSyncing = true;\n const batch = this.pendingExceptions.splice(0);\n\n const frame: CollectionFrame = {\n stackTraces: batch,\n metrics: [],\n traces: [],\n };\n\n const payload: ReportRequest = {\n collectionFrames: [frame],\n appVersion: this.version,\n serverName: \"\",\n };\n\n let failed = false;\n try {\n const success = await sendReport(\n this.apiUrl,\n this.token,\n JSON.stringify(payload),\n );\n if (!success) {\n failed = true;\n this.pendingExceptions.unshift(...batch);\n if (this.debug) {\n console.error(\"Traceway: sync failed, re-queued exceptions\");\n }\n }\n } catch (err) {\n failed = true;\n this.pendingExceptions.unshift(...batch);\n if (this.debug) {\n console.error(\"Traceway: sync error:\", err);\n }\n } finally {\n this.isSyncing = false;\n if (this.pendingExceptions.length > 0) {\n if (failed) {\n // On failure, wait before retrying to avoid hammering the server\n this.scheduleRetry();\n } else {\n // On success, process remaining items immediately\n this.doSync();\n }\n }\n }\n }\n\n private scheduleRetry(): void {\n if (this.retryTimer !== null) return; // Already scheduled\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.doSync();\n }, this.retryDelayMs);\n }\n\n async flush(): Promise<void> {\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n await this.doSync();\n }\n}\n","export async function compressGzip(data: string): Promise<Uint8Array> {\n const encoder = new TextEncoder();\n const inputBytes = encoder.encode(data);\n\n const cs = new CompressionStream(\"gzip\");\n const writer = cs.writable.getWriter();\n writer.write(inputBytes);\n writer.close();\n\n const reader = cs.readable.getReader();\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n totalLength += value.length;\n }\n\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\nexport async function sendReport(\n apiUrl: string,\n token: string,\n body: string,\n): Promise<boolean> {\n const compressed = await compressGzip(body);\n\n const resp = await fetch(apiUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Encoding\": \"gzip\",\n Authorization: `Bearer ${token}`,\n },\n body: compressed,\n });\n\n return resp.status === 200;\n}\n","export function formatBrowserStackTrace(error: Error): string {\n const lines: string[] = [];\n const typeName = error.constructor?.name || \"Error\";\n lines.push(`${typeName}: ${error.message}`);\n\n if (error.stack) {\n const stackLines = error.stack.split(\"\\n\");\n for (const line of stackLines) {\n // V8 format: \" at funcName (file:line:col)\"\n const v8Match = line.match(/^\\s+at\\s+(.+?)\\s+\\((.+):(\\d+):\\d+\\)$/);\n if (v8Match) {\n const funcName = shortenFunctionName(v8Match[1]);\n const file = shortenFilePath(v8Match[2]);\n lines.push(`${funcName}()`);\n lines.push(` ${file}:${v8Match[3]}`);\n continue;\n }\n\n // V8 anonymous: \" at file:line:col\"\n const v8AnonMatch = line.match(/^\\s+at\\s+(.+):(\\d+):\\d+$/);\n if (v8AnonMatch) {\n const file = shortenFilePath(v8AnonMatch[1]);\n lines.push(`<anonymous>()`);\n lines.push(` ${file}:${v8AnonMatch[2]}`);\n continue;\n }\n\n // Firefox format: \"funcName@file:line:col\"\n const ffMatch = line.match(/^(.+)@(.+):(\\d+):\\d+$/);\n if (ffMatch) {\n const funcName = shortenFunctionName(ffMatch[1]) || \"<anonymous>\";\n const file = shortenFilePath(ffMatch[2]);\n lines.push(`${funcName}()`);\n lines.push(` ${file}:${ffMatch[3]}`);\n continue;\n }\n\n // Firefox anonymous: \"@file:line:col\"\n const ffAnonMatch = line.match(/^@(.+):(\\d+):\\d+$/);\n if (ffAnonMatch) {\n const file = shortenFilePath(ffAnonMatch[1]);\n lines.push(`<anonymous>()`);\n lines.push(` ${file}:${ffAnonMatch[2]}`);\n continue;\n }\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction shortenFunctionName(fn: string): string {\n const slashIdx = fn.lastIndexOf(\"/\");\n if (slashIdx >= 0) {\n fn = fn.slice(slashIdx + 1);\n }\n const dotIdx = fn.indexOf(\".\");\n if (dotIdx >= 0) {\n fn = fn.slice(dotIdx + 1);\n }\n return fn;\n}\n\nfunction shortenFilePath(filePath: string): string {\n const parts = filePath.split(\"/\");\n return parts[parts.length - 1];\n}\n","import type { TracewayFrontendClient } from \"./client.js\";\nimport { formatBrowserStackTrace } from \"./stack-trace.js\";\nimport { nowISO } from \"@tracewayapp/core\";\n\nexport function installGlobalHandlers(client: TracewayFrontendClient): void {\n const prevOnError = window.onerror;\n window.onerror = (message, source, lineno, colno, error) => {\n if (error) {\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(error),\n recordedAt: nowISO(),\n isMessage: false,\n });\n } else {\n client.addException({\n traceId: null,\n stackTrace: String(message),\n recordedAt: nowISO(),\n isMessage: false,\n });\n }\n if (typeof prevOnError === \"function\") {\n return prevOnError(message, source, lineno, colno, error);\n }\n return false;\n };\n\n const prevOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n if (reason instanceof Error) {\n client.addException({\n traceId: null,\n stackTrace: formatBrowserStackTrace(reason),\n recordedAt: nowISO(),\n isMessage: false,\n });\n } else {\n client.addException({\n traceId: null,\n stackTrace: String(reason),\n recordedAt: nowISO(),\n isMessage: false,\n });\n }\n if (typeof prevOnUnhandledRejection === \"function\") {\n prevOnUnhandledRejection.call(window, event);\n }\n };\n}\n"],"mappings":"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","names":["nowISO","nowISO","client","nowISO"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tracewayapp/frontend",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Traceway SDK for browser environments",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@tracewayapp/core": "0.2.0"
|
|
27
|
+
}
|
|
28
|
+
}
|