@atsail/browser 0.1.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.d.ts +6 -0
- package/dist/index.js +174 -0
- package/dist/instrumentation.d.ts +6 -0
- package/dist/transport.d.ts +13 -0
- package/dist/types.d.ts +36 -0
- package/package.json +17 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EventLevel, InitOptions } from "./types";
|
|
2
|
+
export declare function init(options: InitOptions): void;
|
|
3
|
+
export declare function captureException(error: unknown, context?: Record<string, unknown>): void;
|
|
4
|
+
export declare function captureMessage(message: string, level?: EventLevel, context?: Record<string, unknown>): void;
|
|
5
|
+
export declare function flush(): void;
|
|
6
|
+
export type { AtsailEvent, EventLevel, InitOptions } from "./types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/transport.ts
|
|
2
|
+
var DEFAULT_ENDPOINT = "https://api.atsail.io/events";
|
|
3
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
4
|
+
var DEFAULT_MAX_BATCH_SIZE = 20;
|
|
5
|
+
|
|
6
|
+
class Transport {
|
|
7
|
+
publishableKey;
|
|
8
|
+
endpoint;
|
|
9
|
+
flushIntervalMs;
|
|
10
|
+
maxBatchSize;
|
|
11
|
+
queue = [];
|
|
12
|
+
timer = null;
|
|
13
|
+
constructor(publishableKey, endpoint = DEFAULT_ENDPOINT, flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
|
|
14
|
+
this.publishableKey = publishableKey;
|
|
15
|
+
this.endpoint = endpoint;
|
|
16
|
+
this.flushIntervalMs = flushIntervalMs;
|
|
17
|
+
this.maxBatchSize = maxBatchSize;
|
|
18
|
+
this.timer = setInterval(() => this.flush(), this.flushIntervalMs);
|
|
19
|
+
document.addEventListener("visibilitychange", () => {
|
|
20
|
+
if (document.visibilityState === "hidden")
|
|
21
|
+
this.flush(true);
|
|
22
|
+
});
|
|
23
|
+
window.addEventListener("pagehide", () => this.flush(true));
|
|
24
|
+
}
|
|
25
|
+
enqueue(event) {
|
|
26
|
+
this.queue.push(event);
|
|
27
|
+
if (this.queue.length >= this.maxBatchSize)
|
|
28
|
+
this.flush();
|
|
29
|
+
}
|
|
30
|
+
flush(useBeacon = false) {
|
|
31
|
+
if (this.queue.length === 0)
|
|
32
|
+
return;
|
|
33
|
+
const events = this.queue;
|
|
34
|
+
this.queue = [];
|
|
35
|
+
const body = JSON.stringify({ publishableKey: this.publishableKey, events });
|
|
36
|
+
if (useBeacon && navigator.sendBeacon) {
|
|
37
|
+
const blob = new Blob([body], { type: "application/json" });
|
|
38
|
+
navigator.sendBeacon(this.endpoint, blob);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
fetch(this.endpoint, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body,
|
|
45
|
+
keepalive: true
|
|
46
|
+
}).catch(() => {
|
|
47
|
+
return;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
destroy() {
|
|
51
|
+
if (this.timer)
|
|
52
|
+
clearInterval(this.timer);
|
|
53
|
+
this.flush(true);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/instrumentation.ts
|
|
58
|
+
function installGlobalHandlers(emit) {
|
|
59
|
+
window.addEventListener("error", (e) => {
|
|
60
|
+
emit({
|
|
61
|
+
type: "exception",
|
|
62
|
+
message: e.message,
|
|
63
|
+
stack: e.error?.stack,
|
|
64
|
+
source: e.filename,
|
|
65
|
+
line: e.lineno,
|
|
66
|
+
col: e.colno,
|
|
67
|
+
timestamp: Date.now()
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
71
|
+
const reason = e.reason;
|
|
72
|
+
emit({
|
|
73
|
+
type: "unhandledrejection",
|
|
74
|
+
message: reason instanceof Error ? reason.message : String(reason),
|
|
75
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
76
|
+
timestamp: Date.now()
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function installFetchInterceptor(emit) {
|
|
81
|
+
const originalFetch = window.fetch;
|
|
82
|
+
if (!originalFetch)
|
|
83
|
+
return;
|
|
84
|
+
const patchedFetch = async (...args) => {
|
|
85
|
+
const [input, init] = args;
|
|
86
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
87
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
88
|
+
const response = await originalFetch(...args);
|
|
89
|
+
if (response.status >= 400) {
|
|
90
|
+
emit({
|
|
91
|
+
type: "network_error",
|
|
92
|
+
url,
|
|
93
|
+
method,
|
|
94
|
+
status: response.status,
|
|
95
|
+
statusText: response.statusText,
|
|
96
|
+
timestamp: Date.now()
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return response;
|
|
100
|
+
};
|
|
101
|
+
window.fetch = Object.assign(patchedFetch, originalFetch);
|
|
102
|
+
}
|
|
103
|
+
function installXhrInterceptor(emit) {
|
|
104
|
+
const OriginalXhr = window.XMLHttpRequest;
|
|
105
|
+
if (!OriginalXhr)
|
|
106
|
+
return;
|
|
107
|
+
const originalOpen = OriginalXhr.prototype.open;
|
|
108
|
+
const originalSend = OriginalXhr.prototype.send;
|
|
109
|
+
OriginalXhr.prototype.open = function(method, url, ...rest) {
|
|
110
|
+
this.__atsailMethod = method;
|
|
111
|
+
this.__atsailUrl = String(url);
|
|
112
|
+
return originalOpen.call(this, method, url, ...rest);
|
|
113
|
+
};
|
|
114
|
+
OriginalXhr.prototype.send = function(...args) {
|
|
115
|
+
this.addEventListener("loadend", () => {
|
|
116
|
+
if (this.status >= 400) {
|
|
117
|
+
emit({
|
|
118
|
+
type: "network_error",
|
|
119
|
+
url: this.__atsailUrl ?? "",
|
|
120
|
+
method: this.__atsailMethod ?? "GET",
|
|
121
|
+
status: this.status,
|
|
122
|
+
statusText: this.statusText,
|
|
123
|
+
timestamp: Date.now()
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return originalSend.call(this, ...args);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/index.ts
|
|
132
|
+
var transport = null;
|
|
133
|
+
function init(options) {
|
|
134
|
+
if (transport)
|
|
135
|
+
return;
|
|
136
|
+
if (typeof window === "undefined")
|
|
137
|
+
return;
|
|
138
|
+
transport = new Transport(options.publishableKey, options.endpoint, options.flushIntervalMs, options.maxBatchSize);
|
|
139
|
+
const emit = transport.enqueue.bind(transport);
|
|
140
|
+
installGlobalHandlers(emit);
|
|
141
|
+
installFetchInterceptor(emit);
|
|
142
|
+
installXhrInterceptor(emit);
|
|
143
|
+
}
|
|
144
|
+
function captureException(error, context) {
|
|
145
|
+
if (!transport)
|
|
146
|
+
return;
|
|
147
|
+
transport.enqueue({
|
|
148
|
+
type: "exception",
|
|
149
|
+
message: error instanceof Error ? error.message : String(error),
|
|
150
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
151
|
+
context,
|
|
152
|
+
timestamp: Date.now()
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function captureMessage(message, level = "info", context) {
|
|
156
|
+
if (!transport)
|
|
157
|
+
return;
|
|
158
|
+
transport.enqueue({
|
|
159
|
+
type: "message",
|
|
160
|
+
message,
|
|
161
|
+
level,
|
|
162
|
+
context,
|
|
163
|
+
timestamp: Date.now()
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function flush() {
|
|
167
|
+
transport?.flush();
|
|
168
|
+
}
|
|
169
|
+
export {
|
|
170
|
+
init,
|
|
171
|
+
flush,
|
|
172
|
+
captureMessage,
|
|
173
|
+
captureException
|
|
174
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AtsailEvent } from "./types";
|
|
2
|
+
type Emit = (event: AtsailEvent) => void;
|
|
3
|
+
export declare function installGlobalHandlers(emit: Emit): void;
|
|
4
|
+
export declare function installFetchInterceptor(emit: Emit): void;
|
|
5
|
+
export declare function installXhrInterceptor(emit: Emit): void;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AtsailEvent } from "./types";
|
|
2
|
+
export declare class Transport {
|
|
3
|
+
private readonly publishableKey;
|
|
4
|
+
private readonly endpoint;
|
|
5
|
+
private readonly flushIntervalMs;
|
|
6
|
+
private readonly maxBatchSize;
|
|
7
|
+
private queue;
|
|
8
|
+
private timer;
|
|
9
|
+
constructor(publishableKey: string, endpoint?: string, flushIntervalMs?: number, maxBatchSize?: number);
|
|
10
|
+
enqueue(event: AtsailEvent): void;
|
|
11
|
+
flush(useBeacon?: boolean): void;
|
|
12
|
+
destroy(): void;
|
|
13
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type EventLevel = "info" | "warning" | "error";
|
|
2
|
+
export type AtsailEvent = {
|
|
3
|
+
type: "exception";
|
|
4
|
+
message: string;
|
|
5
|
+
stack?: string;
|
|
6
|
+
source?: string;
|
|
7
|
+
line?: number;
|
|
8
|
+
col?: number;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
} | {
|
|
12
|
+
type: "unhandledrejection";
|
|
13
|
+
message: string;
|
|
14
|
+
stack?: string;
|
|
15
|
+
context?: Record<string, unknown>;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
} | {
|
|
18
|
+
type: "network_error";
|
|
19
|
+
url: string;
|
|
20
|
+
method: string;
|
|
21
|
+
status: number;
|
|
22
|
+
statusText?: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
} | {
|
|
25
|
+
type: "message";
|
|
26
|
+
message: string;
|
|
27
|
+
level: EventLevel;
|
|
28
|
+
context?: Record<string, unknown>;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
};
|
|
31
|
+
export type InitOptions = {
|
|
32
|
+
publishableKey: string;
|
|
33
|
+
endpoint?: string;
|
|
34
|
+
flushIntervalMs?: number;
|
|
35
|
+
maxBatchSize?: number;
|
|
36
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atsail/browser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AtSail browser SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "bun build src/index.ts --outdir dist --target browser --format esm && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"typescript": "^5.0.0",
|
|
15
|
+
"@types/bun": "latest"
|
|
16
|
+
}
|
|
17
|
+
}
|