@statoss/sdk 0.0.0-stage → 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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -2
  3. package/dist/cjs/client.d.ts +50 -0
  4. package/dist/cjs/client.js +462 -0
  5. package/dist/cjs/dsn.d.ts +25 -0
  6. package/dist/cjs/dsn.js +62 -0
  7. package/dist/cjs/envelope.d.ts +36 -0
  8. package/dist/cjs/envelope.js +55 -0
  9. package/dist/cjs/event.d.ts +9 -0
  10. package/dist/cjs/event.js +128 -0
  11. package/dist/cjs/hub.d.ts +37 -0
  12. package/dist/cjs/hub.js +119 -0
  13. package/dist/cjs/index.d.ts +38 -0
  14. package/dist/cjs/index.js +107 -0
  15. package/dist/cjs/integrations/browser.d.ts +7 -0
  16. package/dist/cjs/integrations/browser.js +124 -0
  17. package/dist/cjs/integrations/console.d.ts +3 -0
  18. package/dist/cjs/integrations/console.js +106 -0
  19. package/dist/cjs/integrations/dedupe.d.ts +7 -0
  20. package/dist/cjs/integrations/dedupe.js +32 -0
  21. package/dist/cjs/integrations/fetch.d.ts +3 -0
  22. package/dist/cjs/integrations/fetch.js +69 -0
  23. package/dist/cjs/integrations/handlers.d.ts +9 -0
  24. package/dist/cjs/integrations/handlers.js +192 -0
  25. package/dist/cjs/levels.d.ts +20 -0
  26. package/dist/cjs/levels.js +77 -0
  27. package/dist/cjs/logger.d.ts +18 -0
  28. package/dist/cjs/logger.js +38 -0
  29. package/dist/cjs/package.json +1 -0
  30. package/dist/cjs/ratelimit.d.ts +11 -0
  31. package/dist/cjs/ratelimit.js +50 -0
  32. package/dist/cjs/runtime.d.ts +42 -0
  33. package/dist/cjs/runtime.js +188 -0
  34. package/dist/cjs/scope.d.ts +30 -0
  35. package/dist/cjs/scope.js +127 -0
  36. package/dist/cjs/stack.d.ts +9 -0
  37. package/dist/cjs/stack.js +129 -0
  38. package/dist/cjs/transport.d.ts +40 -0
  39. package/dist/cjs/transport.js +218 -0
  40. package/dist/cjs/types.d.ts +180 -0
  41. package/dist/cjs/types.js +2 -0
  42. package/dist/cjs/utils.d.ts +27 -0
  43. package/dist/cjs/utils.js +227 -0
  44. package/dist/cjs/version.d.ts +3 -0
  45. package/dist/cjs/version.js +7 -0
  46. package/dist/esm/client.d.ts +50 -0
  47. package/dist/esm/client.js +458 -0
  48. package/dist/esm/dsn.d.ts +25 -0
  49. package/dist/esm/dsn.js +55 -0
  50. package/dist/esm/envelope.d.ts +36 -0
  51. package/dist/esm/envelope.js +49 -0
  52. package/dist/esm/event.d.ts +9 -0
  53. package/dist/esm/event.js +124 -0
  54. package/dist/esm/hub.d.ts +37 -0
  55. package/dist/esm/hub.js +111 -0
  56. package/dist/esm/index.d.ts +38 -0
  57. package/dist/esm/index.js +88 -0
  58. package/dist/esm/integrations/browser.d.ts +7 -0
  59. package/dist/esm/integrations/browser.js +119 -0
  60. package/dist/esm/integrations/console.d.ts +3 -0
  61. package/dist/esm/integrations/console.js +103 -0
  62. package/dist/esm/integrations/dedupe.d.ts +7 -0
  63. package/dist/esm/integrations/dedupe.js +29 -0
  64. package/dist/esm/integrations/fetch.d.ts +3 -0
  65. package/dist/esm/integrations/fetch.js +66 -0
  66. package/dist/esm/integrations/handlers.d.ts +9 -0
  67. package/dist/esm/integrations/handlers.js +188 -0
  68. package/dist/esm/levels.d.ts +20 -0
  69. package/dist/esm/levels.js +69 -0
  70. package/dist/esm/logger.d.ts +18 -0
  71. package/dist/esm/logger.js +33 -0
  72. package/dist/esm/ratelimit.d.ts +11 -0
  73. package/dist/esm/ratelimit.js +45 -0
  74. package/dist/esm/runtime.d.ts +42 -0
  75. package/dist/esm/runtime.js +181 -0
  76. package/dist/esm/scope.d.ts +30 -0
  77. package/dist/esm/scope.js +123 -0
  78. package/dist/esm/stack.d.ts +9 -0
  79. package/dist/esm/stack.js +125 -0
  80. package/dist/esm/transport.d.ts +40 -0
  81. package/dist/esm/transport.js +213 -0
  82. package/dist/esm/types.d.ts +180 -0
  83. package/dist/esm/types.js +1 -0
  84. package/dist/esm/utils.d.ts +27 -0
  85. package/dist/esm/utils.js +211 -0
  86. package/dist/esm/version.d.ts +3 -0
  87. package/dist/esm/version.js +4 -0
  88. package/package.json +48 -4
