@sekolahcode/devpulse-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/README.md +100 -0
- package/dist/devpulse.es.js +218 -0
- package/dist/devpulse.es.js.map +1 -0
- package/dist/devpulse.umd.js +3 -0
- package/dist/devpulse.umd.js.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @sekolahcode/devpulse-browser
|
|
2
|
+
|
|
3
|
+
Zero-dependency browser SDK for DevPulse — frontend error tracking and Core Web Vitals monitoring.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- A running DevPulse server
|
|
8
|
+
- Any modern browser (ES2017+)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
### Via script tag (UMD)
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<script src="http://localhost:8000/devpulse.js"></script>
|
|
16
|
+
<script>
|
|
17
|
+
DevPulse.init({
|
|
18
|
+
dsn: 'http://localhost:8000/api/ingest/YOUR_API_KEY',
|
|
19
|
+
environment: 'production',
|
|
20
|
+
release: '1.0.0',
|
|
21
|
+
});
|
|
22
|
+
</script>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Via npm
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @sekolahcode/devpulse-browser
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { DevPulse } from '@sekolahcode/devpulse-browser';
|
|
33
|
+
|
|
34
|
+
DevPulse.init({
|
|
35
|
+
dsn: 'http://localhost:8000/api/ingest/YOUR_API_KEY',
|
|
36
|
+
environment: 'production',
|
|
37
|
+
release: '1.0.0',
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
### `DevPulse.init(config)`
|
|
44
|
+
|
|
45
|
+
| Option | Default | Description |
|
|
46
|
+
|---------------------|----------------|----------------------------------------------------|
|
|
47
|
+
| `dsn` | *(required)* | Ingest endpoint URL including your API key |
|
|
48
|
+
| `environment` | `"production"` | Environment tag attached to every event |
|
|
49
|
+
| `release` | `null` | Release/version tag (e.g. `"1.2.3"`) |
|
|
50
|
+
| `enabled` | `true` | Enable / disable the SDK globally |
|
|
51
|
+
| `trackVitals` | `true` | Auto-track Core Web Vitals |
|
|
52
|
+
| `tracesSampleRate` | `1.0` | Fraction of events to send (0.0–1.0) |
|
|
53
|
+
|
|
54
|
+
After `init()`, uncaught JS errors and unhandled promise rejections are captured automatically.
|
|
55
|
+
|
|
56
|
+
### `DevPulse.capture(error, extra?)`
|
|
57
|
+
|
|
58
|
+
Manually capture an `Error` object with optional extra context.
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
try {
|
|
62
|
+
riskyOperation();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
DevPulse.capture(err, { userId: 42 });
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `DevPulse.captureMessage(message, level?)`
|
|
69
|
+
|
|
70
|
+
Capture a plain string message. `level` defaults to `"info"`.
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
DevPulse.captureMessage('Quota limit approaching', 'warning');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `DevPulse.setUser(user)`
|
|
77
|
+
|
|
78
|
+
Attach user identity to all subsequent events.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
DevPulse.setUser({ id: '123', email: 'user@example.com', name: 'Alice' });
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Call `DevPulse.clearUser()` on logout.
|
|
85
|
+
|
|
86
|
+
### Core Web Vitals
|
|
87
|
+
|
|
88
|
+
When `trackVitals: true` (default), the SDK automatically tracks LCP, FID, CLS, FCP, and TTFB using the browser's `PerformanceObserver` API.
|
|
89
|
+
|
|
90
|
+
## Build
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install
|
|
94
|
+
npm run build # outputs dist/devpulse.es.js and dist/devpulse.umd.js
|
|
95
|
+
npm test # run Vitest unit tests
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT — see [LICENSE](../../LICENSE)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
var b = Object.defineProperty, v = Object.defineProperties;
|
|
2
|
+
var S = Object.getOwnPropertyDescriptors;
|
|
3
|
+
var f = Object.getOwnPropertySymbols;
|
|
4
|
+
var E = Object.prototype.hasOwnProperty, y = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var m = (r, e, t) => e in r ? b(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t, o = (r, e) => {
|
|
6
|
+
for (var t in e || (e = {}))
|
|
7
|
+
E.call(e, t) && m(r, t, e[t]);
|
|
8
|
+
if (f)
|
|
9
|
+
for (var t of f(e))
|
|
10
|
+
y.call(e, t) && m(r, t, e[t]);
|
|
11
|
+
return r;
|
|
12
|
+
}, u = (r, e) => v(r, S(e));
|
|
13
|
+
function _(r, e = {}) {
|
|
14
|
+
var t, n, s;
|
|
15
|
+
return {
|
|
16
|
+
level: "error",
|
|
17
|
+
exception: {
|
|
18
|
+
type: (t = r.name) != null ? t : "Error",
|
|
19
|
+
message: (n = r.message) != null ? n : String(r),
|
|
20
|
+
stacktrace: T(r.stack)
|
|
21
|
+
},
|
|
22
|
+
context: c(),
|
|
23
|
+
request: d(),
|
|
24
|
+
user: (s = e.user) != null ? s : null,
|
|
25
|
+
platform: "browser",
|
|
26
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function P(r, e = "info", t = {}) {
|
|
30
|
+
var n;
|
|
31
|
+
return {
|
|
32
|
+
level: e,
|
|
33
|
+
message: r,
|
|
34
|
+
context: c(),
|
|
35
|
+
request: d(),
|
|
36
|
+
user: (n = t.user) != null ? n : null,
|
|
37
|
+
platform: "browser",
|
|
38
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function l(r, e, t = {}) {
|
|
42
|
+
var i;
|
|
43
|
+
const n = (i = t.unit) != null ? i : "ms", s = n === "ms" ? Math.round(e) : +Number(e).toFixed(4);
|
|
44
|
+
return {
|
|
45
|
+
level: "info",
|
|
46
|
+
message: `Performance: ${r} = ${s}${n}`,
|
|
47
|
+
context: u(o({}, c()), {
|
|
48
|
+
performance: { name: r, value: s, unit: n }
|
|
49
|
+
}),
|
|
50
|
+
request: d(),
|
|
51
|
+
platform: "browser",
|
|
52
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function T(r) {
|
|
56
|
+
return r ? r.split(`
|
|
57
|
+
`).slice(1).map((e) => {
|
|
58
|
+
var i, a, h;
|
|
59
|
+
const t = e.trim(), n = t.match(/at\s+(.*?)\s+\((.*?):(\d+):(\d+)\)/);
|
|
60
|
+
if (n)
|
|
61
|
+
return {
|
|
62
|
+
function: (i = n[1]) != null ? i : null,
|
|
63
|
+
file: (a = n[2]) != null ? a : null,
|
|
64
|
+
line: parseInt(n[3]) || null,
|
|
65
|
+
column: parseInt(n[4]) || null
|
|
66
|
+
};
|
|
67
|
+
const s = t.match(/at\s+(.*?):(\d+):(\d+)/);
|
|
68
|
+
return s ? {
|
|
69
|
+
function: null,
|
|
70
|
+
file: (h = s[1]) != null ? h : null,
|
|
71
|
+
line: parseInt(s[2]) || null,
|
|
72
|
+
column: parseInt(s[3]) || null
|
|
73
|
+
} : { raw: t };
|
|
74
|
+
}).filter((e) => e.file || e.raw) : [];
|
|
75
|
+
}
|
|
76
|
+
function c() {
|
|
77
|
+
return {
|
|
78
|
+
url: window.location.href,
|
|
79
|
+
userAgent: navigator.userAgent,
|
|
80
|
+
language: navigator.language,
|
|
81
|
+
viewport: {
|
|
82
|
+
width: window.innerWidth,
|
|
83
|
+
height: window.innerHeight
|
|
84
|
+
},
|
|
85
|
+
screen: {
|
|
86
|
+
width: window.screen.width,
|
|
87
|
+
height: window.screen.height
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function d() {
|
|
92
|
+
return {
|
|
93
|
+
url: window.location.href
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
class x {
|
|
97
|
+
constructor(e, t = {}) {
|
|
98
|
+
var n;
|
|
99
|
+
this.dsn = e, this.timeout = (n = t.timeout) != null ? n : 5e3;
|
|
100
|
+
}
|
|
101
|
+
send(e) {
|
|
102
|
+
const t = JSON.stringify(e), n = new AbortController(), s = setTimeout(() => n.abort(), this.timeout);
|
|
103
|
+
return fetch(this.dsn, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/json" },
|
|
106
|
+
body: t,
|
|
107
|
+
keepalive: !0,
|
|
108
|
+
credentials: "omit",
|
|
109
|
+
signal: n.signal
|
|
110
|
+
}).catch(() => {
|
|
111
|
+
}).finally(() => clearTimeout(s)), !0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
class I {
|
|
115
|
+
constructor() {
|
|
116
|
+
this.transport = null, this.config = {}, this.user = null, this._installed = !1;
|
|
117
|
+
}
|
|
118
|
+
init(e = {}) {
|
|
119
|
+
var t, n, s, i, a;
|
|
120
|
+
if (!e.dsn) {
|
|
121
|
+
console.warn("[DevPulse] DSN is required");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.config = {
|
|
125
|
+
dsn: e.dsn,
|
|
126
|
+
environment: (t = e.environment) != null ? t : "production",
|
|
127
|
+
release: (n = e.release) != null ? n : null,
|
|
128
|
+
enabled: (s = e.enabled) != null ? s : !0,
|
|
129
|
+
trackVitals: (i = e.trackVitals) != null ? i : !0,
|
|
130
|
+
tracesSampleRate: (a = e.tracesSampleRate) != null ? a : 1
|
|
131
|
+
}, this.transport = new x(this.config.dsn), this.config.enabled && (this._installHandlers(), this.config.trackVitals && this._trackWebVitals());
|
|
132
|
+
}
|
|
133
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
134
|
+
capture(e, t = {}) {
|
|
135
|
+
!this.transport || !this.config.enabled || Math.random() > this.config.tracesSampleRate || this.transport.send(u(o(o({}, _(e)), t), {
|
|
136
|
+
// Core identity fields always win over anything in extra
|
|
137
|
+
user: this.user,
|
|
138
|
+
environment: this.config.environment,
|
|
139
|
+
release: this.config.release
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
captureMessage(e, t = "info") {
|
|
143
|
+
!this.transport || !this.config.enabled || Math.random() > this.config.tracesSampleRate || this.transport.send(u(o({}, P(e, t)), {
|
|
144
|
+
user: this.user,
|
|
145
|
+
environment: this.config.environment,
|
|
146
|
+
release: this.config.release
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
setUser(e) {
|
|
150
|
+
this.user = e;
|
|
151
|
+
}
|
|
152
|
+
clearUser() {
|
|
153
|
+
this.user = null;
|
|
154
|
+
}
|
|
155
|
+
// ── Error Handlers ────────────────────────────────────────────────────────
|
|
156
|
+
_installHandlers() {
|
|
157
|
+
this._installed || (this._installed = !0, window.addEventListener("error", (e) => {
|
|
158
|
+
var t;
|
|
159
|
+
this.capture((t = e.error) != null ? t : new Error(e.message), {
|
|
160
|
+
context: {
|
|
161
|
+
filename: e.filename,
|
|
162
|
+
line: e.lineno,
|
|
163
|
+
column: e.colno
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}), window.addEventListener("unhandledrejection", (e) => {
|
|
167
|
+
const t = e.reason instanceof Error ? e.reason : new Error(String(e.reason));
|
|
168
|
+
this.capture(t, { context: { type: "unhandledrejection" } });
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
// ── Core Web Vitals ───────────────────────────────────────────────────────
|
|
172
|
+
_trackWebVitals() {
|
|
173
|
+
if (!("PerformanceObserver" in window)) return;
|
|
174
|
+
this._observe("largest-contentful-paint", (t) => {
|
|
175
|
+
const n = t[t.length - 1];
|
|
176
|
+
this.transport.send(l("LCP", n.startTime));
|
|
177
|
+
}), this._observe("first-input", (t) => {
|
|
178
|
+
const n = t[0];
|
|
179
|
+
this.transport.send(
|
|
180
|
+
l("FID", n.processingStart - n.startTime)
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
let e = 0;
|
|
184
|
+
this._observe("layout-shift", (t) => {
|
|
185
|
+
t.forEach((n) => {
|
|
186
|
+
n.hadRecentInput || (e += n.value);
|
|
187
|
+
});
|
|
188
|
+
}), window.addEventListener("pagehide", () => {
|
|
189
|
+
e > 0 && this.transport.send(l("CLS", e, { unit: "" }));
|
|
190
|
+
}), window.addEventListener("load", () => {
|
|
191
|
+
const t = performance.getEntriesByType("navigation")[0];
|
|
192
|
+
t && (this.transport.send(l("TTFB", t.responseStart)), this.transport.send(l("PageLoad", t.loadEventEnd)));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
_observe(e, t) {
|
|
196
|
+
try {
|
|
197
|
+
new PerformanceObserver((s) => {
|
|
198
|
+
t(s.getEntries());
|
|
199
|
+
}).observe({ type: e, buffered: !0 });
|
|
200
|
+
} catch (n) {
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const k = new I();
|
|
205
|
+
var p, g, w;
|
|
206
|
+
if (typeof document != "undefined") {
|
|
207
|
+
const r = document.currentScript;
|
|
208
|
+
(p = r == null ? void 0 : r.dataset) != null && p.dsn && k.init({
|
|
209
|
+
dsn: r.dataset.dsn,
|
|
210
|
+
environment: (g = r.dataset.env) != null ? g : "production",
|
|
211
|
+
release: (w = r.dataset.release) != null ? w : null
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
k as DevPulse,
|
|
216
|
+
k as default
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=devpulse.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devpulse.es.js","sources":["../src/payload.js","../src/transport.js","../src/index.js"],"sourcesContent":["export function buildFromError(error, options = {}) {\n return {\n level: \"error\",\n exception: {\n type: error.name ?? \"Error\",\n message: error.message ?? String(error),\n stacktrace: parseStack(error.stack),\n },\n context: buildContext(),\n request: buildRequest(),\n user: options.user ?? null,\n platform: \"browser\",\n timestamp: new Date().toISOString(),\n };\n}\n\nexport function buildFromMessage(message, level = \"info\", options = {}) {\n return {\n level,\n message,\n context: buildContext(),\n request: buildRequest(),\n user: options.user ?? null,\n platform: \"browser\",\n timestamp: new Date().toISOString(),\n };\n}\n\n// unit defaults to \"ms\" for timing metrics; pass { unit: \"\" } for unitless\n// scores like CLS which are in the 0–1 range, not milliseconds.\nexport function buildFromPerformance(name, value, options = {}) {\n const unit = options.unit ?? \"ms\";\n const displayValue =\n unit === \"ms\" ? Math.round(value) : +Number(value).toFixed(4);\n return {\n level: \"info\",\n message: `Performance: ${name} = ${displayValue}${unit}`,\n context: {\n ...buildContext(),\n performance: { name, value: displayValue, unit },\n },\n request: buildRequest(),\n platform: \"browser\",\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction parseStack(stack) {\n if (!stack) return [];\n return stack\n .split(\"\\n\")\n .slice(1)\n .map((line) => {\n const trimmed = line.trim();\n\n // \" at functionName (file.js:10:5)\"\n const full = trimmed.match(/at\\s+(.*?)\\s+\\((.*?):(\\d+):(\\d+)\\)/);\n if (full) {\n return {\n function: full[1] ?? null,\n file: full[2] ?? null,\n line: parseInt(full[3]) || null,\n column: parseInt(full[4]) || null,\n };\n }\n\n // \" at file.js:10:5\" (anonymous or arrow functions)\n const short = trimmed.match(/at\\s+(.*?):(\\d+):(\\d+)/);\n if (short) {\n return {\n function: null,\n file: short[1] ?? null,\n line: parseInt(short[2]) || null,\n column: parseInt(short[3]) || null,\n };\n }\n\n return { raw: trimmed };\n })\n .filter((f) => f.file || f.raw);\n}\n\nfunction buildContext() {\n return {\n url: window.location.href,\n userAgent: navigator.userAgent,\n language: navigator.language,\n viewport: {\n width: window.innerWidth,\n height: window.innerHeight,\n },\n screen: {\n width: window.screen.width,\n height: window.screen.height,\n },\n };\n}\n\nfunction buildRequest() {\n return {\n url: window.location.href,\n };\n}\n","export class Transport {\n constructor(dsn, options = {}) {\n this.dsn = dsn;\n this.timeout = options.timeout ?? 5000;\n }\n\n send(payload) {\n const body = JSON.stringify(payload);\n\n // Use fetch with keepalive (survives page navigation) and credentials omitted\n // — the ingest endpoint authenticates via the API key in the URL, not cookies.\n // sendBeacon is avoided because it always sends credentials, which breaks\n // CORS preflight when the server responds with a specific origin.\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n fetch(this.dsn, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n keepalive: true,\n credentials: \"omit\",\n signal: controller.signal,\n })\n .catch(() => {})\n .finally(() => clearTimeout(timer));\n\n return true;\n }\n}\n","import {\n buildFromError,\n buildFromMessage,\n buildFromPerformance,\n} from \"./payload.js\";\nimport { Transport } from \"./transport.js\";\n\nclass DevPulseClient {\n constructor() {\n this.transport = null;\n this.config = {};\n this.user = null;\n this._installed = false;\n }\n\n init(config = {}) {\n if (!config.dsn) {\n console.warn(\"[DevPulse] DSN is required\");\n return;\n }\n\n this.config = {\n dsn: config.dsn,\n environment: config.environment ?? \"production\",\n release: config.release ?? null,\n enabled: config.enabled ?? true,\n trackVitals: config.trackVitals ?? true,\n tracesSampleRate: config.tracesSampleRate ?? 1.0,\n };\n\n this.transport = new Transport(this.config.dsn);\n\n if (!this.config.enabled) return;\n\n this._installHandlers();\n if (this.config.trackVitals) this._trackWebVitals();\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n capture(error, extra = {}) {\n if (!this.transport || !this.config.enabled) return;\n if (Math.random() > this.config.tracesSampleRate) return;\n this.transport.send({\n ...buildFromError(error),\n ...extra,\n // Core identity fields always win over anything in extra\n user: this.user,\n environment: this.config.environment,\n release: this.config.release,\n });\n }\n\n captureMessage(message, level = \"info\") {\n if (!this.transport || !this.config.enabled) return;\n if (Math.random() > this.config.tracesSampleRate) return;\n this.transport.send({\n ...buildFromMessage(message, level),\n user: this.user,\n environment: this.config.environment,\n release: this.config.release,\n });\n }\n\n setUser(user) {\n this.user = user; // { id, email, name }\n }\n\n clearUser() {\n this.user = null;\n }\n\n // ── Error Handlers ────────────────────────────────────────────────────────\n _installHandlers() {\n if (this._installed) return;\n this._installed = true;\n\n // Uncaught JS errors\n window.addEventListener(\"error\", (event) => {\n this.capture(event.error ?? new Error(event.message), {\n context: {\n filename: event.filename,\n line: event.lineno,\n column: event.colno,\n },\n });\n });\n\n // Unhandled Promise rejections\n window.addEventListener(\"unhandledrejection\", (event) => {\n const error =\n event.reason instanceof Error\n ? event.reason\n : new Error(String(event.reason));\n this.capture(error, { context: { type: \"unhandledrejection\" } });\n });\n }\n\n // ── Core Web Vitals ───────────────────────────────────────────────────────\n _trackWebVitals() {\n if (!(\"PerformanceObserver\" in window)) return;\n\n // LCP — Largest Contentful Paint\n this._observe(\"largest-contentful-paint\", (entries) => {\n const lcp = entries[entries.length - 1];\n this.transport.send(buildFromPerformance(\"LCP\", lcp.startTime));\n });\n\n // FID — First Input Delay\n this._observe(\"first-input\", (entries) => {\n const fid = entries[0];\n this.transport.send(\n buildFromPerformance(\"FID\", fid.processingStart - fid.startTime),\n );\n });\n\n // CLS — Cumulative Layout Shift (unitless score 0–1, NOT milliseconds)\n let clsValue = 0;\n this._observe(\"layout-shift\", (entries) => {\n entries.forEach((entry) => {\n if (!entry.hadRecentInput) clsValue += entry.value;\n });\n });\n\n window.addEventListener(\"pagehide\", () => {\n if (clsValue > 0) {\n this.transport.send(buildFromPerformance(\"CLS\", clsValue, { unit: \"\" }));\n }\n });\n\n // TTFB — Time to First Byte\n window.addEventListener(\"load\", () => {\n const nav = performance.getEntriesByType(\"navigation\")[0];\n if (nav) {\n this.transport.send(buildFromPerformance(\"TTFB\", nav.responseStart));\n this.transport.send(buildFromPerformance(\"PageLoad\", nav.loadEventEnd));\n }\n });\n }\n\n _observe(type, callback) {\n try {\n const observer = new PerformanceObserver((list) => {\n callback(list.getEntries());\n });\n observer.observe({ type, buffered: true });\n } catch (e) {\n // Browser doesn't support this metric — silently skip\n }\n }\n}\n\n// Export singleton\nexport const DevPulse = new DevPulseClient();\nexport default DevPulse;\n\n// Auto-init from script tag data attributes\n// <script src=\"devpulse.umd.js\" data-dsn=\"...\" data-env=\"production\"></script>\nif (typeof document !== \"undefined\") {\n const script = document.currentScript;\n if (script?.dataset?.dsn) {\n DevPulse.init({\n dsn: script.dataset.dsn,\n environment: script.dataset.env ?? \"production\",\n release: script.dataset.release ?? null,\n });\n }\n}\n"],"names":["buildFromError","error","options","_a","_b","_c","parseStack","buildContext","buildRequest","buildFromMessage","message","level","buildFromPerformance","name","value","unit","displayValue","__spreadProps","__spreadValues","stack","line","trimmed","full","short","f","Transport","dsn","payload","body","controller","timer","DevPulseClient","config","_d","_e","extra","user","event","entries","lcp","fid","clsValue","entry","nav","type","callback","list","e","DevPulse","script"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
(function(i,s){typeof exports=="object"&&typeof module!="undefined"?s(exports):typeof define=="function"&&define.amd?define(["exports"],s):(i=typeof globalThis!="undefined"?globalThis:i||self,s(i.DevPulse={}))})(this,(function(i){"use strict";var _=Object.defineProperty,D=Object.defineProperties;var x=Object.getOwnPropertyDescriptors;var S=Object.getOwnPropertySymbols;var I=Object.prototype.hasOwnProperty,k=Object.prototype.propertyIsEnumerable;var y=(i,s,a)=>s in i?_(i,s,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[s]=a,d=(i,s)=>{for(var a in s||(s={}))I.call(s,a)&&y(i,a,s[a]);if(S)for(var a of S(s))k.call(s,a)&&y(i,a,s[a]);return i},f=(i,s)=>D(i,x(s));var g,w,b;function s(r,e={}){var t,n,o;return{level:"error",exception:{type:(t=r.name)!=null?t:"Error",message:(n=r.message)!=null?n:String(r),stacktrace:E(r.stack)},context:h(),request:m(),user:(o=e.user)!=null?o:null,platform:"browser",timestamp:new Date().toISOString()}}function a(r,e="info",t={}){var n;return{level:e,message:r,context:h(),request:m(),user:(n=t.user)!=null?n:null,platform:"browser",timestamp:new Date().toISOString()}}function u(r,e,t={}){var l;const n=(l=t.unit)!=null?l:"ms",o=n==="ms"?Math.round(e):+Number(e).toFixed(4);return{level:"info",message:`Performance: ${r} = ${o}${n}`,context:f(d({},h()),{performance:{name:r,value:o,unit:n}}),request:m(),platform:"browser",timestamp:new Date().toISOString()}}function E(r){return r?r.split(`
|
|
2
|
+
`).slice(1).map(e=>{var l,c,v;const t=e.trim(),n=t.match(/at\s+(.*?)\s+\((.*?):(\d+):(\d+)\)/);if(n)return{function:(l=n[1])!=null?l:null,file:(c=n[2])!=null?c:null,line:parseInt(n[3])||null,column:parseInt(n[4])||null};const o=t.match(/at\s+(.*?):(\d+):(\d+)/);return o?{function:null,file:(v=o[1])!=null?v:null,line:parseInt(o[2])||null,column:parseInt(o[3])||null}:{raw:t}}).filter(e=>e.file||e.raw):[]}function h(){return{url:window.location.href,userAgent:navigator.userAgent,language:navigator.language,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height}}}function m(){return{url:window.location.href}}class P{constructor(e,t={}){var n;this.dsn=e,this.timeout=(n=t.timeout)!=null?n:5e3}send(e){const t=JSON.stringify(e),n=new AbortController,o=setTimeout(()=>n.abort(),this.timeout);return fetch(this.dsn,{method:"POST",headers:{"Content-Type":"application/json"},body:t,keepalive:!0,credentials:"omit",signal:n.signal}).catch(()=>{}).finally(()=>clearTimeout(o)),!0}}class T{constructor(){this.transport=null,this.config={},this.user=null,this._installed=!1}init(e={}){var t,n,o,l,c;if(!e.dsn){console.warn("[DevPulse] DSN is required");return}this.config={dsn:e.dsn,environment:(t=e.environment)!=null?t:"production",release:(n=e.release)!=null?n:null,enabled:(o=e.enabled)!=null?o:!0,trackVitals:(l=e.trackVitals)!=null?l:!0,tracesSampleRate:(c=e.tracesSampleRate)!=null?c:1},this.transport=new P(this.config.dsn),this.config.enabled&&(this._installHandlers(),this.config.trackVitals&&this._trackWebVitals())}capture(e,t={}){!this.transport||!this.config.enabled||Math.random()>this.config.tracesSampleRate||this.transport.send(f(d(d({},s(e)),t),{user:this.user,environment:this.config.environment,release:this.config.release}))}captureMessage(e,t="info"){!this.transport||!this.config.enabled||Math.random()>this.config.tracesSampleRate||this.transport.send(f(d({},a(e,t)),{user:this.user,environment:this.config.environment,release:this.config.release}))}setUser(e){this.user=e}clearUser(){this.user=null}_installHandlers(){this._installed||(this._installed=!0,window.addEventListener("error",e=>{var t;this.capture((t=e.error)!=null?t:new Error(e.message),{context:{filename:e.filename,line:e.lineno,column:e.colno}})}),window.addEventListener("unhandledrejection",e=>{const t=e.reason instanceof Error?e.reason:new Error(String(e.reason));this.capture(t,{context:{type:"unhandledrejection"}})}))}_trackWebVitals(){if(!("PerformanceObserver"in window))return;this._observe("largest-contentful-paint",t=>{const n=t[t.length-1];this.transport.send(u("LCP",n.startTime))}),this._observe("first-input",t=>{const n=t[0];this.transport.send(u("FID",n.processingStart-n.startTime))});let e=0;this._observe("layout-shift",t=>{t.forEach(n=>{n.hadRecentInput||(e+=n.value)})}),window.addEventListener("pagehide",()=>{e>0&&this.transport.send(u("CLS",e,{unit:""}))}),window.addEventListener("load",()=>{const t=performance.getEntriesByType("navigation")[0];t&&(this.transport.send(u("TTFB",t.responseStart)),this.transport.send(u("PageLoad",t.loadEventEnd)))})}_observe(e,t){try{new PerformanceObserver(o=>{t(o.getEntries())}).observe({type:e,buffered:!0})}catch(n){}}}const p=new T;if(typeof document!="undefined"){const r=document.currentScript;(g=r==null?void 0:r.dataset)!=null&&g.dsn&&p.init({dsn:r.dataset.dsn,environment:(w=r.dataset.env)!=null?w:"production",release:(b=r.dataset.release)!=null?b:null})}i.DevPulse=p,i.default=p,Object.defineProperties(i,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
|
3
|
+
//# sourceMappingURL=devpulse.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devpulse.umd.js","sources":["../src/payload.js","../src/transport.js","../src/index.js"],"sourcesContent":["export function buildFromError(error, options = {}) {\n return {\n level: \"error\",\n exception: {\n type: error.name ?? \"Error\",\n message: error.message ?? String(error),\n stacktrace: parseStack(error.stack),\n },\n context: buildContext(),\n request: buildRequest(),\n user: options.user ?? null,\n platform: \"browser\",\n timestamp: new Date().toISOString(),\n };\n}\n\nexport function buildFromMessage(message, level = \"info\", options = {}) {\n return {\n level,\n message,\n context: buildContext(),\n request: buildRequest(),\n user: options.user ?? null,\n platform: \"browser\",\n timestamp: new Date().toISOString(),\n };\n}\n\n// unit defaults to \"ms\" for timing metrics; pass { unit: \"\" } for unitless\n// scores like CLS which are in the 0–1 range, not milliseconds.\nexport function buildFromPerformance(name, value, options = {}) {\n const unit = options.unit ?? \"ms\";\n const displayValue =\n unit === \"ms\" ? Math.round(value) : +Number(value).toFixed(4);\n return {\n level: \"info\",\n message: `Performance: ${name} = ${displayValue}${unit}`,\n context: {\n ...buildContext(),\n performance: { name, value: displayValue, unit },\n },\n request: buildRequest(),\n platform: \"browser\",\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction parseStack(stack) {\n if (!stack) return [];\n return stack\n .split(\"\\n\")\n .slice(1)\n .map((line) => {\n const trimmed = line.trim();\n\n // \" at functionName (file.js:10:5)\"\n const full = trimmed.match(/at\\s+(.*?)\\s+\\((.*?):(\\d+):(\\d+)\\)/);\n if (full) {\n return {\n function: full[1] ?? null,\n file: full[2] ?? null,\n line: parseInt(full[3]) || null,\n column: parseInt(full[4]) || null,\n };\n }\n\n // \" at file.js:10:5\" (anonymous or arrow functions)\n const short = trimmed.match(/at\\s+(.*?):(\\d+):(\\d+)/);\n if (short) {\n return {\n function: null,\n file: short[1] ?? null,\n line: parseInt(short[2]) || null,\n column: parseInt(short[3]) || null,\n };\n }\n\n return { raw: trimmed };\n })\n .filter((f) => f.file || f.raw);\n}\n\nfunction buildContext() {\n return {\n url: window.location.href,\n userAgent: navigator.userAgent,\n language: navigator.language,\n viewport: {\n width: window.innerWidth,\n height: window.innerHeight,\n },\n screen: {\n width: window.screen.width,\n height: window.screen.height,\n },\n };\n}\n\nfunction buildRequest() {\n return {\n url: window.location.href,\n };\n}\n","export class Transport {\n constructor(dsn, options = {}) {\n this.dsn = dsn;\n this.timeout = options.timeout ?? 5000;\n }\n\n send(payload) {\n const body = JSON.stringify(payload);\n\n // Use fetch with keepalive (survives page navigation) and credentials omitted\n // — the ingest endpoint authenticates via the API key in the URL, not cookies.\n // sendBeacon is avoided because it always sends credentials, which breaks\n // CORS preflight when the server responds with a specific origin.\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n fetch(this.dsn, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n keepalive: true,\n credentials: \"omit\",\n signal: controller.signal,\n })\n .catch(() => {})\n .finally(() => clearTimeout(timer));\n\n return true;\n }\n}\n","import {\n buildFromError,\n buildFromMessage,\n buildFromPerformance,\n} from \"./payload.js\";\nimport { Transport } from \"./transport.js\";\n\nclass DevPulseClient {\n constructor() {\n this.transport = null;\n this.config = {};\n this.user = null;\n this._installed = false;\n }\n\n init(config = {}) {\n if (!config.dsn) {\n console.warn(\"[DevPulse] DSN is required\");\n return;\n }\n\n this.config = {\n dsn: config.dsn,\n environment: config.environment ?? \"production\",\n release: config.release ?? null,\n enabled: config.enabled ?? true,\n trackVitals: config.trackVitals ?? true,\n tracesSampleRate: config.tracesSampleRate ?? 1.0,\n };\n\n this.transport = new Transport(this.config.dsn);\n\n if (!this.config.enabled) return;\n\n this._installHandlers();\n if (this.config.trackVitals) this._trackWebVitals();\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n capture(error, extra = {}) {\n if (!this.transport || !this.config.enabled) return;\n if (Math.random() > this.config.tracesSampleRate) return;\n this.transport.send({\n ...buildFromError(error),\n ...extra,\n // Core identity fields always win over anything in extra\n user: this.user,\n environment: this.config.environment,\n release: this.config.release,\n });\n }\n\n captureMessage(message, level = \"info\") {\n if (!this.transport || !this.config.enabled) return;\n if (Math.random() > this.config.tracesSampleRate) return;\n this.transport.send({\n ...buildFromMessage(message, level),\n user: this.user,\n environment: this.config.environment,\n release: this.config.release,\n });\n }\n\n setUser(user) {\n this.user = user; // { id, email, name }\n }\n\n clearUser() {\n this.user = null;\n }\n\n // ── Error Handlers ────────────────────────────────────────────────────────\n _installHandlers() {\n if (this._installed) return;\n this._installed = true;\n\n // Uncaught JS errors\n window.addEventListener(\"error\", (event) => {\n this.capture(event.error ?? new Error(event.message), {\n context: {\n filename: event.filename,\n line: event.lineno,\n column: event.colno,\n },\n });\n });\n\n // Unhandled Promise rejections\n window.addEventListener(\"unhandledrejection\", (event) => {\n const error =\n event.reason instanceof Error\n ? event.reason\n : new Error(String(event.reason));\n this.capture(error, { context: { type: \"unhandledrejection\" } });\n });\n }\n\n // ── Core Web Vitals ───────────────────────────────────────────────────────\n _trackWebVitals() {\n if (!(\"PerformanceObserver\" in window)) return;\n\n // LCP — Largest Contentful Paint\n this._observe(\"largest-contentful-paint\", (entries) => {\n const lcp = entries[entries.length - 1];\n this.transport.send(buildFromPerformance(\"LCP\", lcp.startTime));\n });\n\n // FID — First Input Delay\n this._observe(\"first-input\", (entries) => {\n const fid = entries[0];\n this.transport.send(\n buildFromPerformance(\"FID\", fid.processingStart - fid.startTime),\n );\n });\n\n // CLS — Cumulative Layout Shift (unitless score 0–1, NOT milliseconds)\n let clsValue = 0;\n this._observe(\"layout-shift\", (entries) => {\n entries.forEach((entry) => {\n if (!entry.hadRecentInput) clsValue += entry.value;\n });\n });\n\n window.addEventListener(\"pagehide\", () => {\n if (clsValue > 0) {\n this.transport.send(buildFromPerformance(\"CLS\", clsValue, { unit: \"\" }));\n }\n });\n\n // TTFB — Time to First Byte\n window.addEventListener(\"load\", () => {\n const nav = performance.getEntriesByType(\"navigation\")[0];\n if (nav) {\n this.transport.send(buildFromPerformance(\"TTFB\", nav.responseStart));\n this.transport.send(buildFromPerformance(\"PageLoad\", nav.loadEventEnd));\n }\n });\n }\n\n _observe(type, callback) {\n try {\n const observer = new PerformanceObserver((list) => {\n callback(list.getEntries());\n });\n observer.observe({ type, buffered: true });\n } catch (e) {\n // Browser doesn't support this metric — silently skip\n }\n }\n}\n\n// Export singleton\nexport const DevPulse = new DevPulseClient();\nexport default DevPulse;\n\n// Auto-init from script tag data attributes\n// <script src=\"devpulse.umd.js\" data-dsn=\"...\" data-env=\"production\"></script>\nif (typeof document !== \"undefined\") {\n const script = document.currentScript;\n if (script?.dataset?.dsn) {\n DevPulse.init({\n dsn: script.dataset.dsn,\n environment: script.dataset.env ?? \"production\",\n release: script.dataset.release ?? null,\n });\n }\n}\n"],"names":["buildFromError","error","options","_a","_b","parseStack","buildContext","buildRequest","_c","buildFromMessage","message","level","buildFromPerformance","name","value","unit","displayValue","__spreadProps","__spreadValues","stack","line","trimmed","full","short","f","Transport","dsn","payload","body","controller","timer","DevPulseClient","config","_d","_e","extra","user","event","entries","lcp","fid","clsValue","entry","nav","type","callback","list","e","DevPulse","script"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sekolahcode/devpulse-browser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DevPulse browser SDK — frontend error and performance tracking",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": ["error-tracking", "monitoring", "browser", "sdk", "web-vitals"],
|
|
7
|
+
"main": "dist/devpulse.umd.js",
|
|
8
|
+
"module": "dist/devpulse.es.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/devpulse.es.js",
|
|
12
|
+
"require": "./dist/devpulse.umd.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": true,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "vite build",
|
|
21
|
+
"dev": "vite build --watch",
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"lint": "eslint src",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"eslint": "^10.0.0",
|
|
28
|
+
"jsdom": "^25.0.0",
|
|
29
|
+
"prettier": "^3.0.0",
|
|
30
|
+
"vite": "^6.0.0",
|
|
31
|
+
"vitest": "^3.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|