@kubit-ai/sentry 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/LICENSE +17 -0
- package/README.md +132 -0
- package/dist/browser/kubit-sentry.global.js +505 -0
- package/dist/browser.d.ts +60 -0
- package/dist/browser.js +121 -0
- package/dist/browser.js.map +1 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +34 -0
- package/dist/config.js.map +1 -0
- package/dist/exporter.d.ts +35 -0
- package/dist/exporter.js +94 -0
- package/dist/exporter.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +41 -0
- package/dist/integration.js +97 -0
- package/dist/integration.js.map +1 -0
- package/dist/sentryToOtel.d.ts +23 -0
- package/dist/sentryToOtel.js +316 -0
- package/dist/sentryToOtel.js.map +1 -0
- package/dist/session.d.ts +52 -0
- package/dist/session.js +125 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sentry `Event` -> OTLP/HTTP+JSON (`ExportTraceServiceRequest`).
|
|
4
|
+
*
|
|
5
|
+
* Two entry points behind one dispatcher (`sentryEventToOtlp`):
|
|
6
|
+
* - transactions (`event.type === 'transaction'`) -> spans (root from
|
|
7
|
+
* `contexts.trace`, one child per `event.spans[]`).
|
|
8
|
+
* - errors (everything else) -> a single span carrying an `exception` span
|
|
9
|
+
* event per `exception.values[]`, with `status.code = ERROR`.
|
|
10
|
+
*
|
|
11
|
+
* Pure functions — no Sentry SDK calls, no I/O, no randomness. Error events
|
|
12
|
+
* without a trace context are anchored on the Sentry `event_id` (a 32-hex
|
|
13
|
+
* string, valid as an OTLP trace id) so the transform stays deterministic.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.sentryEventToOtlp = exports.sentryErrorToOtlp = exports.sentryTransactionToOtlp = void 0;
|
|
17
|
+
const SCOPE_NAME = "kubit.sentry";
|
|
18
|
+
const SCOPE_VERSION = "0.1.0";
|
|
19
|
+
const SPAN_KIND_INTERNAL = 1;
|
|
20
|
+
// OTel Status enum
|
|
21
|
+
const STATUS_UNSET = 0;
|
|
22
|
+
const STATUS_OK = 1;
|
|
23
|
+
const STATUS_ERROR = 2;
|
|
24
|
+
const STATUS_ERROR_VALUES = new Set([
|
|
25
|
+
"cancelled",
|
|
26
|
+
"unknown",
|
|
27
|
+
"unknown_error",
|
|
28
|
+
"invalid_argument",
|
|
29
|
+
"deadline_exceeded",
|
|
30
|
+
"not_found",
|
|
31
|
+
"already_exists",
|
|
32
|
+
"permission_denied",
|
|
33
|
+
"resource_exhausted",
|
|
34
|
+
"failed_precondition",
|
|
35
|
+
"aborted",
|
|
36
|
+
"out_of_range",
|
|
37
|
+
"unimplemented",
|
|
38
|
+
"internal_error",
|
|
39
|
+
"unavailable",
|
|
40
|
+
"data_loss",
|
|
41
|
+
"unauthenticated",
|
|
42
|
+
]);
|
|
43
|
+
const mapStatus = (status) => {
|
|
44
|
+
if (status === "ok") {
|
|
45
|
+
return STATUS_OK;
|
|
46
|
+
}
|
|
47
|
+
if (status !== undefined && STATUS_ERROR_VALUES.has(status)) {
|
|
48
|
+
return STATUS_ERROR;
|
|
49
|
+
}
|
|
50
|
+
return STATUS_UNSET;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Seconds-float -> integer-nanosecond string. Computed as whole seconds plus
|
|
54
|
+
* padded fractional nanos rather than `seconds * 1e9`: the product (~1.7e18)
|
|
55
|
+
* exceeds Number.MAX_SAFE_INTEGER and would quantize timestamps to ~256 ns,
|
|
56
|
+
* distorting sub-microsecond span durations.
|
|
57
|
+
*/
|
|
58
|
+
const toUnixNano = (secondsFloat) => {
|
|
59
|
+
if (typeof secondsFloat !== "number" ||
|
|
60
|
+
!Number.isFinite(secondsFloat) ||
|
|
61
|
+
secondsFloat < 0) {
|
|
62
|
+
return "0";
|
|
63
|
+
}
|
|
64
|
+
let seconds = Math.floor(secondsFloat);
|
|
65
|
+
let nanos = Math.round((secondsFloat - seconds) * 1e9);
|
|
66
|
+
if (nanos >= 1e9) {
|
|
67
|
+
seconds += 1;
|
|
68
|
+
nanos = 0;
|
|
69
|
+
}
|
|
70
|
+
return seconds === 0
|
|
71
|
+
? nanos.toString()
|
|
72
|
+
: `${seconds}${nanos.toString().padStart(9, "0")}`;
|
|
73
|
+
};
|
|
74
|
+
const toAnyValue = (value) => {
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
return { stringValue: value };
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === "boolean") {
|
|
79
|
+
return { boolValue: value };
|
|
80
|
+
}
|
|
81
|
+
if (typeof value === "number") {
|
|
82
|
+
return Number.isInteger(value)
|
|
83
|
+
? { intValue: Math.trunc(value).toString() }
|
|
84
|
+
: { doubleValue: value };
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
const values = value
|
|
88
|
+
.map(toAnyValue)
|
|
89
|
+
.filter((v) => v !== null);
|
|
90
|
+
return { arrayValue: { values } };
|
|
91
|
+
}
|
|
92
|
+
if (value === null || value === undefined) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
// Plain objects -> OTLP kvlistValue (recursively), so nested structure reaches
|
|
96
|
+
// the collector as real key/values instead of a JSON string. This is what lets
|
|
97
|
+
// e.g. each item in a `product_items` array arrive as an object, not a string.
|
|
98
|
+
// Arrays and scalars are already handled above.
|
|
99
|
+
if (typeof value === "object") {
|
|
100
|
+
const values = [];
|
|
101
|
+
for (const [key, child] of Object.entries(value)) {
|
|
102
|
+
const childValue = toAnyValue(child);
|
|
103
|
+
if (childValue !== null) {
|
|
104
|
+
values.push({ key, value: childValue });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { kvlistValue: { values } };
|
|
108
|
+
}
|
|
109
|
+
// Non-object, non-scalar leftovers (bigint, symbol, function) -> string.
|
|
110
|
+
try {
|
|
111
|
+
return { stringValue: JSON.stringify(value) };
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return { stringValue: "[uncoercible]" };
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const toAttributes = (data) => {
|
|
118
|
+
if (!data) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const [key, raw] of Object.entries(data)) {
|
|
123
|
+
const value = toAnyValue(raw);
|
|
124
|
+
if (value !== null) {
|
|
125
|
+
out.push({ key, value });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
};
|
|
130
|
+
/** Conditional single string attribute — empty array when absent/blank. */
|
|
131
|
+
const strAttr = (key, value) => typeof value === "string" && value.length > 0
|
|
132
|
+
? [{ key, value: { stringValue: value } }]
|
|
133
|
+
: [];
|
|
134
|
+
/**
|
|
135
|
+
* Build the OTLP `resource.attributes` array.
|
|
136
|
+
*
|
|
137
|
+
* `service.name` / `service.version` come from config first (so a customer's
|
|
138
|
+
* own app identity flows through), then fall back to the Sentry event's
|
|
139
|
+
* `release`. Browser / OS / device contexts map to OTel resource semconv.
|
|
140
|
+
*/
|
|
141
|
+
const buildResourceAttributes = (event, config) => {
|
|
142
|
+
const browser = event.contexts?.browser;
|
|
143
|
+
const os = event.contexts?.os;
|
|
144
|
+
const device = event.contexts?.device;
|
|
145
|
+
return [
|
|
146
|
+
...strAttr("service.name", config.serviceName ?? "sentry-app"),
|
|
147
|
+
...strAttr("service.version", config.serviceVersion ?? event.release),
|
|
148
|
+
...strAttr("deployment.environment", event.environment),
|
|
149
|
+
// SDK identity
|
|
150
|
+
...strAttr("telemetry.sdk.name", event.sdk?.name),
|
|
151
|
+
...strAttr("telemetry.sdk.version", event.sdk?.version),
|
|
152
|
+
...strAttr("telemetry.sdk.language", event.platform),
|
|
153
|
+
// Browser / OS / device context (typed `unknown` via the index signature)
|
|
154
|
+
...strAttr("browser.name", browser?.name),
|
|
155
|
+
...strAttr("browser.version", browser?.version),
|
|
156
|
+
...strAttr("os.name", os?.name),
|
|
157
|
+
...strAttr("os.version", os?.version),
|
|
158
|
+
...strAttr("device.model.name", device?.model),
|
|
159
|
+
...strAttr("device.manufacturer", device?.brand),
|
|
160
|
+
...strAttr("device.model.identifier", device?.family),
|
|
161
|
+
];
|
|
162
|
+
};
|
|
163
|
+
/** String form of the Sentry user id (`string | number`). */
|
|
164
|
+
const userIdToString = (id) => {
|
|
165
|
+
if (typeof id === "string") {
|
|
166
|
+
return id.length > 0 ? id : undefined;
|
|
167
|
+
}
|
|
168
|
+
if (typeof id === "number" || typeof id === "bigint") {
|
|
169
|
+
return String(id);
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Event-scoped attributes stamped onto every span: the injected `session.id`
|
|
175
|
+
* plus the Sentry user identity (`Sentry.setUser`). `user.id` is the join key
|
|
176
|
+
* for per-user behavior analytics — without it, events can't be grouped by user.
|
|
177
|
+
*/
|
|
178
|
+
const eventScopedAttrs = (event, sessionId) => [
|
|
179
|
+
...strAttr("session.id", sessionId),
|
|
180
|
+
...strAttr("user.id", userIdToString(event.user?.id)),
|
|
181
|
+
...strAttr("user.name", event.user?.username),
|
|
182
|
+
...strAttr("user.email", event.user?.email),
|
|
183
|
+
];
|
|
184
|
+
/**
|
|
185
|
+
* Append the given attributes to every span. Single choke point so all span
|
|
186
|
+
* families (transaction root, children, error) get them uniformly. The transform
|
|
187
|
+
* stays pure — the values are explicit inputs resolved by the impure tee
|
|
188
|
+
* boundary, not read here.
|
|
189
|
+
*/
|
|
190
|
+
const withScopedAttrs = (spans, attrs) => attrs.length === 0
|
|
191
|
+
? spans
|
|
192
|
+
: spans.map((span) => ({
|
|
193
|
+
...span,
|
|
194
|
+
attributes: [...span.attributes, ...attrs],
|
|
195
|
+
}));
|
|
196
|
+
const buildExportRequest = (spans, resourceEvent, config, sessionId) => ({
|
|
197
|
+
resourceSpans: [
|
|
198
|
+
{
|
|
199
|
+
resource: { attributes: buildResourceAttributes(resourceEvent, config) },
|
|
200
|
+
scopeSpans: [
|
|
201
|
+
{
|
|
202
|
+
scope: { name: SCOPE_NAME, version: SCOPE_VERSION },
|
|
203
|
+
spans: withScopedAttrs(spans, eventScopedAttrs(resourceEvent, sessionId)),
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
});
|
|
209
|
+
const sentryTransactionToOtlp = (event, config, sessionId) => {
|
|
210
|
+
const trace = event.contexts?.trace;
|
|
211
|
+
const spans = [];
|
|
212
|
+
if (trace?.trace_id && trace.span_id) {
|
|
213
|
+
spans.push({
|
|
214
|
+
traceId: trace.trace_id,
|
|
215
|
+
spanId: trace.span_id,
|
|
216
|
+
parentSpanId: trace.parent_span_id,
|
|
217
|
+
name: event.transaction ?? trace.op ?? "transaction",
|
|
218
|
+
kind: SPAN_KIND_INTERNAL,
|
|
219
|
+
startTimeUnixNano: toUnixNano(event.start_timestamp),
|
|
220
|
+
endTimeUnixNano: toUnixNano(event.timestamp),
|
|
221
|
+
attributes: [
|
|
222
|
+
...toAttributes(trace.data),
|
|
223
|
+
...strAttr("sentry.op", trace.op),
|
|
224
|
+
...strAttr("sentry.source", event.transaction_info?.source),
|
|
225
|
+
],
|
|
226
|
+
status: { code: mapStatus(trace.status) },
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
for (const child of event.spans ?? []) {
|
|
230
|
+
spans.push({
|
|
231
|
+
traceId: child.trace_id,
|
|
232
|
+
spanId: child.span_id,
|
|
233
|
+
parentSpanId: child.parent_span_id,
|
|
234
|
+
name: child.description ?? child.op ?? "span",
|
|
235
|
+
kind: SPAN_KIND_INTERNAL,
|
|
236
|
+
startTimeUnixNano: toUnixNano(child.start_timestamp),
|
|
237
|
+
endTimeUnixNano: toUnixNano(child.timestamp),
|
|
238
|
+
attributes: [...toAttributes(child.data), ...strAttr("sentry.op", child.op)],
|
|
239
|
+
status: { code: mapStatus(child.status) },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return buildExportRequest(spans, event, config, sessionId);
|
|
243
|
+
};
|
|
244
|
+
exports.sentryTransactionToOtlp = sentryTransactionToOtlp;
|
|
245
|
+
const renderStackFrames = (frames) => frames
|
|
246
|
+
.map((f) => ` at ${f.function ?? "<anonymous>"} (${f.filename ?? "?"}:${f.lineno ?? 0}:${f.colno ?? 0})`)
|
|
247
|
+
.join("\n");
|
|
248
|
+
const sentryErrorToOtlp = (event, config, sessionId) => {
|
|
249
|
+
const trace = event.contexts?.trace;
|
|
250
|
+
// Anchor on the Sentry event_id (32-hex) when there's no trace context,
|
|
251
|
+
// so the transform stays pure (no randomness).
|
|
252
|
+
const traceId = trace?.trace_id ?? event.event_id;
|
|
253
|
+
if (!traceId) {
|
|
254
|
+
return buildExportRequest([], event, config, sessionId);
|
|
255
|
+
}
|
|
256
|
+
// The error span gets its own id derived from the (unique) event_id and is
|
|
257
|
+
// parented under the active span. Reusing `trace.span_id` as the span id
|
|
258
|
+
// would collide with the live span the transaction export also ships.
|
|
259
|
+
const spanId = event.event_id?.slice(0, 16) ?? traceId.slice(0, 16);
|
|
260
|
+
const parentSpanId = trace?.span_id;
|
|
261
|
+
const ts = toUnixNano(event.timestamp);
|
|
262
|
+
const values = event.exception?.values ?? [];
|
|
263
|
+
// Sentry orders exceptions outermost-first; the last entry is the innermost
|
|
264
|
+
// (most specific) error — use it for the span name.
|
|
265
|
+
const primary = values.length > 0 ? values[values.length - 1] : undefined;
|
|
266
|
+
const events = values.map((ex) => {
|
|
267
|
+
const attributes = [
|
|
268
|
+
...strAttr("exception.type", ex.type),
|
|
269
|
+
...strAttr("exception.message", ex.value),
|
|
270
|
+
];
|
|
271
|
+
const frames = ex.stacktrace?.frames;
|
|
272
|
+
if (frames && frames.length > 0) {
|
|
273
|
+
attributes.push({
|
|
274
|
+
key: "exception.stacktrace",
|
|
275
|
+
value: { stringValue: renderStackFrames(frames) },
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (ex.mechanism?.handled !== undefined) {
|
|
279
|
+
attributes.push({
|
|
280
|
+
key: "exception.escaped",
|
|
281
|
+
value: { boolValue: !ex.mechanism?.handled },
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return { timeUnixNano: ts, name: "exception", attributes };
|
|
285
|
+
});
|
|
286
|
+
const span = {
|
|
287
|
+
traceId,
|
|
288
|
+
spanId,
|
|
289
|
+
parentSpanId,
|
|
290
|
+
name: event.transaction ?? primary?.type ?? "error",
|
|
291
|
+
kind: SPAN_KIND_INTERNAL,
|
|
292
|
+
startTimeUnixNano: ts,
|
|
293
|
+
endTimeUnixNano: ts,
|
|
294
|
+
attributes: [
|
|
295
|
+
...toAttributes(event.tags),
|
|
296
|
+
...toAttributes(event.extra),
|
|
297
|
+
...strAttr("sentry.level", event.level),
|
|
298
|
+
...strAttr("exception.type", primary?.type),
|
|
299
|
+
...strAttr("exception.message", primary?.value),
|
|
300
|
+
],
|
|
301
|
+
events,
|
|
302
|
+
status: { code: STATUS_ERROR, message: primary?.value },
|
|
303
|
+
};
|
|
304
|
+
return buildExportRequest([span], event, config, sessionId);
|
|
305
|
+
};
|
|
306
|
+
exports.sentryErrorToOtlp = sentryErrorToOtlp;
|
|
307
|
+
const isTransactionEvent = (event) => event.type === "transaction";
|
|
308
|
+
/**
|
|
309
|
+
* Dispatcher: route a Sentry event to the right transform by `event.type`.
|
|
310
|
+
* Transactions carry `type: 'transaction'`; error events leave it undefined.
|
|
311
|
+
*/
|
|
312
|
+
const sentryEventToOtlp = (event, config, sessionId) => isTransactionEvent(event)
|
|
313
|
+
? (0, exports.sentryTransactionToOtlp)(event, config, sessionId)
|
|
314
|
+
: (0, exports.sentryErrorToOtlp)(event, config, sessionId);
|
|
315
|
+
exports.sentryEventToOtlp = sentryEventToOtlp;
|
|
316
|
+
//# sourceMappingURL=sentryToOtel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentryToOtel.js","sourceRoot":"","sources":["../src/sentryToOtel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session id management for behavior analytics.
|
|
3
|
+
*
|
|
4
|
+
* `createRollingSession` returns a provider — call it once per teed event to get
|
|
5
|
+
* the current session id. A session keeps its id for a fixed window (default 30
|
|
6
|
+
* min) measured from when it STARTED, regardless of activity: a hard clock cap,
|
|
7
|
+
* not an inactivity timeout. When the window elapses the next call mints a fresh
|
|
8
|
+
* id.
|
|
9
|
+
*
|
|
10
|
+
* The first session of a fresh visitor can be SEEDED via `initialId` (a string
|
|
11
|
+
* or a lazy function) — e.g. a hashed access token — so the opening session is
|
|
12
|
+
* tied to a known identity; every later window uses `generateId`. This is the
|
|
13
|
+
* "one more layer" some apps want on top of the plain rolling default:
|
|
14
|
+
*
|
|
15
|
+
* // Simple: plain 30-min rolling, random ids
|
|
16
|
+
* createRollingSession()
|
|
17
|
+
*
|
|
18
|
+
* // Seeded: first window = a hashed identity, then roll to generated ids
|
|
19
|
+
* createRollingSession({ initialId: () => hashAccessToken(token) })
|
|
20
|
+
*
|
|
21
|
+
* Persisted to `localStorage` by default so the id survives page navigations;
|
|
22
|
+
* pass `storage: null` for in-memory only (Node, or privacy-sensitive contexts).
|
|
23
|
+
*/
|
|
24
|
+
/** Minimal storage contract — a subset of the Web Storage API. */
|
|
25
|
+
export interface SessionStorageLike {
|
|
26
|
+
getItem(key: string): string | null;
|
|
27
|
+
setItem(key: string, value: string): void;
|
|
28
|
+
}
|
|
29
|
+
export interface RollingSessionOptions {
|
|
30
|
+
/** Hard cap per session, in ms. Default 1_800_000 (30 min). */
|
|
31
|
+
windowMs?: number;
|
|
32
|
+
/** Persistence key. Default "kubit-session". */
|
|
33
|
+
storageKey?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Id for the FIRST session of a fresh visitor (no persisted session yet). A
|
|
36
|
+
* string, or a function evaluated once when that session starts. Every later
|
|
37
|
+
* window uses `generateId` instead.
|
|
38
|
+
*/
|
|
39
|
+
initialId?: string | (() => string);
|
|
40
|
+
/** Id generator for every window after the first. Default: `crypto.randomUUID`. */
|
|
41
|
+
generateId?: () => string;
|
|
42
|
+
/**
|
|
43
|
+
* Storage backend. Defaults to `localStorage` when available, else in-memory.
|
|
44
|
+
* Pass `null` to force in-memory (no cross-reload persistence).
|
|
45
|
+
*/
|
|
46
|
+
storage?: SessionStorageLike | null;
|
|
47
|
+
/** Clock source — injectable for tests. Default `Date.now`. */
|
|
48
|
+
now?: () => number;
|
|
49
|
+
}
|
|
50
|
+
/** Returns the current session id. Call once per event. */
|
|
51
|
+
export type SessionIdProvider = () => string;
|
|
52
|
+
export declare const createRollingSession: (options?: RollingSessionOptions) => SessionIdProvider;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session id management for behavior analytics.
|
|
4
|
+
*
|
|
5
|
+
* `createRollingSession` returns a provider — call it once per teed event to get
|
|
6
|
+
* the current session id. A session keeps its id for a fixed window (default 30
|
|
7
|
+
* min) measured from when it STARTED, regardless of activity: a hard clock cap,
|
|
8
|
+
* not an inactivity timeout. When the window elapses the next call mints a fresh
|
|
9
|
+
* id.
|
|
10
|
+
*
|
|
11
|
+
* The first session of a fresh visitor can be SEEDED via `initialId` (a string
|
|
12
|
+
* or a lazy function) — e.g. a hashed access token — so the opening session is
|
|
13
|
+
* tied to a known identity; every later window uses `generateId`. This is the
|
|
14
|
+
* "one more layer" some apps want on top of the plain rolling default:
|
|
15
|
+
*
|
|
16
|
+
* // Simple: plain 30-min rolling, random ids
|
|
17
|
+
* createRollingSession()
|
|
18
|
+
*
|
|
19
|
+
* // Seeded: first window = a hashed identity, then roll to generated ids
|
|
20
|
+
* createRollingSession({ initialId: () => hashAccessToken(token) })
|
|
21
|
+
*
|
|
22
|
+
* Persisted to `localStorage` by default so the id survives page navigations;
|
|
23
|
+
* pass `storage: null` for in-memory only (Node, or privacy-sensitive contexts).
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.createRollingSession = void 0;
|
|
27
|
+
const DEFAULT_WINDOW_MS = 30 * 60 * 1000;
|
|
28
|
+
const DEFAULT_STORAGE_KEY = "kubit-session";
|
|
29
|
+
/** `localStorage` when present (browser); null in Node or sandboxed contexts. */
|
|
30
|
+
const getDefaultStorage = () => {
|
|
31
|
+
try {
|
|
32
|
+
if (typeof localStorage !== "undefined") {
|
|
33
|
+
return localStorage;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// accessing localStorage can throw in sandboxed iframes
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
const defaultGenerateId = () => {
|
|
42
|
+
try {
|
|
43
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
44
|
+
return crypto.randomUUID();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// fall through to the non-crypto id
|
|
49
|
+
}
|
|
50
|
+
return `sess-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
51
|
+
};
|
|
52
|
+
const resolveSeed = (seed) => {
|
|
53
|
+
try {
|
|
54
|
+
const value = typeof seed === "function" ? seed() : seed;
|
|
55
|
+
return typeof value === "string" ? value : "";
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const isLive = (saved, now, windowMs) => saved !== null && saved.id.length > 0 && now - saved.start < windowMs;
|
|
62
|
+
const createRollingSession = (options = {}) => {
|
|
63
|
+
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
|
|
64
|
+
const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
65
|
+
const generateId = options.generateId ?? defaultGenerateId;
|
|
66
|
+
const now = options.now ?? (() => Date.now());
|
|
67
|
+
const storage = options.storage === undefined ? getDefaultStorage() : options.storage;
|
|
68
|
+
// In-memory mirror — authoritative within a page, backed by `storage` across
|
|
69
|
+
// reloads / navigations.
|
|
70
|
+
let memory = null;
|
|
71
|
+
const read = () => {
|
|
72
|
+
if (memory) {
|
|
73
|
+
return memory;
|
|
74
|
+
}
|
|
75
|
+
if (!storage) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const raw = storage.getItem(storageKey);
|
|
80
|
+
if (!raw) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const parsed = JSON.parse(raw);
|
|
84
|
+
if (parsed &&
|
|
85
|
+
typeof parsed.id === "string" &&
|
|
86
|
+
typeof parsed.start === "number") {
|
|
87
|
+
return { id: parsed.id, start: parsed.start };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// unreadable / malformed — treat as no session
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
const write = (session) => {
|
|
96
|
+
memory = session;
|
|
97
|
+
if (!storage) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
storage.setItem(storageKey, JSON.stringify(session));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// storage full / unavailable — `memory` still holds the session
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return () => {
|
|
108
|
+
const t = now();
|
|
109
|
+
const saved = read();
|
|
110
|
+
if (isLive(saved, t, windowMs)) {
|
|
111
|
+
memory = saved;
|
|
112
|
+
return saved.id;
|
|
113
|
+
}
|
|
114
|
+
// Mint a new session. A fresh visitor (no prior session at all) may be
|
|
115
|
+
// seeded with `initialId`; every later window uses `generateId`.
|
|
116
|
+
const seeded = saved === null && options.initialId !== undefined
|
|
117
|
+
? resolveSeed(options.initialId)
|
|
118
|
+
: "";
|
|
119
|
+
const id = seeded.length > 0 ? seeded : generateId();
|
|
120
|
+
write({ id, start: t });
|
|
121
|
+
return id;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
exports.createRollingSession = createRollingSession;
|
|
125
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP/HTTP+JSON encoding types (trace signal only).
|
|
3
|
+
*
|
|
4
|
+
* Reference: OpenTelemetry OTLP/HTTP JSON encoding spec
|
|
5
|
+
* https://opentelemetry.io/docs/specs/otlp/#otlphttp-with-json-encoding
|
|
6
|
+
*
|
|
7
|
+
* These mirror the subset of the OTLP `ExportTraceServiceRequest` shape we
|
|
8
|
+
* emit. The Kubit collector accepts both protobuf and JSON; this SDK sends
|
|
9
|
+
* JSON to keep the browser build free of a protobuf dependency.
|
|
10
|
+
*/
|
|
11
|
+
export type OtlpAnyValue = {
|
|
12
|
+
stringValue: string;
|
|
13
|
+
} | {
|
|
14
|
+
intValue: string;
|
|
15
|
+
} | {
|
|
16
|
+
doubleValue: number;
|
|
17
|
+
} | {
|
|
18
|
+
boolValue: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
arrayValue: {
|
|
21
|
+
values: OtlpAnyValue[];
|
|
22
|
+
};
|
|
23
|
+
} | {
|
|
24
|
+
kvlistValue: {
|
|
25
|
+
values: OtlpKeyValue[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export interface OtlpKeyValue {
|
|
29
|
+
key: string;
|
|
30
|
+
value: OtlpAnyValue;
|
|
31
|
+
}
|
|
32
|
+
/** A span event (e.g. a recorded exception). */
|
|
33
|
+
export interface OtlpSpanEvent {
|
|
34
|
+
timeUnixNano: string;
|
|
35
|
+
name: string;
|
|
36
|
+
attributes: OtlpKeyValue[];
|
|
37
|
+
}
|
|
38
|
+
export interface OtlpSpan {
|
|
39
|
+
traceId: string;
|
|
40
|
+
spanId: string;
|
|
41
|
+
parentSpanId?: string;
|
|
42
|
+
name: string;
|
|
43
|
+
/** SpanKind enum. 1 = INTERNAL (default for browser-side spans). */
|
|
44
|
+
kind: number;
|
|
45
|
+
startTimeUnixNano: string;
|
|
46
|
+
endTimeUnixNano: string;
|
|
47
|
+
attributes: OtlpKeyValue[];
|
|
48
|
+
events?: OtlpSpanEvent[];
|
|
49
|
+
status: {
|
|
50
|
+
code: number;
|
|
51
|
+
message?: string;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export interface OtlpExportRequest {
|
|
55
|
+
resourceSpans: Array<{
|
|
56
|
+
resource: {
|
|
57
|
+
attributes: OtlpKeyValue[];
|
|
58
|
+
};
|
|
59
|
+
scopeSpans: Array<{
|
|
60
|
+
scope: {
|
|
61
|
+
name: string;
|
|
62
|
+
version: string;
|
|
63
|
+
};
|
|
64
|
+
spans: OtlpSpan[];
|
|
65
|
+
}>;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OTLP/HTTP+JSON encoding types (trace signal only).
|
|
4
|
+
*
|
|
5
|
+
* Reference: OpenTelemetry OTLP/HTTP JSON encoding spec
|
|
6
|
+
* https://opentelemetry.io/docs/specs/otlp/#otlphttp-with-json-encoding
|
|
7
|
+
*
|
|
8
|
+
* These mirror the subset of the OTLP `ExportTraceServiceRequest` shape we
|
|
9
|
+
* emit. The Kubit collector accepts both protobuf and JSON; this SDK sends
|
|
10
|
+
* JSON to keep the browser build free of a protobuf dependency.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kubit-ai/sentry",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tee Sentry events (errors + transactions) to Kubit as OTLP for behavior analytics.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"sentry",
|
|
14
|
+
"opentelemetry",
|
|
15
|
+
"otel",
|
|
16
|
+
"kubit",
|
|
17
|
+
"observability",
|
|
18
|
+
"behavior-analytics",
|
|
19
|
+
"tracing"
|
|
20
|
+
],
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Kubit Inc",
|
|
23
|
+
"url": "https://www.kubit.ai"
|
|
24
|
+
},
|
|
25
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
26
|
+
"homepage": "https://www.kubit.ai",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.build.json",
|
|
35
|
+
"build:browser": "node scripts/build-browser.mjs",
|
|
36
|
+
"build:all": "npm run build && npm run build:browser",
|
|
37
|
+
"typecheck": "tsc",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"smoke": "npm run build && node scripts/smoke.js",
|
|
41
|
+
"prepublishOnly": "npm test && npm run build:all"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@sentry/core": ">=8.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@sentry/browser": "^10.57.0",
|
|
48
|
+
"@sentry/core": "^10.0.0",
|
|
49
|
+
"@types/node": "^20.0.0",
|
|
50
|
+
"esbuild": "^0.28.1",
|
|
51
|
+
"typescript": "^5.3.0",
|
|
52
|
+
"vitest": "^1.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|