@@ -0,0 +1,462 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Client = void 0;
4
+ const dsn_js_1 = require("./dsn.js");
5
+ const envelope_js_1 = require("./envelope.js");
6
+ const event_js_1 = require("./event.js");
7
+ const hub_js_1 = require("./hub.js");
8
+ const browser_js_1 = require("./integrations/browser.js");
9
+ const console_js_1 = require("./integrations/console.js");
10
+ const dedupe_js_1 = require("./integrations/dedupe.js");
11
+ const fetch_js_1 = require("./integrations/fetch.js");
12
+ const handlers_js_1 = require("./integrations/handlers.js");
13
+ const levels_js_1 = require("./levels.js");
14
+ const logger_js_1 = require("./logger.js");
15
+ const runtime_js_1 = require("./runtime.js");
16
+ const scope_js_1 = require("./scope.js");
17
+ const transport_js_1 = require("./transport.js");
18
+ const utils_js_1 = require("./utils.js");
19
+ const version_js_1 = require("./version.js");
20
+ const REPORT_INTERVAL_MS = 30000;
21
+ const DEFAULT_FLUSH_MS = 2000;
22
+ // The server drops event items over 1 MiB.
23
+ const MAX_EVENT_CHARS = 1000000;
24
+ const MAX_TAG_VALUE = 200;
25
+ const KNOWN_KEYS = new Set([
26
+ "event_id",
27
+ "timestamp",
28
+ "platform",
29
+ "level",
30
+ "logger",
31
+ "transaction",
32
+ "server_name",
33
+ "environment",
34
+ "release",
35
+ "dist",
36
+ "message",
37
+ "exception",
38
+ "breadcrumbs",
39
+ "tags",
40
+ "extra",
41
+ "contexts",
42
+ "user",
43
+ "request",
44
+ "fingerprint",
45
+ "sdk",
46
+ ]);
47
+ function cleanTags(tags) {
48
+ const out = {};
49
+ for (const [key, value] of Object.entries(tags)) {
50
+ if (value === undefined || value === null)
51
+ continue;
52
+ out[key] = (0, utils_js_1.truncate)(typeof value === "string" ? value : (0, utils_js_1.safeString)(value), MAX_TAG_VALUE);
53
+ }
54
+ return out;
55
+ }
56
+ function cleanContexts(contexts) {
57
+ const out = {};
58
+ for (const [name, value] of Object.entries(contexts)) {
59
+ if ((0, utils_js_1.isObject)(value))
60
+ out[name] = (0, utils_js_1.normalize)(value, 5);
61
+ }
62
+ return out;
63
+ }
64
+ function pageRequest() {
65
+ const g = globalThis;
66
+ const href = (0, utils_js_1.safely)(() => g.location?.href);
67
+ const ua = (0, utils_js_1.safely)(() => g.navigator?.userAgent);
68
+ const request = {};
69
+ if (typeof href === "string")
70
+ request.url = (0, utils_js_1.scrubUrl)(href);
71
+ if (typeof ua === "string")
72
+ request.headers = { "User-Agent": ua };
73
+ return Object.keys(request).length ? request : undefined;
74
+ }
75
+ function crumbsOf(value) {
76
+ if (Array.isArray(value))
77
+ return value;
78
+ if ((0, utils_js_1.isObject)(value) && Array.isArray(value.values))
79
+ return value.values;
80
+ return [];
81
+ }
82
+ function withTimeout(promise, ms) {
83
+ return new Promise((resolve) => {
84
+ const timer = setTimeout(() => resolve(false), ms);
85
+ (0, utils_js_1.unref)(timer);
86
+ promise.then(() => {
87
+ clearTimeout(timer);
88
+ resolve(true);
89
+ }, () => {
90
+ clearTimeout(timer);
91
+ resolve(true);
92
+ });
93
+ });
94
+ }
95
+ class Client {
96
+ constructor(options) {
97
+ this.integrations = [];
98
+ this.discarded = new Map();
99
+ this.pending = new Set();
100
+ this.teardowns = [];
101
+ this.closed = false;
102
+ this.options = options;
103
+ this.logger = (0, logger_js_1.makeLogger)(options.debug === true);
104
+ this.runtime = (0, runtime_js_1.describeRuntime)();
105
+ this.levels = (0, levels_js_1.readLevels)(options.levels, this.logger);
106
+ const rate = options.sampleRate;
107
+ if (rate !== undefined &&
108
+ !(typeof rate === "number" && rate >= 0 && rate <= 1)) {
109
+ this.logger.warn("Ignored sampleRate: it should be a number from 0 to 1.");
110
+ }
111
+ this.sampleRate =
112
+ typeof rate === "number" && rate >= 0 && rate <= 1 ? rate : 1;
113
+ const max = options.maxBreadcrumbs;
114
+ this.maxBreadcrumbs =
115
+ typeof max === "number" && max >= 0
116
+ ? Math.min(Math.floor(max), scope_js_1.MAX_BREADCRUMBS)
117
+ : scope_js_1.MAX_BREADCRUMBS;
118
+ let dsn;
119
+ if (options.enabled === false) {
120
+ this.logger.log("Not started: enabled is false.");
121
+ }
122
+ else if (options.dsn === undefined || options.dsn === "") {
123
+ this.logger.warn("Not started: no DSN given.");
124
+ }
125
+ else {
126
+ const parsed = (0, dsn_js_1.parseDsn)(options.dsn);
127
+ if (parsed.error !== undefined)
128
+ this.logger.warn(`Not started: ${parsed.error}`);
129
+ dsn = parsed.dsn;
130
+ }
131
+ this.dsn = dsn;
132
+ this.enabled = dsn !== undefined;
133
+ if (!dsn)
134
+ return;
135
+ this.endpoint = (0, dsn_js_1.envelopeEndpoint)(dsn);
136
+ const browserLike = this.runtime.browserLike;
137
+ this.sender = new transport_js_1.Sender({
138
+ url: browserLike ? (0, dsn_js_1.envelopeUrlWithQueryAuth)(dsn) : this.endpoint,
139
+ headers: browserLike
140
+ ? { "Content-Type": "text/plain;charset=UTF-8" }
141
+ : {
142
+ "Content-Type": "application/x-sentry-envelope",
143
+ "X-Sentry-Auth": (0, dsn_js_1.authHeader)(dsn),
144
+ },
145
+ browserLike,
146
+ transport: options.transport ?? (0, transport_js_1.fetchTransport)(browserLike),
147
+ recordLoss: (reason, category, quantity) => this.recordLoss(reason, category, quantity),
148
+ logger: this.logger,
149
+ });
150
+ this.logger.log(`Started for app ${dsn.projectId} on ${dsn.host} (${this.runtime.name}, ${version_js_1.SDK_NAME} ${version_js_1.SDK_VERSION}).`);
151
+ }
152
+ /** Installs the integrations that apply to this runtime. */
153
+ setup() {
154
+ if (!this.enabled)
155
+ return;
156
+ const toggles = {
157
+ globalHandlers: true,
158
+ console: true,
159
+ fetch: true,
160
+ navigation: true,
161
+ clicks: true,
162
+ dedupe: true,
163
+ ...this.options.integrations,
164
+ };
165
+ const add = (name, on, install) => {
166
+ if (!on)
167
+ return;
168
+ const teardown = (0, utils_js_1.safely)(install);
169
+ if (!teardown)
170
+ return;
171
+ this.integrations.push(name);
172
+ this.teardowns.push(teardown);
173
+ };
174
+ const browser = this.runtime.name === "browser";
175
+ add("GlobalHandlers", toggles.globalHandlers, () => (0, handlers_js_1.installGlobalHandlers)(this));
176
+ add("Console", toggles.console, () => (0, console_js_1.installConsole)(this));
177
+ add("Fetch", toggles.fetch, () => (0, fetch_js_1.installFetch)(this));
178
+ add("Navigation", browser && toggles.navigation, () => (0, browser_js_1.installNavigation)(this));
179
+ add("Clicks", browser && toggles.clicks, () => (0, browser_js_1.installClicks)(this));
180
+ if (toggles.dedupe)
181
+ this.integrations.push("Dedupe");
182
+ if (browser)
183
+ this.flushReportsWhenHidden();
184
+ this.logger.log(`Integrations: ${this.integrations.join(", ") || "none"}.`);
185
+ }
186
+ captureException(error, context, scope) {
187
+ return this.capture(() => (0, event_js_1.eventFromUnknown)(error, { type: "generic", handled: true }), context, scope, { originalException: error });
188
+ }
189
+ captureMessage(message, context, scope) {
190
+ return this.capture(() => ({ message: (0, utils_js_1.truncate)((0, utils_js_1.safeString)(message)), level: "info" }), context, scope, {});
191
+ }
192
+ captureEvent(event, context, scope) {
193
+ const id = (0, utils_js_1.isObject)(event) && typeof event.event_id === "string"
194
+ ? event.event_id
195
+ : "";
196
+ return this.capture(() => ({ ...event }), context, scope, {
197
+ eventId: /^[0-9a-f]{32}$/.test(id) ? id : undefined,
198
+ });
199
+ }
200
+ /** @internal Used by the global handlers. */
201
+ captureUnhandled(input, mechanism, level, options = {}) {
202
+ return this.capture(() => {
203
+ const event = (0, event_js_1.eventFromUnknown)(input, mechanism, options.rejection);
204
+ return options.frame ? (0, event_js_1.addInitialFrame)(event, options.frame) : event;
205
+ }, undefined, (0, hub_js_1.getCurrentScope)(), { originalException: input, level });
206
+ }
207
+ /** Applies beforeBreadcrumb and keeps the breadcrumb on the current scope. */
208
+ addBreadcrumb(breadcrumb, hint) {
209
+ if (!this.enabled ||
210
+ this.closed ||
211
+ this.maxBreadcrumbs === 0 ||
212
+ !(0, utils_js_1.isObject)(breadcrumb))
213
+ return;
214
+ try {
215
+ let crumb = { timestamp: (0, utils_js_1.now)(), ...breadcrumb };
216
+ if (crumb.message !== undefined)
217
+ crumb.message = (0, utils_js_1.truncate)((0, utils_js_1.safeString)(crumb.message), 1024);
218
+ if (crumb.data !== undefined) {
219
+ crumb.data = (0, utils_js_1.normalize)(crumb.data, 3, 1024);
220
+ }
221
+ if (this.options.beforeBreadcrumb)
222
+ crumb = this.options.beforeBreadcrumb(crumb, hint);
223
+ if (!crumb)
224
+ return;
225
+ const scope = (0, hub_js_1.getCurrentScope)();
226
+ scope._maxBreadcrumbs = this.maxBreadcrumbs;
227
+ scope.addBreadcrumb(crumb);
228
+ }
229
+ catch (error) {
230
+ this.logger.error("Could not add a breadcrumb:", error);
231
+ }
232
+ }
233
+ /** True for a URL this client sends envelopes to, so the fetch breadcrumbs skip it. */
234
+ isOwnUrl(url) {
235
+ return this.endpoint !== undefined && url.startsWith(this.endpoint);
236
+ }
237
+ /** Counts something not sent, for the next client report. */
238
+ recordLoss(reason, category, quantity = 1) {
239
+ const key = `${reason}:${category}`;
240
+ const entry = this.discarded.get(key);
241
+ if (entry)
242
+ entry.quantity += quantity;
243
+ else
244
+ this.discarded.set(key, { reason, category, quantity });
245
+ if (this.reportTimer === undefined && this.sender) {
246
+ this.reportTimer = (0, utils_js_1.safely)(() => setTimeout(() => {
247
+ this.reportTimer = undefined;
248
+ this.sendClientReport();
249
+ }, REPORT_INTERVAL_MS));
250
+ (0, utils_js_1.unref)(this.reportTimer);
251
+ }
252
+ }
253
+ sendClientReport() {
254
+ if (this.reportTimer !== undefined) {
255
+ clearTimeout(this.reportTimer);
256
+ this.reportTimer = undefined;
257
+ }
258
+ if (!this.discarded.size || !this.sender || !this.dsn)
259
+ return;
260
+ const discarded = [...this.discarded.values()];
261
+ this.discarded.clear();
262
+ this.sender.send((0, envelope_js_1.clientReportEnvelope)(discarded, this.dsn));
263
+ }
264
+ /** Waits for captured events to be sent; false if timeoutMs passed first. */
265
+ async flush(timeoutMs = DEFAULT_FLUSH_MS) {
266
+ if (!this.sender)
267
+ return true;
268
+ const deadline = Date.now() + timeoutMs;
269
+ const left = () => Math.max(0, deadline - Date.now());
270
+ const processed = this.pending.size
271
+ ? await withTimeout(Promise.all([...this.pending]), timeoutMs)
272
+ : true;
273
+ this.sendClientReport();
274
+ let sent = await this.sender.flush(left());
275
+ // Report what this flush itself lost, such as events that hit a network error.
276
+ if (this.discarded.size) {
277
+ this.sendClientReport();
278
+ sent = (await this.sender.flush(left())) && sent;
279
+ }
280
+ return processed && sent;
281
+ }
282
+ /** Flushes, then removes the integrations. Nothing is captured afterwards. */
283
+ async close(timeoutMs = DEFAULT_FLUSH_MS) {
284
+ this.closed = true;
285
+ this.teardown();
286
+ return this.flush(timeoutMs);
287
+ }
288
+ /** Removes the integrations at once, without waiting. */
289
+ teardown() {
290
+ for (const teardown of this.teardowns.splice(0))
291
+ (0, utils_js_1.safely)(teardown);
292
+ }
293
+ flushReportsWhenHidden() {
294
+ const doc = globalThis.document;
295
+ if (!doc || typeof doc.addEventListener !== "function")
296
+ return;
297
+ const onChange = () => {
298
+ if (doc.visibilityState === "hidden")
299
+ (0, utils_js_1.safely)(() => this.sendClientReport());
300
+ };
301
+ doc.addEventListener("visibilitychange", onChange);
302
+ this.teardowns.push(() => doc.removeEventListener?.("visibilitychange", onChange));
303
+ }
304
+ capture(build, context, scope, hint) {
305
+ const eventId = hint.eventId ?? (0, utils_js_1.uuid4)();
306
+ if (!this.enabled || this.closed)
307
+ return eventId;
308
+ try {
309
+ const event = this.prepare(build(), context, scope, eventId, hint.level);
310
+ const task = this.process(event, {
311
+ event_id: eventId,
312
+ originalException: hint.originalException,
313
+ }).catch((error) => {
314
+ this.logger.error("Could not send an event:", error);
315
+ });
316
+ this.pending.add(task);
317
+ void task.then(() => this.pending.delete(task));
318
+ }
319
+ catch (error) {
320
+ this.logger.error("Could not capture an event:", error);
321
+ }
322
+ return eventId;
323
+ }
324
+ /** Merges scope, context and options into the event, in Sentry's field order. */
325
+ prepare(base, context, scope, eventId, forcedLevel) {
326
+ const ctx = (0, utils_js_1.isObject)(context) ? context : {};
327
+ const choice = (0, levels_js_1.chooseLevel)([ctx.level, forcedLevel, scope._level, base.level], this.levels, "error", this.logger);
328
+ const tags = {
329
+ ...scope._tags,
330
+ ...ctx.tags,
331
+ ...base.tags,
332
+ };
333
+ if (choice.custom)
334
+ tags[levels_js_1.LEVEL_TAG] = choice.custom;
335
+ const category = (0, levels_js_1.readCategory)(ctx.category, this.logger);
336
+ if (category)
337
+ tags[levels_js_1.CATEGORY_TAG] = category;
338
+ const user = {
339
+ ...scope._user,
340
+ ...ctx.user,
341
+ ...base.user,
342
+ };
343
+ if (this.options.sendDefaultPii &&
344
+ this.runtime.browserLike &&
345
+ user.ip_address === undefined) {
346
+ user.ip_address = "{{auto}}";
347
+ }
348
+ const request = this.runtime.browserLike
349
+ ? { ...pageRequest(), ...base.request }
350
+ : base.request;
351
+ const fingerprint = base.fingerprint ?? ctx.fingerprint ?? scope._fingerprint;
352
+ const allCrumbs = [...scope._breadcrumbs, ...crumbsOf(base.breadcrumbs)];
353
+ const breadcrumbs = allCrumbs.slice(Math.max(0, allCrumbs.length - this.maxBreadcrumbs));
354
+ const event = {
355
+ event_id: eventId,
356
+ timestamp: typeof base.timestamp === "number" ? base.timestamp : (0, utils_js_1.now)(),
357
+ platform: base.platform ?? this.runtime.platform,
358
+ level: choice.level,
359
+ };
360
+ const set = (key, value) => {
361
+ if (value === undefined)
362
+ return;
363
+ if ((0, utils_js_1.isObject)(value) &&
364
+ !Array.isArray(value) &&
365
+ Object.keys(value).length === 0)
366
+ return;
367
+ event[key] = value;
368
+ };
369
+ set("logger", base.logger);
370
+ set("transaction", base.transaction);
371
+ set("server_name", base.server_name ?? this.options.serverName ?? this.runtime.serverName);
372
+ set("environment", base.environment ?? this.options.environment ?? "production");
373
+ set("release", base.release ?? this.options.release);
374
+ set("dist", base.dist ?? this.options.dist);
375
+ set("message", base.message);
376
+ set("exception", base.exception);
377
+ set("breadcrumbs", breadcrumbs.length ? { values: breadcrumbs } : undefined);
378
+ set("tags", cleanTags(tags));
379
+ set("extra", (0, utils_js_1.normalize)({ ...scope._extra, ...ctx.extra, ...base.extra }, 6));
380
+ set("contexts", cleanContexts({
381
+ ...this.runtime.contexts,
382
+ ...scope._contexts,
383
+ ...ctx.contexts,
384
+ ...base.contexts,
385
+ }));
386
+ set("user", (0, utils_js_1.normalize)(user, 3));
387
+ set("request", request);
388
+ set("fingerprint", Array.isArray(fingerprint)
389
+ ? fingerprint.map((f) => (0, utils_js_1.safeString)(f))
390
+ : undefined);
391
+ set("sdk", {
392
+ name: version_js_1.SDK_NAME,
393
+ version: version_js_1.SDK_VERSION,
394
+ integrations: [...this.integrations],
395
+ packages: [{ name: version_js_1.SDK_PACKAGE, version: version_js_1.SDK_VERSION }],
396
+ });
397
+ for (const key of Object.keys(base)) {
398
+ if (!KNOWN_KEYS.has(key) && base[key] !== undefined)
399
+ event[key] = base[key];
400
+ }
401
+ return event;
402
+ }
403
+ async process(event, hint) {
404
+ if (this.sampleRate < 1 && Math.random() >= this.sampleRate) {
405
+ this.logger.log("Dropped an event by sampleRate.");
406
+ this.recordLoss("sample_rate", "error");
407
+ return;
408
+ }
409
+ if (this.integrations.includes("Dedupe")) {
410
+ const signature = (0, dedupe_js_1.eventSignature)(event);
411
+ if (signature !== undefined && signature === this.previousSignature) {
412
+ this.logger.log("Dropped an event identical to the one before it.");
413
+ this.recordLoss("event_processor", "error");
414
+ return;
415
+ }
416
+ this.previousSignature = signature;
417
+ }
418
+ let final = event;
419
+ if (this.options.beforeSend) {
420
+ try {
421
+ const result = this.options.beforeSend(event, hint);
422
+ final = (0, utils_js_1.isThenable)(result) ? await result : result;
423
+ }
424
+ catch (error) {
425
+ this.logger.error("beforeSend threw, so the event was dropped:", error);
426
+ this.recordLoss("internal_sdk_error", "error");
427
+ return;
428
+ }
429
+ if (!(0, utils_js_1.isObject)(final)) {
430
+ this.logger.log("beforeSend dropped an event.");
431
+ this.recordLoss("before_send", "error");
432
+ return;
433
+ }
434
+ }
435
+ this.send(this.finish(final));
436
+ }
437
+ /** Makes what beforeSend handed back valid again: a Sentry level, string tags, a sane size. */
438
+ finish(event) {
439
+ if (!(0, levels_js_1.isSentryLevel)(event.level)) {
440
+ const choice = (0, levels_js_1.chooseLevel)([event.level], this.levels, "error", this.logger);
441
+ event.level = choice.level;
442
+ if (choice.custom)
443
+ event.tags = { ...event.tags, [levels_js_1.LEVEL_TAG]: choice.custom };
444
+ }
445
+ if ((0, utils_js_1.isObject)(event.tags))
446
+ event.tags = cleanTags(event.tags);
447
+ const size = () => (0, utils_js_1.safely)(() => JSON.stringify(event).length, 0) ?? 0;
448
+ if (size() > MAX_EVENT_CHARS) {
449
+ this.logger.warn("The event is over 1 MB; sending it without breadcrumbs and extra.");
450
+ delete event.breadcrumbs;
451
+ if (size() > MAX_EVENT_CHARS)
452
+ delete event.extra;
453
+ }
454
+ return event;
455
+ }
456
+ send(event) {
457
+ if (!this.sender || !this.dsn)
458
+ return;
459
+ this.sender.send((0, envelope_js_1.eventEnvelope)(event, this.dsn));
460
+ }
461
+ }
462
+ exports.Client = Client;
@@ -0,0 +1,25 @@
1
+ export interface Dsn {
2
+ protocol: "http" | "https";
3
+ publicKey: string;
4
+ host: string;
5
+ port: string;
6
+ /** Anything between the host and the app number, without slashes at the ends. */
7
+ path: string;
8
+ /** The app number (Sentry's project id). */
9
+ projectId: string;
10
+ }
11
+ export type DsnResult = {
12
+ dsn: Dsn;
13
+ error?: undefined;
14
+ } | {
15
+ dsn?: undefined;
16
+ error: string;
17
+ };
18
+ /** Parses a DSN; a bad one comes back as a reason, never as an exception. */
19
+ export declare function parseDsn(input: unknown): DsnResult;
20
+ /** The DSN without any secret part, as Sentry writes it in envelope headers. */
21
+ export declare function dsnToString(dsn: Dsn): string;
22
+ export declare function envelopeEndpoint(dsn: Dsn): string;
23
+ /** The endpoint with the key in the query, so browsers send without a CORS preflight. */
24
+ export declare function envelopeUrlWithQueryAuth(dsn: Dsn): string;
25
+ export declare function authHeader(dsn: Dsn): string;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseDsn = parseDsn;
4
+ exports.dsnToString = dsnToString;
5
+ exports.envelopeEndpoint = envelopeEndpoint;
6
+ exports.envelopeUrlWithQueryAuth = envelopeUrlWithQueryAuth;
7
+ exports.authHeader = authHeader;
8
+ const version_js_1 = require("./version.js");
9
+ const DSN_RE = /^(https?):\/\/([^:@/?#\s]+)(?::[^@/?#\s]*)?@(\[[0-9a-fA-F:.]+\]|[^:/?#@\s[\]]+)(?::(\d{1,5}))?(\/[^?#\s]*)?$/;
10
+ /** Parses a DSN; a bad one comes back as a reason, never as an exception. */
11
+ function parseDsn(input) {
12
+ if (typeof input !== "string" || input.trim() === "") {
13
+ return { error: "The DSN is empty." };
14
+ }
15
+ const text = input.trim();
16
+ if (!/^https?:\/\//.test(text)) {
17
+ return { error: "The DSN must start with https:// (or http://)." };
18
+ }
19
+ const m = DSN_RE.exec(text);
20
+ if (!m) {
21
+ return {
22
+ error: "The DSN should look like https://<key>@ingest.statoss.com/<app number>.",
23
+ };
24
+ }
25
+ const segments = (m[5] ?? "").split("/").filter(Boolean);
26
+ const projectId = segments.pop();
27
+ if (!projectId) {
28
+ return { error: "The DSN has no app number after the host." };
29
+ }
30
+ if (!/^\d+$/.test(projectId)) {
31
+ return {
32
+ error: `The DSN's app number must be digits, not "${projectId}".`,
33
+ };
34
+ }
35
+ return {
36
+ dsn: {
37
+ protocol: m[1],
38
+ publicKey: m[2],
39
+ host: m[3],
40
+ port: m[4] ?? "",
41
+ path: segments.join("/"),
42
+ projectId,
43
+ },
44
+ };
45
+ }
46
+ function origin(dsn) {
47
+ return `${dsn.protocol}://${dsn.host}${dsn.port ? `:${dsn.port}` : ""}`;
48
+ }
49
+ /** The DSN without any secret part, as Sentry writes it in envelope headers. */
50
+ function dsnToString(dsn) {
51
+ return `${dsn.protocol}://${dsn.publicKey}@${dsn.host}${dsn.port ? `:${dsn.port}` : ""}/${dsn.path ? `${dsn.path}/` : ""}${dsn.projectId}`;
52
+ }
53
+ function envelopeEndpoint(dsn) {
54
+ return `${origin(dsn)}${dsn.path ? `/${dsn.path}` : ""}/api/${dsn.projectId}/envelope/`;
55
+ }
56
+ /** The endpoint with the key in the query, so browsers send without a CORS preflight. */
57
+ function envelopeUrlWithQueryAuth(dsn) {
58
+ return `${envelopeEndpoint(dsn)}?sentry_version=7&sentry_key=${encodeURIComponent(dsn.publicKey)}&sentry_client=${encodeURIComponent(`${version_js_1.SDK_NAME}/${version_js_1.SDK_VERSION}`)}`;
59
+ }
60
+ function authHeader(dsn) {
61
+ return `Sentry sentry_version=7, sentry_key=${dsn.publicKey}, sentry_client=${version_js_1.SDK_NAME}/${version_js_1.SDK_VERSION}`;
62
+ }
@@ -0,0 +1,36 @@
1
+ import { type Dsn } from "./dsn.js";
2
+ import type { Event } from "./types.js";
3
+ export interface DiscardedEvent {
4
+ reason: string;
5
+ category: string;
6
+ quantity: number;
7
+ }
8
+ export interface EnvelopeHeader {
9
+ event_id?: string;
10
+ sent_at: string;
11
+ dsn: string;
12
+ sdk: {
13
+ name: string;
14
+ version: string;
15
+ };
16
+ }
17
+ export type EventItem = [{
18
+ type: "event";
19
+ }, Event];
20
+ export type ClientReportItem = [
21
+ {
22
+ type: "client_report";
23
+ },
24
+ {
25
+ timestamp: number;
26
+ discarded_events: DiscardedEvent[];
27
+ }
28
+ ];
29
+ export type EnvelopeItem = EventItem | ClientReportItem;
30
+ export type Envelope = [EnvelopeHeader, EnvelopeItem[]];
31
+ export declare function eventEnvelope(event: Event, dsn: Dsn): Envelope;
32
+ export declare function clientReportEnvelope(discarded: DiscardedEvent[], dsn: Dsn, timestamp?: number): Envelope;
33
+ /** Sentry's rate-limit category for an envelope item. */
34
+ export declare function itemCategory(item: EnvelopeItem): string;
35
+ /** Byte for byte what Sentry's serializeEnvelope produces for string payloads. */
36
+ export declare function serializeEnvelope(envelope: Envelope): string;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.eventEnvelope = eventEnvelope;
4
+ exports.clientReportEnvelope = clientReportEnvelope;
5
+ exports.itemCategory = itemCategory;
6
+ exports.serializeEnvelope = serializeEnvelope;
7
+ const dsn_js_1 = require("./dsn.js");
8
+ const utils_js_1 = require("./utils.js");
9
+ const version_js_1 = require("./version.js");
10
+ function sdkHeader() {
11
+ return { name: version_js_1.SDK_NAME, version: version_js_1.SDK_VERSION };
12
+ }
13
+ function eventEnvelope(event, dsn) {
14
+ return [
15
+ {
16
+ event_id: event.event_id,
17
+ sent_at: new Date().toISOString(),
18
+ dsn: (0, dsn_js_1.dsnToString)(dsn),
19
+ sdk: sdkHeader(),
20
+ },
21
+ [[{ type: "event" }, event]],
22
+ ];
23
+ }
24
+ function clientReportEnvelope(discarded, dsn, timestamp = Date.now() / 1000) {
25
+ return [
26
+ {
27
+ sent_at: new Date().toISOString(),
28
+ dsn: (0, dsn_js_1.dsnToString)(dsn),
29
+ sdk: sdkHeader(),
30
+ },
31
+ [[{ type: "client_report" }, { timestamp, discarded_events: discarded }]],
32
+ ];
33
+ }
34
+ /** Sentry's rate-limit category for an envelope item. */
35
+ function itemCategory(item) {
36
+ return item[0].type === "event" ? "error" : "internal";
37
+ }
38
+ function stringify(payload) {
39
+ try {
40
+ return JSON.stringify(payload);
41
+ }
42
+ catch {
43
+ // beforeSend may have put something JSON cannot hold back in.
44
+ return JSON.stringify((0, utils_js_1.normalize)(payload, 10));
45
+ }
46
+ }
47
+ /** Byte for byte what Sentry's serializeEnvelope produces for string payloads. */
48
+ function serializeEnvelope(envelope) {
49
+ const [header, items] = envelope;
50
+ let out = JSON.stringify(header);
51
+ for (const [itemHeader, payload] of items) {
52
+ out += `\n${JSON.stringify(itemHeader)}\n${stringify(payload)}`;
53
+ }
54
+ return out;
55
+ }
@@ -0,0 +1,9 @@
1
+ import type { Event, Mechanism, StackFrame } from "./types.js";
2
+ /**
3
+ * Builds an event from anything thrown or rejected, as Sentry's SDKs do:
4
+ * errors with their stacks and causes, other objects and plain values as
5
+ * synthetic exceptions.
6
+ */
7
+ export declare function eventFromUnknown(input: unknown, mechanism: Mechanism, rejection?: boolean): Event;
8
+ /** For a browser error event that carried no Error: the file and line it names. */
9
+ export declare function addInitialFrame(event: Event, frame: StackFrame): Event;