@goliapkg/sentori-javascript 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/breadcrumbs.d.ts +3 -3
- package/lib/breadcrumbs.d.ts.map +1 -1
- package/lib/breadcrumbs.js +6 -16
- package/lib/breadcrumbs.js.map +1 -1
- package/lib/capture.js +10 -5
- package/lib/capture.js.map +1 -1
- package/lib/stack.d.ts +6 -1
- package/lib/stack.d.ts.map +1 -1
- package/lib/stack.js +8 -36
- package/lib/stack.js.map +1 -1
- package/lib/types.d.ts +7 -72
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +6 -0
- package/lib/types.js.map +1 -1
- package/lib/uuid.d.ts +1 -9
- package/lib/uuid.d.ts.map +1 -1
- package/lib/uuid.js +3 -53
- package/lib/uuid.js.map +1 -1
- package/package.json +4 -1
- package/src/breadcrumbs.ts +11 -17
- package/src/capture.ts +10 -5
- package/src/stack.ts +9 -35
- package/src/types.ts +23 -64
- package/src/uuid.ts +3 -59
package/lib/breadcrumbs.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { clearBreadcrumbs, getBreadcrumbs } from '@goliapkg/sentori-core';
|
|
2
|
+
import type { BreadcrumbType } from './types.js';
|
|
2
3
|
export type AddBreadcrumbInput = {
|
|
3
4
|
data?: Record<string, unknown>;
|
|
4
5
|
type: BreadcrumbType;
|
|
5
6
|
};
|
|
6
7
|
export declare function addBreadcrumb(input: AddBreadcrumbInput): void;
|
|
7
|
-
export
|
|
8
|
-
export declare function clearBreadcrumbs(): void;
|
|
8
|
+
export { clearBreadcrumbs, getBreadcrumbs };
|
|
9
9
|
//# sourceMappingURL=breadcrumbs.d.ts.map
|
package/lib/breadcrumbs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,gBAAgB,EAChB,cAAc,EACf,MAAM,wBAAwB,CAAA;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,IAAI,EAAE,cAAc,CAAA;CACrB,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAE7D;AAED,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAA"}
|
package/lib/breadcrumbs.js
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Phase 21: ring buffer logic lives in @goliapkg/sentori-core. The
|
|
2
|
+
// public surface here keeps its object-form `addBreadcrumb({ type,
|
|
3
|
+
// data })` so existing callers don't break.
|
|
4
|
+
import { addBreadcrumb as addBreadcrumbCore, clearBreadcrumbs, getBreadcrumbs, } from '@goliapkg/sentori-core';
|
|
3
5
|
export function addBreadcrumb(input) {
|
|
4
|
-
|
|
5
|
-
data: input.data ?? {},
|
|
6
|
-
timestamp: new Date().toISOString(),
|
|
7
|
-
type: input.type,
|
|
8
|
-
};
|
|
9
|
-
buf.push(crumb);
|
|
10
|
-
if (buf.length > MAX)
|
|
11
|
-
buf.shift();
|
|
12
|
-
}
|
|
13
|
-
export function getBreadcrumbs() {
|
|
14
|
-
return [...buf];
|
|
15
|
-
}
|
|
16
|
-
export function clearBreadcrumbs() {
|
|
17
|
-
buf.length = 0;
|
|
6
|
+
addBreadcrumbCore(input.type, input.data ?? {});
|
|
18
7
|
}
|
|
8
|
+
export { clearBreadcrumbs, getBreadcrumbs };
|
|
19
9
|
//# sourceMappingURL=breadcrumbs.js.map
|
package/lib/breadcrumbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,mEAAmE;AACnE,4CAA4C;AAC5C,OAAO,EACL,aAAa,IAAI,iBAAiB,EAClC,gBAAgB,EAChB,cAAc,GACf,MAAM,wBAAwB,CAAA;AAS/B,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;AACjD,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAA"}
|
package/lib/capture.js
CHANGED
|
@@ -56,13 +56,17 @@ function parseRelease(release) {
|
|
|
56
56
|
return { build: m?.[2], version: m?.[1] ?? '0.0.0' };
|
|
57
57
|
}
|
|
58
58
|
function detectDevice() {
|
|
59
|
-
//
|
|
60
|
-
//
|
|
59
|
+
// The server's device.os is a strict enum: `ios | android | web | other`
|
|
60
|
+
// (see docs/protocol.md). Browser → web; Node + everything else → other.
|
|
61
|
+
// The pre-Phase-21 build sent free-form values like "macos" / "windows"
|
|
62
|
+
// which the server quietly rejected with `validationFailed`. Detail
|
|
63
|
+
// about the underlying OS family rides along in `model` instead.
|
|
61
64
|
const w = globalThis.navigator;
|
|
62
65
|
if (w?.userAgent) {
|
|
63
66
|
return {
|
|
64
67
|
locale: w.language,
|
|
65
|
-
|
|
68
|
+
model: detectBrowserOs(w.userAgent),
|
|
69
|
+
os: 'web',
|
|
66
70
|
osVersion: '0',
|
|
67
71
|
};
|
|
68
72
|
}
|
|
@@ -70,11 +74,12 @@ function detectDevice() {
|
|
|
70
74
|
const p = globalThis.process;
|
|
71
75
|
if (p?.platform) {
|
|
72
76
|
return {
|
|
73
|
-
|
|
77
|
+
model: p.platform,
|
|
78
|
+
os: 'other',
|
|
74
79
|
osVersion: p.version?.replace(/^v/, '') ?? '0',
|
|
75
80
|
};
|
|
76
81
|
}
|
|
77
|
-
return { os: '
|
|
82
|
+
return { os: 'other', osVersion: '0' };
|
|
78
83
|
}
|
|
79
84
|
function detectBrowserOs(ua) {
|
|
80
85
|
if (ua.includes('Mac OS X') || ua.includes('Macintosh'))
|
package/lib/capture.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAElC,IAAI,KAAK,GAAgB,IAAI,CAAA;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,IAAiB;IACvC,KAAK,GAAG,IAAI,CAAA;AACd,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAY,EAAE,MAAsB;IAC/D,IAAI,CAAC,aAAa,EAAE;QAAE,OAAM;IAC5B,MAAM,GAAG,GAAG,SAAS,EAAG,CAAA;IACxB,MAAM,KAAK,GAAU;QACnB,GAAG,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;QACnD,WAAW,EAAE,cAAc,EAAE;QAC7B,MAAM,EAAE,YAAY,EAAE;QACtB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC;QAC3B,WAAW,EAAE,MAAM,EAAE,WAAW;QAChC,EAAE,EAAE,MAAM,EAAE;QACZ,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,IAAI,EAAE,MAAM,EAAE,IAAI;QAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,KAAK;KAC5B,CAAA;IACD,KAAK,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,CAAA;AAClE,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAA;AAE5C,SAAS,aAAa,CAAC,KAAY;IACjC,MAAM,QAAQ,GAAI,KAA6B,CAAC,KAAK,CAAA;IACrD,IAAI,KAAK,GAAwB,IAAI,CAAA;IACrC,IAAI,QAAQ,YAAY,KAAK;QAAE,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC9D,OAAO;QACL,KAAK;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;QAC9B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,OAAO;KAC5B,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,CAAC,GAAG,iCAAiC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACzD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAA;AACtD,CAAC;AAED,SAAS,YAAY;IACnB,
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
|
package/lib/stack.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Frame } from './types.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* JS-flavoured wrapper. The browser dashboard wants short filenames so
|
|
4
|
+
* "https://cdn.example.com/static/App.tsx" displays as "static/App.tsx".
|
|
5
|
+
* RN keeps the long path because Hermes paths are already short and
|
|
6
|
+
* symbolication needs the absolute form.
|
|
7
|
+
*/
|
|
3
8
|
export declare function parseStack(stack: string | undefined): Frame[];
|
|
4
9
|
//# sourceMappingURL=stack.d.ts.map
|
package/lib/stack.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAEvC;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,EAAE,CAE7D"}
|
package/lib/stack.js
CHANGED
|
@@ -1,39 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { parseStack as parseStackCore } from '@goliapkg/sentori-core';
|
|
2
|
+
/**
|
|
3
|
+
* JS-flavoured wrapper. The browser dashboard wants short filenames so
|
|
4
|
+
* "https://cdn.example.com/static/App.tsx" displays as "static/App.tsx".
|
|
5
|
+
* RN keeps the long path because Hermes paths are already short and
|
|
6
|
+
* symbolication needs the absolute form.
|
|
7
|
+
*/
|
|
8
8
|
export function parseStack(stack) {
|
|
9
|
-
|
|
10
|
-
return [];
|
|
11
|
-
const lines = stack.split('\n');
|
|
12
|
-
const out = [];
|
|
13
|
-
for (const raw of lines) {
|
|
14
|
-
const line = raw.trim();
|
|
15
|
-
if (!line)
|
|
16
|
-
continue;
|
|
17
|
-
const m = V8_RE.exec(line) ?? SPIDER_RE.exec(line);
|
|
18
|
-
if (!m?.groups)
|
|
19
|
-
continue;
|
|
20
|
-
const file = m.groups.file ?? '<anonymous>';
|
|
21
|
-
out.push({
|
|
22
|
-
absolutePath: file,
|
|
23
|
-
column: Number(m.groups.col),
|
|
24
|
-
file: shortFile(file),
|
|
25
|
-
function: m.groups.fn?.trim(),
|
|
26
|
-
inApp: !file.includes('node_modules') && !file.startsWith('node:'),
|
|
27
|
-
line: Number(m.groups.line),
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
function shortFile(absolute) {
|
|
33
|
-
// Strip protocol + leading path noise so the dashboard shows
|
|
34
|
-
// e.g. "App.tsx" instead of "https://example.com/static/App.tsx".
|
|
35
|
-
const noProto = absolute.replace(/^https?:\/\/[^/]+\//, '');
|
|
36
|
-
const tail = noProto.split('/').slice(-2).join('/');
|
|
37
|
-
return tail || absolute;
|
|
9
|
+
return parseStackCore(stack, { shortFilenames: true });
|
|
38
10
|
}
|
|
39
11
|
//# sourceMappingURL=stack.js.map
|
package/lib/stack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAIrE;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAyB;IAClD,OAAO,cAAc,CAAC,KAAK,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAA;AACxD,CAAC"}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,78 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Wire
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Wire-format types now live in `@goliapkg/sentori-core`. This file is
|
|
3
|
+
* kept as a thin re-export so existing relative imports inside the
|
|
4
|
+
* package continue to work. JS-specific extras (the `InitOptions`
|
|
5
|
+
* shape with `enableGlobalHooks`) are declared here.
|
|
5
6
|
*/
|
|
6
|
-
export type Event
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
framework?: {
|
|
10
|
-
name: string;
|
|
11
|
-
version: string;
|
|
12
|
-
};
|
|
13
|
-
version: string;
|
|
14
|
-
};
|
|
15
|
-
breadcrumbs: Breadcrumb[];
|
|
16
|
-
device: {
|
|
17
|
-
locale?: string;
|
|
18
|
-
model?: string;
|
|
19
|
-
os: string;
|
|
20
|
-
osVersion: string;
|
|
21
|
-
};
|
|
22
|
-
environment: string;
|
|
23
|
-
error: SentoriError;
|
|
24
|
-
fingerprint?: string[];
|
|
25
|
-
id: string;
|
|
26
|
-
kind: 'error';
|
|
27
|
-
platform: 'javascript';
|
|
28
|
-
release: string;
|
|
29
|
-
spanId?: null | string;
|
|
30
|
-
tags?: Tags;
|
|
31
|
-
timestamp: string;
|
|
32
|
-
traceId?: null | string;
|
|
33
|
-
user?: null | User;
|
|
34
|
-
};
|
|
35
|
-
export type SentoriError = {
|
|
36
|
-
cause: null | SentoriError;
|
|
37
|
-
message: string;
|
|
38
|
-
stack: Frame[];
|
|
39
|
-
type: string;
|
|
40
|
-
};
|
|
41
|
-
export type Frame = {
|
|
42
|
-
absolutePath?: string;
|
|
43
|
-
column?: number;
|
|
44
|
-
file: string;
|
|
45
|
-
function?: string;
|
|
46
|
-
inApp: boolean;
|
|
47
|
-
line: number;
|
|
48
|
-
};
|
|
49
|
-
export type BreadcrumbType = 'custom' | 'log' | 'nav' | 'net' | 'user';
|
|
50
|
-
export type Breadcrumb = {
|
|
51
|
-
data: Record<string, unknown>;
|
|
52
|
-
timestamp: string;
|
|
53
|
-
type: BreadcrumbType;
|
|
54
|
-
};
|
|
55
|
-
/** PII-minimal — same shape as the RN SDK and the server schema. */
|
|
56
|
-
export type User = {
|
|
57
|
-
anonymous?: boolean;
|
|
58
|
-
id?: string;
|
|
59
|
-
};
|
|
60
|
-
export type Tags = Record<string, string>;
|
|
61
|
-
export type CaptureExtras = {
|
|
62
|
-
fingerprint?: string[];
|
|
63
|
-
tags?: Tags;
|
|
64
|
-
user?: User;
|
|
65
|
-
};
|
|
66
|
-
export type InitOptions = {
|
|
7
|
+
export type { App, Breadcrumb, BreadcrumbType, CaptureExtras, Device, DeviceOS, Event, EventKind, Frame, Platform, SentoriError, Tags, User, } from '@goliapkg/sentori-core';
|
|
8
|
+
import type { CommonInitOptions } from '@goliapkg/sentori-core';
|
|
9
|
+
export type InitOptions = CommonInitOptions & {
|
|
67
10
|
/** Override automatic global hooks. Default: true on browser + node. */
|
|
68
11
|
enableGlobalHooks?: boolean;
|
|
69
|
-
/** "prod" / "dev" / "staging" / whatever you want. */
|
|
70
|
-
environment: string;
|
|
71
|
-
/** e.g. https://ingest.sentori.golia.jp */
|
|
72
|
-
ingestUrl: string;
|
|
73
|
-
/** e.g. "myapp@1.2.3+456" */
|
|
74
|
-
release: string;
|
|
75
|
-
/** st_pk_<26 base32 chars> */
|
|
76
|
-
token: string;
|
|
77
12
|
};
|
|
78
13
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EACV,GAAG,EACH,UAAU,EACV,cAAc,EACd,aAAa,EACb,MAAM,EACN,QAAQ,EACR,KAAK,EACL,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,IAAI,GACL,MAAM,wBAAwB,CAAA;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG;IAC5C,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA"}
|
package/lib/types.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-format types now live in `@goliapkg/sentori-core`. This file is
|
|
3
|
+
* kept as a thin re-export so existing relative imports inside the
|
|
4
|
+
* package continue to work. JS-specific extras (the `InitOptions`
|
|
5
|
+
* shape with `enableGlobalHooks`) are declared here.
|
|
6
|
+
*/
|
|
1
7
|
export {};
|
|
2
8
|
//# sourceMappingURL=types.js.map
|
package/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
package/lib/uuid.d.ts
CHANGED
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* uuid v7 (timestamp-prefixed). Modern Node ≥ 19 + browsers expose
|
|
3
|
-
* `crypto.randomUUID()` for v4 — that gives us the entropy half;
|
|
4
|
-
* v7 layout is cheap to assemble manually.
|
|
5
|
-
*
|
|
6
|
-
* Layout (RFC 9562 v7):
|
|
7
|
-
* ms (48 bits) | ver=7 (4) | rand_a (12) | var=10 (2) | rand_b (62)
|
|
8
|
-
*/
|
|
9
|
-
export declare function uuidV7(): string;
|
|
1
|
+
export { uuidV7 } from '@goliapkg/sentori-core';
|
|
10
2
|
//# sourceMappingURL=uuid.d.ts.map
|
package/lib/uuid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA"}
|
package/lib/uuid.js
CHANGED
|
@@ -1,54 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* v7 layout is cheap to assemble manually.
|
|
5
|
-
*
|
|
6
|
-
* Layout (RFC 9562 v7):
|
|
7
|
-
* ms (48 bits) | ver=7 (4) | rand_a (12) | var=10 (2) | rand_b (62)
|
|
8
|
-
*/
|
|
9
|
-
export function uuidV7() {
|
|
10
|
-
const ms = Date.now();
|
|
11
|
-
const rand = new Uint8Array(10);
|
|
12
|
-
cryptoRandomFill(rand);
|
|
13
|
-
// 6 bytes of timestamp (ms), big-endian
|
|
14
|
-
const t = new Uint8Array(6);
|
|
15
|
-
let n = ms;
|
|
16
|
-
for (let i = 5; i >= 0; i--) {
|
|
17
|
-
t[i] = n & 0xff;
|
|
18
|
-
n = Math.floor(n / 256);
|
|
19
|
-
}
|
|
20
|
-
// pack version + variant
|
|
21
|
-
rand[0] = (rand[0] & 0x0f) | 0x70; // version 7 in high nibble of byte 6
|
|
22
|
-
rand[2] = (rand[2] & 0x3f) | 0x80; // variant 10 in high two bits of byte 8
|
|
23
|
-
const bytes = new Uint8Array(16);
|
|
24
|
-
bytes.set(t, 0);
|
|
25
|
-
bytes.set(rand, 6);
|
|
26
|
-
return (hex(bytes.subarray(0, 4)) +
|
|
27
|
-
'-' +
|
|
28
|
-
hex(bytes.subarray(4, 6)) +
|
|
29
|
-
'-' +
|
|
30
|
-
hex(bytes.subarray(6, 8)) +
|
|
31
|
-
'-' +
|
|
32
|
-
hex(bytes.subarray(8, 10)) +
|
|
33
|
-
'-' +
|
|
34
|
-
hex(bytes.subarray(10, 16)));
|
|
35
|
-
}
|
|
36
|
-
function cryptoRandomFill(buf) {
|
|
37
|
-
// Browser + Node 19+ + Bun all expose globalThis.crypto.
|
|
38
|
-
const c = globalThis.crypto;
|
|
39
|
-
if (c?.getRandomValues) {
|
|
40
|
-
c.getRandomValues(buf);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
// Last-resort Math.random (only hits in very old envs; entropy
|
|
44
|
-
// quality is bad but we still want a unique-ish id).
|
|
45
|
-
for (let i = 0; i < buf.length; i++)
|
|
46
|
-
buf[i] = Math.floor(Math.random() * 256);
|
|
47
|
-
}
|
|
48
|
-
function hex(b) {
|
|
49
|
-
let s = '';
|
|
50
|
-
for (const x of b)
|
|
51
|
-
s += x.toString(16).padStart(2, '0');
|
|
52
|
-
return s;
|
|
53
|
-
}
|
|
1
|
+
// Phase 21: moved to @goliapkg/sentori-core. Kept as a re-export so
|
|
2
|
+
// internal imports don't churn.
|
|
3
|
+
export { uuidV7 } from '@goliapkg/sentori-core';
|
|
54
4
|
//# sourceMappingURL=uuid.js.map
|
package/lib/uuid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,gCAAgC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-javascript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Sentori SDK for browsers and Node — error capture, breadcrumbs, and HTTP transport. Same wire protocol as @goliapkg/sentori-react-native.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://sentori.golia.jp",
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"src/",
|
|
36
36
|
"README.md"
|
|
37
37
|
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@goliapkg/sentori-core": "0.1.0"
|
|
40
|
+
},
|
|
38
41
|
"scripts": {
|
|
39
42
|
"build": "tsc -p tsconfig.json",
|
|
40
43
|
"typecheck": "tsc --noEmit",
|
package/src/breadcrumbs.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
// Phase 21: ring buffer logic lives in @goliapkg/sentori-core. The
|
|
2
|
+
// public surface here keeps its object-form `addBreadcrumb({ type,
|
|
3
|
+
// data })` so existing callers don't break.
|
|
4
|
+
import {
|
|
5
|
+
addBreadcrumb as addBreadcrumbCore,
|
|
6
|
+
clearBreadcrumbs,
|
|
7
|
+
getBreadcrumbs,
|
|
8
|
+
} from '@goliapkg/sentori-core'
|
|
2
9
|
|
|
3
|
-
|
|
4
|
-
const buf: Breadcrumb[] = []
|
|
10
|
+
import type { BreadcrumbType } from './types.js'
|
|
5
11
|
|
|
6
12
|
export type AddBreadcrumbInput = {
|
|
7
13
|
data?: Record<string, unknown>
|
|
@@ -9,19 +15,7 @@ export type AddBreadcrumbInput = {
|
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
export function addBreadcrumb(input: AddBreadcrumbInput): void {
|
|
12
|
-
|
|
13
|
-
data: input.data ?? {},
|
|
14
|
-
timestamp: new Date().toISOString(),
|
|
15
|
-
type: input.type,
|
|
16
|
-
}
|
|
17
|
-
buf.push(crumb)
|
|
18
|
-
if (buf.length > MAX) buf.shift()
|
|
18
|
+
addBreadcrumbCore(input.type, input.data ?? {})
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
return [...buf]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function clearBreadcrumbs(): void {
|
|
26
|
-
buf.length = 0
|
|
27
|
-
}
|
|
21
|
+
export { clearBreadcrumbs, getBreadcrumbs }
|
package/src/capture.ts
CHANGED
|
@@ -63,13 +63,17 @@ function parseRelease(release: string): { build?: string; version: string } {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function detectDevice(): Event['device'] {
|
|
66
|
-
//
|
|
67
|
-
//
|
|
66
|
+
// The server's device.os is a strict enum: `ios | android | web | other`
|
|
67
|
+
// (see docs/protocol.md). Browser → web; Node + everything else → other.
|
|
68
|
+
// The pre-Phase-21 build sent free-form values like "macos" / "windows"
|
|
69
|
+
// which the server quietly rejected with `validationFailed`. Detail
|
|
70
|
+
// about the underlying OS family rides along in `model` instead.
|
|
68
71
|
const w = (globalThis as { navigator?: { language?: string; userAgent?: string } }).navigator
|
|
69
72
|
if (w?.userAgent) {
|
|
70
73
|
return {
|
|
71
74
|
locale: w.language,
|
|
72
|
-
|
|
75
|
+
model: detectBrowserOs(w.userAgent),
|
|
76
|
+
os: 'web',
|
|
73
77
|
osVersion: '0',
|
|
74
78
|
}
|
|
75
79
|
}
|
|
@@ -77,11 +81,12 @@ function detectDevice(): Event['device'] {
|
|
|
77
81
|
const p = (globalThis as { process?: { platform?: string; version?: string } }).process
|
|
78
82
|
if (p?.platform) {
|
|
79
83
|
return {
|
|
80
|
-
|
|
84
|
+
model: p.platform,
|
|
85
|
+
os: 'other',
|
|
81
86
|
osVersion: p.version?.replace(/^v/, '') ?? '0',
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
|
-
return { os: '
|
|
89
|
+
return { os: 'other', osVersion: '0' }
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
function detectBrowserOs(ua: string): string {
|
package/src/stack.ts
CHANGED
|
@@ -1,39 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { parseStack as parseStackCore } from '@goliapkg/sentori-core'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
// File can be a URL (https://…), so we anchor on the trailing `:\d+:\d+\)?$`
|
|
5
|
-
// and let `(?<file>.+?)` swallow whatever comes before.
|
|
6
|
-
const V8_RE = /^\s*at\s+(?:(?<fn>.+?)\s+)?\(?(?<file>.+?):(?<line>\d+):(?<col>\d+)\)?\s*$/
|
|
7
|
-
// SpiderMonkey / Safari: "fn@file:line:col" — same trailing anchor.
|
|
8
|
-
const SPIDER_RE = /^(?:(?<fn>[^@]*)@)?(?<file>.+?):(?<line>\d+):(?<col>\d+)\s*$/
|
|
3
|
+
import type { Frame } from './types.js'
|
|
9
4
|
|
|
10
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* JS-flavoured wrapper. The browser dashboard wants short filenames so
|
|
7
|
+
* "https://cdn.example.com/static/App.tsx" displays as "static/App.tsx".
|
|
8
|
+
* RN keeps the long path because Hermes paths are already short and
|
|
9
|
+
* symbolication needs the absolute form.
|
|
10
|
+
*/
|
|
11
11
|
export function parseStack(stack: string | undefined): Frame[] {
|
|
12
|
-
|
|
13
|
-
const lines = stack.split('\n')
|
|
14
|
-
const out: Frame[] = []
|
|
15
|
-
for (const raw of lines) {
|
|
16
|
-
const line = raw.trim()
|
|
17
|
-
if (!line) continue
|
|
18
|
-
const m = V8_RE.exec(line) ?? SPIDER_RE.exec(line)
|
|
19
|
-
if (!m?.groups) continue
|
|
20
|
-
const file = m.groups.file ?? '<anonymous>'
|
|
21
|
-
out.push({
|
|
22
|
-
absolutePath: file,
|
|
23
|
-
column: Number(m.groups.col),
|
|
24
|
-
file: shortFile(file),
|
|
25
|
-
function: m.groups.fn?.trim(),
|
|
26
|
-
inApp: !file.includes('node_modules') && !file.startsWith('node:'),
|
|
27
|
-
line: Number(m.groups.line),
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
return out
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function shortFile(absolute: string): string {
|
|
34
|
-
// Strip protocol + leading path noise so the dashboard shows
|
|
35
|
-
// e.g. "App.tsx" instead of "https://example.com/static/App.tsx".
|
|
36
|
-
const noProto = absolute.replace(/^https?:\/\/[^/]+\//, '')
|
|
37
|
-
const tail = noProto.split('/').slice(-2).join('/')
|
|
38
|
-
return tail || absolute
|
|
12
|
+
return parseStackCore(stack, { shortFilenames: true })
|
|
39
13
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,70 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Wire
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Wire-format types now live in `@goliapkg/sentori-core`. This file is
|
|
3
|
+
* kept as a thin re-export so existing relative imports inside the
|
|
4
|
+
* package continue to work. JS-specific extras (the `InitOptions`
|
|
5
|
+
* shape with `enableGlobalHooks`) are declared here.
|
|
5
6
|
*/
|
|
6
|
-
export type Event = {
|
|
7
|
-
app: { build?: string; framework?: { name: string; version: string }; version: string }
|
|
8
|
-
breadcrumbs: Breadcrumb[]
|
|
9
|
-
device: { locale?: string; model?: string; os: string; osVersion: string }
|
|
10
|
-
environment: string
|
|
11
|
-
error: SentoriError
|
|
12
|
-
fingerprint?: string[]
|
|
13
|
-
id: string
|
|
14
|
-
kind: 'error'
|
|
15
|
-
platform: 'javascript'
|
|
16
|
-
release: string
|
|
17
|
-
spanId?: null | string
|
|
18
|
-
tags?: Tags
|
|
19
|
-
timestamp: string
|
|
20
|
-
traceId?: null | string
|
|
21
|
-
user?: null | User
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type SentoriError = {
|
|
25
|
-
cause: null | SentoriError
|
|
26
|
-
message: string
|
|
27
|
-
stack: Frame[]
|
|
28
|
-
type: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type Frame = {
|
|
32
|
-
absolutePath?: string
|
|
33
|
-
column?: number
|
|
34
|
-
file: string
|
|
35
|
-
function?: string
|
|
36
|
-
inApp: boolean
|
|
37
|
-
line: number
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type BreadcrumbType = 'custom' | 'log' | 'nav' | 'net' | 'user'
|
|
41
|
-
|
|
42
|
-
export type Breadcrumb = {
|
|
43
|
-
data: Record<string, unknown>
|
|
44
|
-
timestamp: string
|
|
45
|
-
type: BreadcrumbType
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** PII-minimal — same shape as the RN SDK and the server schema. */
|
|
49
|
-
export type User = { anonymous?: boolean; id?: string }
|
|
50
|
-
|
|
51
|
-
export type Tags = Record<string, string>
|
|
52
|
-
|
|
53
|
-
export type CaptureExtras = {
|
|
54
|
-
fingerprint?: string[]
|
|
55
|
-
tags?: Tags
|
|
56
|
-
user?: User
|
|
57
|
-
}
|
|
58
7
|
|
|
59
|
-
export type
|
|
8
|
+
export type {
|
|
9
|
+
App,
|
|
10
|
+
Breadcrumb,
|
|
11
|
+
BreadcrumbType,
|
|
12
|
+
CaptureExtras,
|
|
13
|
+
Device,
|
|
14
|
+
DeviceOS,
|
|
15
|
+
Event,
|
|
16
|
+
EventKind,
|
|
17
|
+
Frame,
|
|
18
|
+
Platform,
|
|
19
|
+
SentoriError,
|
|
20
|
+
Tags,
|
|
21
|
+
User,
|
|
22
|
+
} from '@goliapkg/sentori-core'
|
|
23
|
+
|
|
24
|
+
import type { CommonInitOptions } from '@goliapkg/sentori-core'
|
|
25
|
+
|
|
26
|
+
export type InitOptions = CommonInitOptions & {
|
|
60
27
|
/** Override automatic global hooks. Default: true on browser + node. */
|
|
61
28
|
enableGlobalHooks?: boolean
|
|
62
|
-
/** "prod" / "dev" / "staging" / whatever you want. */
|
|
63
|
-
environment: string
|
|
64
|
-
/** e.g. https://ingest.sentori.golia.jp */
|
|
65
|
-
ingestUrl: string
|
|
66
|
-
/** e.g. "myapp@1.2.3+456" */
|
|
67
|
-
release: string
|
|
68
|
-
/** st_pk_<26 base32 chars> */
|
|
69
|
-
token: string
|
|
70
29
|
}
|
package/src/uuid.ts
CHANGED
|
@@ -1,59 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* v7 layout is cheap to assemble manually.
|
|
5
|
-
*
|
|
6
|
-
* Layout (RFC 9562 v7):
|
|
7
|
-
* ms (48 bits) | ver=7 (4) | rand_a (12) | var=10 (2) | rand_b (62)
|
|
8
|
-
*/
|
|
9
|
-
export function uuidV7(): string {
|
|
10
|
-
const ms = Date.now()
|
|
11
|
-
const rand = new Uint8Array(10)
|
|
12
|
-
cryptoRandomFill(rand)
|
|
13
|
-
|
|
14
|
-
// 6 bytes of timestamp (ms), big-endian
|
|
15
|
-
const t = new Uint8Array(6)
|
|
16
|
-
let n = ms
|
|
17
|
-
for (let i = 5; i >= 0; i--) {
|
|
18
|
-
t[i] = n & 0xff
|
|
19
|
-
n = Math.floor(n / 256)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// pack version + variant
|
|
23
|
-
rand[0] = (rand[0]! & 0x0f) | 0x70 // version 7 in high nibble of byte 6
|
|
24
|
-
rand[2] = (rand[2]! & 0x3f) | 0x80 // variant 10 in high two bits of byte 8
|
|
25
|
-
|
|
26
|
-
const bytes = new Uint8Array(16)
|
|
27
|
-
bytes.set(t, 0)
|
|
28
|
-
bytes.set(rand, 6)
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
hex(bytes.subarray(0, 4)) +
|
|
32
|
-
'-' +
|
|
33
|
-
hex(bytes.subarray(4, 6)) +
|
|
34
|
-
'-' +
|
|
35
|
-
hex(bytes.subarray(6, 8)) +
|
|
36
|
-
'-' +
|
|
37
|
-
hex(bytes.subarray(8, 10)) +
|
|
38
|
-
'-' +
|
|
39
|
-
hex(bytes.subarray(10, 16))
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function cryptoRandomFill(buf: Uint8Array): void {
|
|
44
|
-
// Browser + Node 19+ + Bun all expose globalThis.crypto.
|
|
45
|
-
const c = (globalThis as { crypto?: { getRandomValues?: (b: Uint8Array) => void } }).crypto
|
|
46
|
-
if (c?.getRandomValues) {
|
|
47
|
-
c.getRandomValues(buf)
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
// Last-resort Math.random (only hits in very old envs; entropy
|
|
51
|
-
// quality is bad but we still want a unique-ish id).
|
|
52
|
-
for (let i = 0; i < buf.length; i++) buf[i] = Math.floor(Math.random() * 256)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function hex(b: Uint8Array): string {
|
|
56
|
-
let s = ''
|
|
57
|
-
for (const x of b) s += x.toString(16).padStart(2, '0')
|
|
58
|
-
return s
|
|
59
|
-
}
|
|
1
|
+
// Phase 21: moved to @goliapkg/sentori-core. Kept as a re-export so
|
|
2
|
+
// internal imports don't churn.
|
|
3
|
+
export { uuidV7 } from '@goliapkg/sentori-core'
|