@lark-sentry/core 1.0.0 → 1.0.1

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/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { Status, type IOptions, type SentryPlugin } from "@lark-sentry/types";
2
+
3
+ import { getBaseData, sentry } from "@lark-sentry/utils";
4
+
5
+ import { DEFAULT_OPTIONS } from "@lark-sentry/constants";
6
+
7
+ import { handleError } from "./src/handlers.js";
8
+ import setup from "./src/setup.js";
9
+
10
+ import { type Plugin, type ComponentPublicInstance } from "vue";
11
+ import { Component, type ErrorInfo } from "react";
12
+ import { EventType } from "@lark-sentry/types/dist/index.cjs";
13
+
14
+ function init(
15
+ options: Partial<Omit<IOptions, "dsn">> & NonNullable<Pick<IOptions, "dsn">>,
16
+ ) {
17
+ sentry.setOptions({ ...DEFAULT_OPTIONS, ...options });
18
+ const { dsn } = sentry.options;
19
+ if (dsn === "") {
20
+ console.error("[lark-sentry] dsn is empty");
21
+ return;
22
+ }
23
+ setup();
24
+ }
25
+
26
+ const vuePlugin: Plugin = (app, options: IOptions) => {
27
+ const handler = app.config.errorHandler;
28
+ app.config.errorHandler = (
29
+ err: unknown,
30
+ vueInstance: ComponentPublicInstance | null,
31
+ info: string,
32
+ ) => {
33
+ handleError({
34
+ ...getBaseData(),
35
+ type: EventType.Vue,
36
+ status: Status.Error,
37
+ extra: err,
38
+ });
39
+ if (handler) {
40
+ handler.call(null, err, vueInstance, info);
41
+ }
42
+ };
43
+ init(options);
44
+ };
45
+
46
+ class ReactErrorBoundary extends Component {
47
+ override componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
48
+ handleError({
49
+ ...getBaseData(),
50
+ type: EventType.React,
51
+ status: Status.Error,
52
+ extra: {
53
+ error,
54
+ errorInfo,
55
+ },
56
+ });
57
+ }
58
+ }
59
+
60
+ function use<T extends SentryPlugin, U = unknown>(
61
+ Plugin: new (options?: U) => T,
62
+ options?: U,
63
+ ) {
64
+ const plugin = new Plugin(options);
65
+ plugin.init();
66
+ }
67
+
68
+ export { init, vuePlugin, use, ReactErrorBoundary };
69
+
70
+ // init({ dsn: "http://localhost:3000" });
package/package.json CHANGED
@@ -1,24 +1,8 @@
1
1
  {
2
2
  "name": "@lark-sentry/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Sentry sdk core",
5
5
  "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
14
- }
15
- },
16
- "files": [
17
- "dist"
18
- ],
19
- "publishConfig": {
20
- "access": "public"
21
- },
22
6
  "keywords": [
23
7
  "sentry",
24
8
  "sdk",
@@ -27,10 +11,10 @@
27
11
  "author": "github.com/161043261",
28
12
  "license": "MIT",
29
13
  "dependencies": {
30
- "@lark-sentry/constants": "^1.0.0",
31
- "@lark-sentry/utils": "^1.0.0",
32
- "@lark-sentry/reporter": "^1.0.0",
33
- "@lark-sentry/types": "^1.0.0"
14
+ "@lark-sentry/constants": "^1.0.1",
15
+ "@lark-sentry/utils": "^1.0.1",
16
+ "@lark-sentry/reporter": "^1.0.1",
17
+ "@lark-sentry/types": "^1.0.1"
34
18
  },
35
19
  "devDependencies": {
36
20
  "@types/react": "^19.2.7",
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@lark-sentry/core",
3
+ "version": "1.0.0",
4
+ "description": "Sentry sdk core",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": ["sentry", "sdk", "core"],
21
+ "author": "github.com/161043261",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@lark-sentry/constants": "workspace:^",
25
+ "@lark-sentry/reporter": "workspace:^",
26
+ "@lark-sentry/types": "workspace:^",
27
+ "@lark-sentry/utils": "workspace:^"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^19.2.7",
31
+ "@types/react-dom": "^19.2.3",
32
+ "react": "^19.2.3",
33
+ "vue": "^3.5.26"
34
+ },
35
+ "peerDependencies": {
36
+ "react": "^19.2.3",
37
+ "vue": "^3.5.26"
38
+ }
39
+ }
@@ -0,0 +1,26 @@
1
+ import type { IBreadcrumbItem } from "@lark-sentry/types";
2
+
3
+ import { MinHeap, sentry } from "@lark-sentry/utils";
4
+
5
+ export class Breadcrumb extends MinHeap<IBreadcrumbItem> {
6
+ static #instance: Breadcrumb;
7
+
8
+ public static get instance() {
9
+ if (!Breadcrumb.#instance) {
10
+ Breadcrumb.#instance = new Breadcrumb();
11
+ }
12
+ return Breadcrumb.#instance;
13
+ }
14
+
15
+ override push(data: IBreadcrumbItem) {
16
+ const { onBeforePushBreadcrumb } = sentry.options;
17
+ if (onBeforePushBreadcrumb) {
18
+ data = onBeforePushBreadcrumb(data);
19
+ }
20
+ return super.push(data);
21
+ }
22
+ }
23
+
24
+ const breadcrumb = Breadcrumb.instance;
25
+
26
+ export default breadcrumb;
package/src/bus.ts ADDED
@@ -0,0 +1,31 @@
1
+ import {
2
+ EventType,
3
+ type IPub,
4
+ type ISub,
5
+ type TEventHandler,
6
+ } from "@lark-sentry/types";
7
+
8
+ const event2handlers = new Map<EventType, Set<TEventHandler>>();
9
+
10
+ export const pub: IPub = (type, data) => {
11
+ const handlers = event2handlers.get(type);
12
+ if (!handlers) {
13
+ return;
14
+ }
15
+ try {
16
+ for (const handler of handlers) {
17
+ handler(data);
18
+ }
19
+ } catch (err) {
20
+ console.log("[lark-sentry] error", err);
21
+ }
22
+ };
23
+
24
+ export const sub: ISub = (type, handler) => {
25
+ const handlers = event2handlers.get(type);
26
+ if (!handlers) {
27
+ event2handlers.set(type, new Set([handler]));
28
+ return;
29
+ }
30
+ handlers.add(handler);
31
+ };
@@ -0,0 +1,276 @@
1
+ import {
2
+ EventType,
3
+ HttpMethod,
4
+ HttpStatusCode,
5
+ type IHttpData,
6
+ type WithSentry,
7
+ } from "@lark-sentry/types";
8
+
9
+ import {
10
+ throttle,
11
+ sentry,
12
+ decorateProp,
13
+ getBaseData,
14
+ isExcludedApi,
15
+ } from "@lark-sentry/utils";
16
+
17
+ import { pub } from "./bus.js";
18
+
19
+ function decoratePublish(type: EventType) {
20
+ switch (type) {
21
+ case EventType.Click: {
22
+ pubClick();
23
+ break;
24
+ }
25
+
26
+ case EventType.Error: {
27
+ pubError();
28
+ break;
29
+ }
30
+
31
+ case EventType.Xhr: {
32
+ pubXhr();
33
+ break;
34
+ }
35
+
36
+ case EventType.Fetch: {
37
+ pubFetch();
38
+ break;
39
+ }
40
+
41
+ case EventType.History: {
42
+ pubHistory();
43
+ break;
44
+ }
45
+
46
+ case EventType.UnhandledRejection: {
47
+ pubUnhandledRejection();
48
+ break;
49
+ }
50
+
51
+ case EventType.HashChange: {
52
+ pubHashChange();
53
+ break;
54
+ }
55
+
56
+ case EventType.WhiteScreen: {
57
+ pubWhiteScreen();
58
+ break;
59
+ }
60
+
61
+ default: {
62
+ break;
63
+ }
64
+ }
65
+ }
66
+
67
+ function pubClick() {
68
+ const throttledPub = throttle(pub, sentry.options.clickThrottleDelay);
69
+ document.addEventListener("click", function (ctx: MouseEvent) {
70
+ // publish
71
+ throttledPub(EventType.Click, {
72
+ ...getBaseData(),
73
+ type: EventType.Click,
74
+ extra: ctx,
75
+ });
76
+ });
77
+ }
78
+
79
+ function pubError() {
80
+ globalThis.addEventListener("error", function (ctx: ErrorEvent) {
81
+ // publish
82
+ pub(EventType.Error, {
83
+ ...getBaseData(),
84
+ type: EventType.Error,
85
+ extra: ctx,
86
+ });
87
+ });
88
+ }
89
+
90
+ function pubXhr() {
91
+ type TXhrProtoOpen = (
92
+ method: string,
93
+ url: string,
94
+ async?: boolean,
95
+ ...rest: string[]
96
+ ) => void;
97
+
98
+ const xhrProto = XMLHttpRequest.prototype;
99
+
100
+ decorateProp(xhrProto, "open", (oldPropVal: TXhrProtoOpen) => {
101
+ return function (
102
+ this: WithSentry<XMLHttpRequest, IHttpData>,
103
+ method: string,
104
+ url: string,
105
+ async?: boolean,
106
+ ...rest: string[]
107
+ ) {
108
+ const httpData: IHttpData = {
109
+ ...getBaseData(),
110
+ name: "XMLHttpRequest",
111
+ type: EventType.Xhr,
112
+ method: method.toUpperCase(),
113
+ api: url,
114
+ elapsedTime: 0,
115
+ statusCode: HttpStatusCode.OK,
116
+ };
117
+ this.__sentry__ = httpData;
118
+ return oldPropVal.call(this, method, url, async, ...rest);
119
+ };
120
+ });
121
+
122
+ decorateProp(xhrProto, "send", (oldPropVal) => {
123
+ return function (
124
+ this: WithSentry<XMLHttpRequest, IHttpData>,
125
+ body?: Document | XMLHttpRequestBodyInit | null | undefined,
126
+ ) {
127
+ const { method, api } = this.__sentry__;
128
+ this.addEventListener("loadend", () => {
129
+ if (
130
+ (method.toUpperCase() === HttpMethod.Post &&
131
+ api === sentry.options.dsn) ||
132
+ isExcludedApi(api)
133
+ ) {
134
+ return;
135
+ }
136
+ const { status, responseType, response } = this;
137
+ this.__sentry__.statusCode = status;
138
+ this.__sentry__.requestData = { body };
139
+ this.__sentry__.responseData = {
140
+ responseType,
141
+ response,
142
+ };
143
+ const endTime = Date.now();
144
+ this.__sentry__.elapsedTime = endTime - this.__sentry__.timestamp;
145
+
146
+ // publish
147
+ pub(EventType.Xhr, this.__sentry__);
148
+ });
149
+ return oldPropVal.call(this, body);
150
+ };
151
+ });
152
+ }
153
+
154
+ function pubFetch() {
155
+ decorateProp(globalThis, "fetch", (oldPropVal) => {
156
+ return async function (
157
+ url: RequestInfo | URL,
158
+ options?: RequestInit | undefined,
159
+ ) {
160
+ const method = options?.method?.toUpperCase() ?? HttpMethod.Get;
161
+ const httpData: IHttpData = {
162
+ ...getBaseData(),
163
+ type: EventType.Fetch,
164
+ method,
165
+ requestData: { body: options?.body },
166
+ name: "Fetch",
167
+ api: url.toString(),
168
+ elapsedTime: 0,
169
+ statusCode: HttpStatusCode.OK,
170
+ };
171
+ return oldPropVal.call(globalThis, url, options).then((res: Response) => {
172
+ const resClone = res.clone();
173
+ const endTime = Date.now();
174
+ httpData.elapsedTime = endTime - httpData.timestamp;
175
+ httpData.statusCode = resClone.status;
176
+ resClone.text().then((res: string) => {
177
+ if (
178
+ (method === HttpMethod.Post &&
179
+ url.toString() === sentry.options.dsn) ||
180
+ isExcludedApi(url.toString())
181
+ ) {
182
+ return;
183
+ }
184
+ httpData.responseData = res;
185
+
186
+ // publish
187
+ pub(EventType.Fetch, httpData);
188
+ });
189
+ return res;
190
+ });
191
+ };
192
+ });
193
+ }
194
+
195
+ let latestHref = document.location.href;
196
+ function pubHistory() {
197
+ const oldOnpopstate = globalThis.onpopstate;
198
+ if (typeof oldOnpopstate !== "function") {
199
+ return;
200
+ }
201
+
202
+ globalThis.onpopstate = function (this: Window, ev: PopStateEvent) {
203
+ const from = latestHref;
204
+ const to = document.location.href;
205
+ latestHref = to;
206
+ pub(EventType.History, {
207
+ ...getBaseData(),
208
+ type: EventType.History,
209
+ from,
210
+ to,
211
+ });
212
+ return oldOnpopstate.call(this, ev);
213
+ };
214
+
215
+ const historyDecorator = (oldPropsVal: History["pushState"]) => {
216
+ return function (
217
+ this: History,
218
+ data: unknown,
219
+ unused: string,
220
+ url?: string | URL | null,
221
+ ) {
222
+ if (url) {
223
+ const from = latestHref;
224
+ const to = url.toString();
225
+ latestHref = to;
226
+
227
+ // publish
228
+ pub(EventType.History, {
229
+ ...getBaseData(),
230
+ type: EventType.History,
231
+ from,
232
+ to,
233
+ });
234
+ }
235
+ return oldPropsVal.call(this, data, unused, url);
236
+ };
237
+ };
238
+ decorateProp(globalThis.history, "pushState", historyDecorator);
239
+ decorateProp(globalThis.history, "replaceState", historyDecorator);
240
+ }
241
+
242
+ function pubUnhandledRejection() {
243
+ globalThis.addEventListener(
244
+ "unhandledrejection",
245
+ function (ctx: PromiseRejectionEvent) {
246
+ // publish
247
+ pub(EventType.UnhandledRejection, {
248
+ ...getBaseData(),
249
+ type: EventType.UnhandledRejection,
250
+ extra: ctx,
251
+ });
252
+ },
253
+ );
254
+ }
255
+
256
+ function pubHashChange() {
257
+ globalThis.addEventListener("hashchange", function (ctx: HashChangeEvent) {
258
+ // publish
259
+ pub(EventType.HashChange, {
260
+ ...getBaseData(),
261
+ type: EventType.HashChange,
262
+ extra: ctx,
263
+ });
264
+ });
265
+ }
266
+
267
+ function pubWhiteScreen() {
268
+ // publish
269
+ pub(EventType.WhiteScreen, {
270
+ ...getBaseData(),
271
+ type: EventType.WhiteScreen,
272
+ extra: "WhiteScreen",
273
+ });
274
+ }
275
+
276
+ export default decoratePublish;
@@ -0,0 +1,230 @@
1
+ import {
2
+ EventType,
3
+ Status,
4
+ type IReportPayload,
5
+ type ICodeError,
6
+ type IHttpData,
7
+ type IRouteData,
8
+ type IResourceError,
9
+ type TEventHandler,
10
+ type IBaseDataWithEvent,
11
+ } from "@lark-sentry/types";
12
+
13
+ import {
14
+ sentry,
15
+ getBaseData,
16
+ event2breadcrumb,
17
+ base64v2,
18
+ transformHttpData,
19
+ dom2str,
20
+ isIExtendedErrorEvent,
21
+ isErrorEvent,
22
+ isError,
23
+ } from "@lark-sentry/utils";
24
+
25
+ import { UNKNOWN } from "@lark-sentry/constants";
26
+
27
+ import reporter from "@lark-sentry/reporter";
28
+
29
+ import breadcrumb from "./breadcrumb.js";
30
+ import checkWhiteScreen from "./white-screen.js";
31
+
32
+ const handleHttp: TEventHandler<IHttpData> = (data: IHttpData) => {
33
+ data = transformHttpData(data);
34
+ const { id, name, time, timestamp, message, status, type } = data;
35
+ if (!data.api.includes(sentry.options.dsn)) {
36
+ breadcrumb.push({
37
+ id,
38
+ name,
39
+ time,
40
+ timestamp,
41
+ message,
42
+ status,
43
+ type,
44
+ userAction: event2breadcrumb(type),
45
+ });
46
+ }
47
+ if (status === Status.Error) {
48
+ reporter.send(data);
49
+ }
50
+ };
51
+
52
+ const handleError: TEventHandler<IBaseDataWithEvent> = ({
53
+ extra: err,
54
+ ...rest
55
+ }) => {
56
+ if (isErrorEvent(err)) {
57
+ handleCodeError(err);
58
+ }
59
+
60
+ if (isIExtendedErrorEvent(err)) {
61
+ const { localName, src, href } = err.target;
62
+ const { message } = err;
63
+ const resourceError: IResourceError = {
64
+ ...rest,
65
+ type: EventType.Resource,
66
+ status: Status.Error,
67
+ name: localName,
68
+ src,
69
+ href,
70
+ message,
71
+ };
72
+ breadcrumb.push({
73
+ ...resourceError,
74
+ userAction: event2breadcrumb(EventType.Resource),
75
+ });
76
+ reporter.send(resourceError);
77
+ return;
78
+ }
79
+
80
+ if (isError(err)) {
81
+ const { name, message } = err;
82
+ const data: IBaseDataWithEvent = {
83
+ ...rest,
84
+ type: EventType.Error,
85
+ name,
86
+ message,
87
+ status: Status.Error,
88
+ extra: err,
89
+ };
90
+ breadcrumb.push({
91
+ ...data,
92
+ userAction: event2breadcrumb(EventType.Error),
93
+ });
94
+ reporter.send(data);
95
+ return;
96
+ }
97
+
98
+ // Unknown error
99
+ const data: IBaseDataWithEvent = {
100
+ ...rest,
101
+ type: EventType.Error,
102
+ name: "Unknown Error",
103
+ message: JSON.stringify(err),
104
+ status: Status.Error,
105
+ extra: err,
106
+ };
107
+ breadcrumb.push({
108
+ ...data,
109
+ userAction: event2breadcrumb(EventType.Error),
110
+ });
111
+ reporter.send(data);
112
+ };
113
+
114
+ const handleHistory: TEventHandler<IRouteData> = ({
115
+ from,
116
+ to,
117
+ ...rest
118
+ }: IRouteData) => {
119
+ const routeChange = `${from} => ${to}`;
120
+ const routeData: IRouteData = {
121
+ ...rest,
122
+ name: routeChange,
123
+ message: routeChange,
124
+ type: EventType.History,
125
+ from,
126
+ to,
127
+ };
128
+ breadcrumb.push({
129
+ ...routeData,
130
+ userAction: event2breadcrumb(EventType.History),
131
+ });
132
+ };
133
+
134
+ const handleHashChange: TEventHandler<IBaseDataWithEvent> = ({
135
+ extra,
136
+ ...rest
137
+ }: IBaseDataWithEvent) => {
138
+ const { oldURL: from = UNKNOWN, newURL: to = UNKNOWN } =
139
+ extra as HashChangeEvent;
140
+ const pathChange = `${from} => ${to}`;
141
+ const routeData: IRouteData = {
142
+ ...rest,
143
+ name: pathChange,
144
+ message: pathChange,
145
+ type: EventType.HashChange,
146
+ from,
147
+ to,
148
+ };
149
+ breadcrumb.push({
150
+ ...routeData,
151
+ userAction: event2breadcrumb(EventType.HashChange),
152
+ });
153
+ };
154
+
155
+ const handleUnhandledRejection: TEventHandler<IBaseDataWithEvent> = (
156
+ data: IBaseDataWithEvent,
157
+ ) => {
158
+ if (!isIExtendedErrorEvent(data.extra)) {
159
+ handleError(data);
160
+ return;
161
+ }
162
+ handleCodeError(data.extra);
163
+ };
164
+
165
+ const handleWhiteScreen: TEventHandler<IBaseDataWithEvent> = (
166
+ data: IBaseDataWithEvent,
167
+ ) => {
168
+ checkWhiteScreen(() => {
169
+ reporter.send(data);
170
+ });
171
+ return;
172
+ };
173
+
174
+ const handleClick: TEventHandler<IBaseDataWithEvent> = ({
175
+ extra,
176
+ ...rest
177
+ }: IBaseDataWithEvent) => {
178
+ const typedEvent = extra as PointerEvent;
179
+ const str =
180
+ typedEvent.target instanceof HTMLElement ? dom2str(typedEvent.target) : "";
181
+ breadcrumb.push({
182
+ ...rest,
183
+ type: EventType.Click,
184
+ name: str,
185
+ message: str,
186
+ userAction: event2breadcrumb(EventType.Click),
187
+ });
188
+ };
189
+
190
+ export {
191
+ handleError,
192
+ handleHistory,
193
+ handleHashChange,
194
+ handleHttp,
195
+ handleUnhandledRejection,
196
+ handleWhiteScreen,
197
+ handleClick,
198
+ };
199
+
200
+ const handleCodeError = (err: ErrorEvent) => {
201
+ const { filename, colno: column, lineno: line, message } = err;
202
+ const data: IReportPayload = {
203
+ ...getBaseData(),
204
+ type: EventType.Error,
205
+ name: filename,
206
+ message,
207
+ status: Status.Error,
208
+ };
209
+ const codeError: ICodeError = {
210
+ ...data,
211
+ column,
212
+ line,
213
+ };
214
+ breadcrumb.push({
215
+ ...data,
216
+ userAction: event2breadcrumb(EventType.Error),
217
+ });
218
+
219
+ const errorId = base64v2(
220
+ `${EventType.Error}-${message}-${filename}-${line}-${column}`,
221
+ );
222
+ if (
223
+ errorId.includes(UNKNOWN) ||
224
+ sentry.options.repeatCodeError ||
225
+ (!sentry.options.repeatCodeError && !sentry.codeErrors.has(errorId))
226
+ ) {
227
+ sentry.codeErrors.add(errorId);
228
+ reporter.send(codeError);
229
+ }
230
+ };