@nebulaaa/sentry 0.0.0-stage → 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/README.md +127 -2
- package/dist/package.json +35 -0
- package/dist/sentry.cjs +219 -0
- package/dist/sentry.d.ts +125 -0
- package/dist/sentry.min.cjs +1 -0
- package/dist/sentry.min.mjs +1 -0
- package/dist/sentry.mjs +217 -0
- package/package.json +73 -4
package/README.md
CHANGED
|
@@ -1,3 +1,128 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nebulaaa/sentry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
哨兵(异常监控)插件:捕获全局 JavaScript 错误、未处理的 Promise 拒绝与原生错误,统一转发给 `@nebulaaa/reporter` 上报。
|
|
4
|
+
|
|
5
|
+
## 设计概览
|
|
6
|
+
|
|
7
|
+
- **三通道捕获**:Web 走 `window` 的 `error` / `unhandledrejection` 事件;原生走 jsb 运行时的 `__errorHandler` 链式接管;小游戏平台当前无覆盖(见注意事项)。
|
|
8
|
+
- **跨 realm 安全**:拒绝原因可能非 `Error`(跨 realm 实例、字符串、任意对象),序列化逐级回退:`message` 属性 → `String` → `JSON.stringify` → `Object.prototype.toString`,任何一级抛错都不逃逸出事件监听器。
|
|
9
|
+
- **链式兼容**:原生处理器接管 `__errorHandler` 前先保存旧处理器,捕获后原样回调,不破坏宿主既有链路。
|
|
10
|
+
- **监控组件底线**:自身故障(监听器抛错、写入失败)不得拖垮宿主;捕获计数与转发双层防御,开发/测试环境只记日志不上报。
|
|
11
|
+
- **依赖收窄**:仅依赖 `@nebulaaa/access`(全局访问)、`@nebulaaa/reporter`(上报)、`@nebulaaa/launcher`(环境守卫)与 `@nebulaaa/ms`(时间戳)。
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @nebulaaa/sentry
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
依赖 `@nebulaaa/reporter` 建议 1.2.0 及以上(含切后台落盘、优先级排序等修复)。
|
|
20
|
+
|
|
21
|
+
## 基本用法
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { SentryPlugin } from '@nebulaaa/sentry';
|
|
25
|
+
|
|
26
|
+
const sentry = new SentryPlugin(
|
|
27
|
+
{ Access: access, Reporter: reporter, Launcher: launcher },
|
|
28
|
+
{},
|
|
29
|
+
);
|
|
30
|
+
// 无需宿主接线:构造时自动注册全局错误监听(Web)或接管 __errorHandler(原生)
|
|
31
|
+
|
|
32
|
+
// 查看捕获统计(快照副本)
|
|
33
|
+
sentry.Stats; // { Total, Javascript, Promise, Native }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> **单例约束**:插件构造时向全局注册监听且无卸载钩子,整个应用生命周期内仅应实例化一次;重复实例化会导致监听重复注册、原生错误处理器链式叠加。
|
|
37
|
+
|
|
38
|
+
## 捕获通道
|
|
39
|
+
|
|
40
|
+
### Web(window 事件)
|
|
41
|
+
|
|
42
|
+
- `error` → `SentryType.JAVASCRIPT`:提取 `message` / `error.stack` / `filename` / `lineno` / `colno`。
|
|
43
|
+
- `unhandledrejection` → `SentryType.PROMISE`:按 `Error` / `string` / 其它对象分别提取消息与堆栈。
|
|
44
|
+
|
|
45
|
+
实现说明:`addEventListener` 位于 `EventTarget.prototype`(非全局自有属性),`@access` 委托的自有属性语义(`hasOwnProperty`)不可达,故本插件直接访问 `globalThis` 并 `bind` 接收者——剥离调用在 ESM 严格模式下会因 `this` 为 `undefined` 抛 `Illegal invocation`。
|
|
46
|
+
|
|
47
|
+
### 原生(`__errorHandler` 链式接管)
|
|
48
|
+
|
|
49
|
+
- `sys.isNative` 时先读旧 `__errorHandler`(非函数则不参与链式),再写入本插件处理器;捕获后**先**原样回调旧处理器(保持宿主链路时序),**再**构造载荷转发。
|
|
50
|
+
- 全局写入失败(`Set` 返回 `false`)时回退链引用并 `Warn`——旧处理器原位不受影响,本插件原生捕获标记为未生效。
|
|
51
|
+
|
|
52
|
+
## 公共接口
|
|
53
|
+
|
|
54
|
+
### `SentryPlugin`
|
|
55
|
+
|
|
56
|
+
| 成员 | 签名 | 说明 |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `constructor` | `(deps: SentryDeps, cfg: SentryCfg)` | 构造即注册全局捕获 |
|
|
59
|
+
| `Stats` | `get Stats(): SentryStats` | 捕获统计(快照副本,消费方不可改内部状态) |
|
|
60
|
+
|
|
61
|
+
### 类型
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
enum SentryType {
|
|
65
|
+
JAVASCRIPT = 'JAVASCRIPT',
|
|
66
|
+
PROMISE = 'PROMISE',
|
|
67
|
+
NATIVE = 'NATIVE',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface SentryPayload {
|
|
71
|
+
Type: SentryType;
|
|
72
|
+
Code: string; // JAVASCRIPT_ERROR / PROMISE_REJECTION / NATIVE_ERROR
|
|
73
|
+
Message: string;
|
|
74
|
+
Timestamp: number;
|
|
75
|
+
Stack?: string;
|
|
76
|
+
Data?: Record<string, unknown>; // 预留字段,本插件当前不填充
|
|
77
|
+
Filename?: string;
|
|
78
|
+
Lineno?: number;
|
|
79
|
+
Colno?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface SentryStats {
|
|
83
|
+
Total: number; // 捕获总数(含开发环境只记日志的捕获)
|
|
84
|
+
Javascript: number;
|
|
85
|
+
Promise: number;
|
|
86
|
+
Native: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface SentryDeps extends PluginDeps {
|
|
90
|
+
Access: IAccessPlugin; // 全局对象访问
|
|
91
|
+
Reporter: IReporterPlugin; // 上报中心
|
|
92
|
+
Launcher: ILauncherPlugin; // 环境守卫(IsDev / IsAlpha)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface SentryCfg extends PluginCfg {} // 当前无可配置项
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 转发语义
|
|
99
|
+
|
|
100
|
+
捕获成功后(所有通道统一走 `sendToReporter`):
|
|
101
|
+
|
|
102
|
+
1. `Logger.Error('捕获到异常', payload)`——任何环境都记录,保留本地排查线索;
|
|
103
|
+
2. `IsDev || IsAlpha` 环境直接返回(与 `@reporter.Collect` 的守卫对齐,双层防御);
|
|
104
|
+
3. 其余环境 `Reporter.Collect(ReporterType.ERROR, code, message, payload)`,由 reporter 负责攒批、重试与送达。
|
|
105
|
+
|
|
106
|
+
`Stats` 在捕获时就递增(含开发环境),因此统计口径是**捕获数**而非**上报数**。
|
|
107
|
+
|
|
108
|
+
## 注意事项
|
|
109
|
+
|
|
110
|
+
- **单例约束**:见"基本用法",重复实例化会叠加全局监听与原生处理器链。
|
|
111
|
+
- **小游戏平台无覆盖**:微信/抖音等小游戏全局对象无 `addEventListener` 且 `sys.isNative` 为 `false`,两个捕获通道均不生效;如需支持请接入对应平台的 `onError` / `onUnhandledRejection` API。
|
|
112
|
+
- **跨源脚本错误**:浏览器安全策略下跨域脚本的 `error` 事件 `error` 为 `null`、`message` 为 `Script error.`,需服务端响应 `Cross-Origin-Resource-Policy` / 脚本 `crossorigin` 属性配合才能拿到完整堆栈。
|
|
113
|
+
- **拒绝原因序列化**:跨 realm `Error` 不是 `instanceof Error`,经鸭子类型提取 `message` / `stack`;自带 `message` 读取器抛错、`String`/`JSON` 转换抛错均有末级回退,不会逃逸。
|
|
114
|
+
- **Data 字段**:`SentryPayload.Data` 为对外保留的扩展位,本插件当前不填充。
|
|
115
|
+
|
|
116
|
+
## 构建与测试
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm run build
|
|
120
|
+
npm test
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
在仓库内 `@sentry` 目录下也可直接运行:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx tsc --noEmit -p tsconfig.json
|
|
127
|
+
npx jest --config jest.config.mjs --passWithNoTests --runInBand
|
|
128
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebulaaa/sentry",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "哨兵(异常监控)插件",
|
|
5
|
+
"author": "doooreyn",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"sentry",
|
|
9
|
+
"nebulaaa"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=16.0.0",
|
|
13
|
+
"npm": ">=10.0.0"
|
|
14
|
+
},
|
|
15
|
+
"main": "./sentry.cjs",
|
|
16
|
+
"module": "./sentry.mjs",
|
|
17
|
+
"types": "./sentry.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./sentry.d.ts",
|
|
21
|
+
"require": "./sentry.cjs",
|
|
22
|
+
"import": "./sentry.mjs",
|
|
23
|
+
"default": "./sentry.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./min": {
|
|
26
|
+
"types": "./sentry.d.ts",
|
|
27
|
+
"require": "./sentry.min.cjs",
|
|
28
|
+
"import": "./sentry.min.mjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"registry": "https://registry.npmjs.org/",
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/dist/sentry.cjs
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var cc = require('cc');
|
|
4
|
+
var ms = require('@nebulaaa/ms');
|
|
5
|
+
var plugin = require('@nebulaaa/plugin');
|
|
6
|
+
var reporter = require('@nebulaaa/reporter');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 异常类型
|
|
10
|
+
*/
|
|
11
|
+
exports.SentryType = void 0;
|
|
12
|
+
(function (SentryType) {
|
|
13
|
+
/** JavaScript 错误 */
|
|
14
|
+
SentryType["JAVASCRIPT"] = "JAVASCRIPT";
|
|
15
|
+
/** Promise 拒绝 */
|
|
16
|
+
SentryType["PROMISE"] = "PROMISE";
|
|
17
|
+
/** 原生错误 */
|
|
18
|
+
SentryType["NATIVE"] = "NATIVE";
|
|
19
|
+
})(exports.SentryType || (exports.SentryType = {}));
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 哨兵能力实现
|
|
23
|
+
*
|
|
24
|
+
* 职责:
|
|
25
|
+
* - 捕获全局 JavaScript 错误
|
|
26
|
+
* - 捕获未处理的 Promise 拒绝
|
|
27
|
+
* - 通知上报中心报告异常
|
|
28
|
+
*
|
|
29
|
+
* @tip 单例组件:构造时向全局对象注册错误监听(Web)或改写 __errorHandler(原生),
|
|
30
|
+
* 且无卸载钩子,整个应用生命周期内仅应实例化一次;
|
|
31
|
+
* 重复实例化会导致监听重复注册、原生错误处理器链式叠加。
|
|
32
|
+
* @tip 平台覆盖:Web 走 window 事件、原生走 __errorHandler;
|
|
33
|
+
* 小游戏平台(微信/抖音等,全局对象无 addEventListener 且 sys.isNative 为 false)
|
|
34
|
+
* 当前无捕获覆盖,如需支持请接入对应平台 API。
|
|
35
|
+
*/
|
|
36
|
+
class SentryPlugin extends plugin.Plugin {
|
|
37
|
+
constructor(deps, cfg) {
|
|
38
|
+
super(deps, cfg);
|
|
39
|
+
this._stats = {
|
|
40
|
+
Total: 0,
|
|
41
|
+
Javascript: 0,
|
|
42
|
+
Promise: 0,
|
|
43
|
+
Native: 0,
|
|
44
|
+
};
|
|
45
|
+
this._jsErrorHandler = null;
|
|
46
|
+
this._promiseRejectionHandler = null;
|
|
47
|
+
this._nativeErrorHandler = null;
|
|
48
|
+
const global = this.Deps.Access.Global;
|
|
49
|
+
// Web 捕获:addEventListener 位于 EventTarget.prototype(非全局自有属性),
|
|
50
|
+
// Access.Delegate.Has/Get 的自有属性语义取不到(实测浏览器 hasOwnProperty 为 false),
|
|
51
|
+
// 故此处直接访问 globalThis 并绑定接收者——剥离调用在 ESM 严格模式下
|
|
52
|
+
// 因 this 为 undefined 抛 Illegal invocation
|
|
53
|
+
if (typeof globalThis.addEventListener === 'function') {
|
|
54
|
+
const addEventListener = globalThis.addEventListener.bind(globalThis);
|
|
55
|
+
// 注册全局错误处理器和 Promise 拒绝处理器
|
|
56
|
+
this._jsErrorHandler = this.handleJavaScriptError.bind(this);
|
|
57
|
+
this._promiseRejectionHandler = this.handlePromiseRejection.bind(this);
|
|
58
|
+
addEventListener('error', this._jsErrorHandler);
|
|
59
|
+
addEventListener('unhandledrejection', this._promiseRejectionHandler);
|
|
60
|
+
}
|
|
61
|
+
if (cc.sys.isNative) {
|
|
62
|
+
// 注册原生错误处理器:__errorHandler 由 jsb 运行时以自有属性注入,Access 语义可用;
|
|
63
|
+
// 原有处理器必须为函数才参与链式调用,避免错误发生时才抛 TypeError
|
|
64
|
+
const legacy = global.Get('__errorHandler');
|
|
65
|
+
this._nativeErrorHandler =
|
|
66
|
+
typeof legacy === 'function' ? legacy : null;
|
|
67
|
+
// Set 失败(返回 false)说明全局写入被拒:旧处理器保持原位不受影响,
|
|
68
|
+
// 但本插件的原生捕获未生效,回退链引用并告警
|
|
69
|
+
if (!global.Set('__errorHandler', this.handleNativeError.bind(this))) {
|
|
70
|
+
this._nativeErrorHandler = null;
|
|
71
|
+
this.Logger.Warn('原生错误处理器写入失败,原生捕获未生效');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 处理 JavaScript 错误
|
|
77
|
+
*/
|
|
78
|
+
handleJavaScriptError(event) {
|
|
79
|
+
var _a;
|
|
80
|
+
this._stats.Total++;
|
|
81
|
+
this._stats.Javascript++;
|
|
82
|
+
const error = {
|
|
83
|
+
Type: exports.SentryType.JAVASCRIPT,
|
|
84
|
+
Code: 'JAVASCRIPT_ERROR',
|
|
85
|
+
Message: event.message || 'Unknown error',
|
|
86
|
+
Timestamp: ms.Now(),
|
|
87
|
+
Stack: (_a = event.error) === null || _a === void 0 ? void 0 : _a.stack,
|
|
88
|
+
Filename: event.filename,
|
|
89
|
+
Lineno: event.lineno,
|
|
90
|
+
Colno: event.colno,
|
|
91
|
+
};
|
|
92
|
+
this.sendToReporter(error);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 处理 Promise 拒绝
|
|
96
|
+
*/
|
|
97
|
+
handlePromiseRejection(event) {
|
|
98
|
+
this._stats.Total++;
|
|
99
|
+
this._stats.Promise++;
|
|
100
|
+
const { reason } = event;
|
|
101
|
+
let message;
|
|
102
|
+
let stack;
|
|
103
|
+
if (reason instanceof Error) {
|
|
104
|
+
const { message: _message, stack: _stack } = reason;
|
|
105
|
+
message = _message;
|
|
106
|
+
stack = _stack;
|
|
107
|
+
}
|
|
108
|
+
else if (typeof reason === 'string') {
|
|
109
|
+
message = reason;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
message = this.stringifyReason(reason);
|
|
113
|
+
stack = this.extractStack(reason);
|
|
114
|
+
}
|
|
115
|
+
const error = {
|
|
116
|
+
Type: exports.SentryType.PROMISE,
|
|
117
|
+
Code: 'PROMISE_REJECTION',
|
|
118
|
+
Message: message,
|
|
119
|
+
Timestamp: ms.Now(),
|
|
120
|
+
Stack: stack,
|
|
121
|
+
};
|
|
122
|
+
this.sendToReporter(error);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 安全序列化拒绝原因
|
|
126
|
+
*
|
|
127
|
+
* @tip String(reason) 可因 toPrimitive/toString 抛出异常(异常将从事件监听器逃逸,
|
|
128
|
+
* 造成计数与转发不一致);普通对象还会退化为无信息量的 '[object Object]'。
|
|
129
|
+
* 故逐级回退:message 属性 → String → JSON.stringify → Object.prototype.toString
|
|
130
|
+
*/
|
|
131
|
+
stringifyReason(reason) {
|
|
132
|
+
// 跨 realm Error 实例 / 自带 message 的对象,优先提取 message
|
|
133
|
+
if (reason !== null && typeof reason === 'object') {
|
|
134
|
+
try {
|
|
135
|
+
const { message } = reason;
|
|
136
|
+
if (typeof message === 'string' && message.length > 0) {
|
|
137
|
+
return message;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
// message 读取器抛错,走后续回退
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// String 可抛(toPrimitive/toString);结果为 '[object Object]' 时无信息量
|
|
145
|
+
try {
|
|
146
|
+
const text = String(reason);
|
|
147
|
+
if (text !== '[object Object]') {
|
|
148
|
+
return text;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
// 忽略,走后续回退
|
|
153
|
+
}
|
|
154
|
+
// JSON 可抛(循环引用 / toJSON 抛错)
|
|
155
|
+
try {
|
|
156
|
+
const json = JSON.stringify(reason);
|
|
157
|
+
if (typeof json === 'string') {
|
|
158
|
+
return json;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
// 忽略,走末级回退
|
|
163
|
+
}
|
|
164
|
+
return Object.prototype.toString.call(reason);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 提取 Error 形态对象的堆栈
|
|
168
|
+
* @tip 跨 realm Error 实例不是 instanceof Error,其 message/stack 属性原样可取
|
|
169
|
+
*/
|
|
170
|
+
extractStack(reason) {
|
|
171
|
+
if (reason !== null && typeof reason === 'object') {
|
|
172
|
+
try {
|
|
173
|
+
const { stack } = reason;
|
|
174
|
+
if (typeof stack === 'string') {
|
|
175
|
+
return stack;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
// stack 读取器抛错,保持 undefined
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 处理原生错误
|
|
186
|
+
*/
|
|
187
|
+
handleNativeError(name, line, msg, stack) {
|
|
188
|
+
this._stats.Total++;
|
|
189
|
+
this._stats.Native++;
|
|
190
|
+
if (typeof this._nativeErrorHandler === 'function') {
|
|
191
|
+
this._nativeErrorHandler(name, line, msg, stack);
|
|
192
|
+
}
|
|
193
|
+
const error = {
|
|
194
|
+
Type: exports.SentryType.NATIVE,
|
|
195
|
+
Code: 'NATIVE_ERROR',
|
|
196
|
+
Message: msg,
|
|
197
|
+
Filename: name,
|
|
198
|
+
Lineno: line,
|
|
199
|
+
Timestamp: ms.Now(),
|
|
200
|
+
Stack: stack,
|
|
201
|
+
};
|
|
202
|
+
this.sendToReporter(error);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 报告给记者
|
|
206
|
+
*/
|
|
207
|
+
sendToReporter(error) {
|
|
208
|
+
this.Logger.Error('捕获到异常', error);
|
|
209
|
+
// 开发、测试环境不上报(与 @reporter.Collect 的 IsDev||IsAlpha 守卫对齐,双层防御)
|
|
210
|
+
if (this.Deps.Launcher.IsDev || this.Deps.Launcher.IsAlpha)
|
|
211
|
+
return;
|
|
212
|
+
this.Deps.Reporter.Collect(reporter.ReporterType.ERROR, error.Code, error.Message, Object.assign({}, error));
|
|
213
|
+
}
|
|
214
|
+
get Stats() {
|
|
215
|
+
return Object.assign({}, this._stats);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
exports.SentryPlugin = SentryPlugin;
|
package/dist/sentry.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { IAccessPlugin } from '@nebulaaa/access';
|
|
2
|
+
import { ILauncherPlugin } from '@nebulaaa/launcher';
|
|
3
|
+
import { IPlugin, PluginDeps, PluginCfg, Plugin } from '@nebulaaa/plugin';
|
|
4
|
+
import { IReporterPlugin } from '@nebulaaa/reporter';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 异常类型
|
|
8
|
+
*/
|
|
9
|
+
declare enum SentryType {
|
|
10
|
+
/** JavaScript 错误 */
|
|
11
|
+
JAVASCRIPT = "JAVASCRIPT",
|
|
12
|
+
/** Promise 拒绝 */
|
|
13
|
+
PROMISE = "PROMISE",
|
|
14
|
+
/** 原生错误 */
|
|
15
|
+
NATIVE = "NATIVE"
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 异常数据
|
|
19
|
+
*/
|
|
20
|
+
interface SentryPayload {
|
|
21
|
+
/** 异常类型 */
|
|
22
|
+
Type: SentryType;
|
|
23
|
+
/** 异常代码 */
|
|
24
|
+
Code: string;
|
|
25
|
+
/** 异常消息 */
|
|
26
|
+
Message: string;
|
|
27
|
+
/** 异常时间戳 */
|
|
28
|
+
Timestamp: number;
|
|
29
|
+
/** 异常堆栈 */
|
|
30
|
+
Stack?: string;
|
|
31
|
+
/** 额外数据 */
|
|
32
|
+
Data?: Record<string, unknown>;
|
|
33
|
+
/** 文件名 */
|
|
34
|
+
Filename?: string;
|
|
35
|
+
/** 行号 */
|
|
36
|
+
Lineno?: number;
|
|
37
|
+
/** 列号 */
|
|
38
|
+
Colno?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 哨兵统计
|
|
42
|
+
*/
|
|
43
|
+
interface SentryStats {
|
|
44
|
+
/** 捕获的错误总数 */
|
|
45
|
+
Total: number;
|
|
46
|
+
/** 捕获的 JavaScript 错误总数 */
|
|
47
|
+
Javascript: number;
|
|
48
|
+
/** 捕获的 Promise 拒绝总数 */
|
|
49
|
+
Promise: number;
|
|
50
|
+
/** 捕获的原生错误总数 */
|
|
51
|
+
Native: number;
|
|
52
|
+
}
|
|
53
|
+
/** 哨兵插件依赖 */
|
|
54
|
+
interface SentryDeps extends PluginDeps {
|
|
55
|
+
Access: IAccessPlugin;
|
|
56
|
+
Reporter: IReporterPlugin;
|
|
57
|
+
Launcher: ILauncherPlugin;
|
|
58
|
+
}
|
|
59
|
+
/** 哨兵插件配置 */
|
|
60
|
+
interface SentryCfg extends PluginCfg {
|
|
61
|
+
}
|
|
62
|
+
/** 哨兵插件接口 */
|
|
63
|
+
interface ISentryPlugin extends IPlugin<SentryDeps, SentryCfg> {
|
|
64
|
+
get Stats(): SentryStats;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 哨兵能力实现
|
|
69
|
+
*
|
|
70
|
+
* 职责:
|
|
71
|
+
* - 捕获全局 JavaScript 错误
|
|
72
|
+
* - 捕获未处理的 Promise 拒绝
|
|
73
|
+
* - 通知上报中心报告异常
|
|
74
|
+
*
|
|
75
|
+
* @tip 单例组件:构造时向全局对象注册错误监听(Web)或改写 __errorHandler(原生),
|
|
76
|
+
* 且无卸载钩子,整个应用生命周期内仅应实例化一次;
|
|
77
|
+
* 重复实例化会导致监听重复注册、原生错误处理器链式叠加。
|
|
78
|
+
* @tip 平台覆盖:Web 走 window 事件、原生走 __errorHandler;
|
|
79
|
+
* 小游戏平台(微信/抖音等,全局对象无 addEventListener 且 sys.isNative 为 false)
|
|
80
|
+
* 当前无捕获覆盖,如需支持请接入对应平台 API。
|
|
81
|
+
*/
|
|
82
|
+
declare class SentryPlugin extends Plugin<SentryDeps, SentryCfg> implements ISentryPlugin {
|
|
83
|
+
/** 统计 */
|
|
84
|
+
private _stats;
|
|
85
|
+
/** JavaScript 错误处理器 */
|
|
86
|
+
private _jsErrorHandler;
|
|
87
|
+
/** Promise 拒绝处理器 */
|
|
88
|
+
private _promiseRejectionHandler;
|
|
89
|
+
/** 原生错误捕获 */
|
|
90
|
+
private _nativeErrorHandler;
|
|
91
|
+
constructor(deps: SentryDeps, cfg: SentryCfg);
|
|
92
|
+
/**
|
|
93
|
+
* 处理 JavaScript 错误
|
|
94
|
+
*/
|
|
95
|
+
private handleJavaScriptError;
|
|
96
|
+
/**
|
|
97
|
+
* 处理 Promise 拒绝
|
|
98
|
+
*/
|
|
99
|
+
private handlePromiseRejection;
|
|
100
|
+
/**
|
|
101
|
+
* 安全序列化拒绝原因
|
|
102
|
+
*
|
|
103
|
+
* @tip String(reason) 可因 toPrimitive/toString 抛出异常(异常将从事件监听器逃逸,
|
|
104
|
+
* 造成计数与转发不一致);普通对象还会退化为无信息量的 '[object Object]'。
|
|
105
|
+
* 故逐级回退:message 属性 → String → JSON.stringify → Object.prototype.toString
|
|
106
|
+
*/
|
|
107
|
+
private stringifyReason;
|
|
108
|
+
/**
|
|
109
|
+
* 提取 Error 形态对象的堆栈
|
|
110
|
+
* @tip 跨 realm Error 实例不是 instanceof Error,其 message/stack 属性原样可取
|
|
111
|
+
*/
|
|
112
|
+
private extractStack;
|
|
113
|
+
/**
|
|
114
|
+
* 处理原生错误
|
|
115
|
+
*/
|
|
116
|
+
private handleNativeError;
|
|
117
|
+
/**
|
|
118
|
+
* 报告给记者
|
|
119
|
+
*/
|
|
120
|
+
private sendToReporter;
|
|
121
|
+
get Stats(): SentryStats;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { SentryPlugin, SentryType };
|
|
125
|
+
export type { ISentryPlugin, SentryCfg, SentryDeps, SentryPayload, SentryStats };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var e,t=require("cc"),r=require("@nebulaaa/ms"),s=require("@nebulaaa/plugin"),n=require("@nebulaaa/reporter");exports.SentryType=void 0,(e=exports.SentryType||(exports.SentryType={})).JAVASCRIPT="JAVASCRIPT",e.PROMISE="PROMISE",e.NATIVE="NATIVE";class i extends s.Plugin{constructor(e,r){super(e,r),this._stats={Total:0,Javascript:0,Promise:0,Native:0},this._jsErrorHandler=null,this._promiseRejectionHandler=null,this._nativeErrorHandler=null;const s=this.Deps.Access.Global;if("function"==typeof globalThis.addEventListener){const e=globalThis.addEventListener.bind(globalThis);this._jsErrorHandler=this.handleJavaScriptError.bind(this),this._promiseRejectionHandler=this.handlePromiseRejection.bind(this),e("error",this._jsErrorHandler),e("unhandledrejection",this._promiseRejectionHandler)}if(t.sys.isNative){const e=s.Get("__errorHandler");this._nativeErrorHandler="function"==typeof e?e:null,s.Set("__errorHandler",this.handleNativeError.bind(this))||(this._nativeErrorHandler=null,this.Logger.Warn("原生错误处理器写入失败,原生捕获未生效"))}}handleJavaScriptError(e){var t;this._stats.Total++,this._stats.Javascript++;const s={Type:exports.SentryType.JAVASCRIPT,Code:"JAVASCRIPT_ERROR",Message:e.message||"Unknown error",Timestamp:r.Now(),Stack:null===(t=e.error)||void 0===t?void 0:t.stack,Filename:e.filename,Lineno:e.lineno,Colno:e.colno};this.sendToReporter(s)}handlePromiseRejection(e){this._stats.Total++,this._stats.Promise++;const{reason:t}=e;let s,n;if(t instanceof Error){const{message:e,stack:r}=t;s=e,n=r}else"string"==typeof t?s=t:(s=this.stringifyReason(t),n=this.extractStack(t));const i={Type:exports.SentryType.PROMISE,Code:"PROMISE_REJECTION",Message:s,Timestamp:r.Now(),Stack:n};this.sendToReporter(i)}stringifyReason(e){if(null!==e&&"object"==typeof e)try{const{message:t}=e;if("string"==typeof t&&t.length>0)return t}catch(e){}try{const t=String(e);if("[object Object]"!==t)return t}catch(e){}try{const t=JSON.stringify(e);if("string"==typeof t)return t}catch(e){}return Object.prototype.toString.call(e)}extractStack(e){if(null!==e&&"object"==typeof e)try{const{stack:t}=e;if("string"==typeof t)return t}catch(e){}}handleNativeError(e,t,s,n){this._stats.Total++,this._stats.Native++,"function"==typeof this._nativeErrorHandler&&this._nativeErrorHandler(e,t,s,n);const i={Type:exports.SentryType.NATIVE,Code:"NATIVE_ERROR",Message:s,Filename:e,Lineno:t,Timestamp:r.Now(),Stack:n};this.sendToReporter(i)}sendToReporter(e){this.Logger.Error("捕获到异常",e),this.Deps.Launcher.IsDev||this.Deps.Launcher.IsAlpha||this.Deps.Reporter.Collect(n.ReporterType.ERROR,e.Code,e.Message,Object.assign({},e))}get Stats(){return Object.assign({},this._stats)}}exports.SentryPlugin=i;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sys as t}from"cc";import{Now as e}from"@nebulaaa/ms";import{Plugin as r}from"@nebulaaa/plugin";import{ReporterType as s}from"@nebulaaa/reporter";var n;!function(t){t.JAVASCRIPT="JAVASCRIPT",t.PROMISE="PROMISE",t.NATIVE="NATIVE"}(n||(n={}));class i extends r{constructor(e,r){super(e,r),this._stats={Total:0,Javascript:0,Promise:0,Native:0},this._jsErrorHandler=null,this._promiseRejectionHandler=null,this._nativeErrorHandler=null;const s=this.Deps.Access.Global;if("function"==typeof globalThis.addEventListener){const t=globalThis.addEventListener.bind(globalThis);this._jsErrorHandler=this.handleJavaScriptError.bind(this),this._promiseRejectionHandler=this.handlePromiseRejection.bind(this),t("error",this._jsErrorHandler),t("unhandledrejection",this._promiseRejectionHandler)}if(t.isNative){const t=s.Get("__errorHandler");this._nativeErrorHandler="function"==typeof t?t:null,s.Set("__errorHandler",this.handleNativeError.bind(this))||(this._nativeErrorHandler=null,this.Logger.Warn("原生错误处理器写入失败,原生捕获未生效"))}}handleJavaScriptError(t){var r;this._stats.Total++,this._stats.Javascript++;const s={Type:n.JAVASCRIPT,Code:"JAVASCRIPT_ERROR",Message:t.message||"Unknown error",Timestamp:e(),Stack:null===(r=t.error)||void 0===r?void 0:r.stack,Filename:t.filename,Lineno:t.lineno,Colno:t.colno};this.sendToReporter(s)}handlePromiseRejection(t){this._stats.Total++,this._stats.Promise++;const{reason:r}=t;let s,i;if(r instanceof Error){const{message:t,stack:e}=r;s=t,i=e}else"string"==typeof r?s=r:(s=this.stringifyReason(r),i=this.extractStack(r));const o={Type:n.PROMISE,Code:"PROMISE_REJECTION",Message:s,Timestamp:e(),Stack:i};this.sendToReporter(o)}stringifyReason(t){if(null!==t&&"object"==typeof t)try{const{message:e}=t;if("string"==typeof e&&e.length>0)return e}catch(t){}try{const e=String(t);if("[object Object]"!==e)return e}catch(t){}try{const e=JSON.stringify(t);if("string"==typeof e)return e}catch(t){}return Object.prototype.toString.call(t)}extractStack(t){if(null!==t&&"object"==typeof t)try{const{stack:e}=t;if("string"==typeof e)return e}catch(t){}}handleNativeError(t,r,s,i){this._stats.Total++,this._stats.Native++,"function"==typeof this._nativeErrorHandler&&this._nativeErrorHandler(t,r,s,i);const o={Type:n.NATIVE,Code:"NATIVE_ERROR",Message:s,Filename:t,Lineno:r,Timestamp:e(),Stack:i};this.sendToReporter(o)}sendToReporter(t){this.Logger.Error("捕获到异常",t),this.Deps.Launcher.IsDev||this.Deps.Launcher.IsAlpha||this.Deps.Reporter.Collect(s.ERROR,t.Code,t.Message,Object.assign({},t))}get Stats(){return Object.assign({},this._stats)}}export{i as SentryPlugin,n as SentryType};
|
package/dist/sentry.mjs
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { sys } from 'cc';
|
|
2
|
+
import { Now } from '@nebulaaa/ms';
|
|
3
|
+
import { Plugin } from '@nebulaaa/plugin';
|
|
4
|
+
import { ReporterType } from '@nebulaaa/reporter';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 异常类型
|
|
8
|
+
*/
|
|
9
|
+
var SentryType;
|
|
10
|
+
(function (SentryType) {
|
|
11
|
+
/** JavaScript 错误 */
|
|
12
|
+
SentryType["JAVASCRIPT"] = "JAVASCRIPT";
|
|
13
|
+
/** Promise 拒绝 */
|
|
14
|
+
SentryType["PROMISE"] = "PROMISE";
|
|
15
|
+
/** 原生错误 */
|
|
16
|
+
SentryType["NATIVE"] = "NATIVE";
|
|
17
|
+
})(SentryType || (SentryType = {}));
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 哨兵能力实现
|
|
21
|
+
*
|
|
22
|
+
* 职责:
|
|
23
|
+
* - 捕获全局 JavaScript 错误
|
|
24
|
+
* - 捕获未处理的 Promise 拒绝
|
|
25
|
+
* - 通知上报中心报告异常
|
|
26
|
+
*
|
|
27
|
+
* @tip 单例组件:构造时向全局对象注册错误监听(Web)或改写 __errorHandler(原生),
|
|
28
|
+
* 且无卸载钩子,整个应用生命周期内仅应实例化一次;
|
|
29
|
+
* 重复实例化会导致监听重复注册、原生错误处理器链式叠加。
|
|
30
|
+
* @tip 平台覆盖:Web 走 window 事件、原生走 __errorHandler;
|
|
31
|
+
* 小游戏平台(微信/抖音等,全局对象无 addEventListener 且 sys.isNative 为 false)
|
|
32
|
+
* 当前无捕获覆盖,如需支持请接入对应平台 API。
|
|
33
|
+
*/
|
|
34
|
+
class SentryPlugin extends Plugin {
|
|
35
|
+
constructor(deps, cfg) {
|
|
36
|
+
super(deps, cfg);
|
|
37
|
+
this._stats = {
|
|
38
|
+
Total: 0,
|
|
39
|
+
Javascript: 0,
|
|
40
|
+
Promise: 0,
|
|
41
|
+
Native: 0,
|
|
42
|
+
};
|
|
43
|
+
this._jsErrorHandler = null;
|
|
44
|
+
this._promiseRejectionHandler = null;
|
|
45
|
+
this._nativeErrorHandler = null;
|
|
46
|
+
const global = this.Deps.Access.Global;
|
|
47
|
+
// Web 捕获:addEventListener 位于 EventTarget.prototype(非全局自有属性),
|
|
48
|
+
// Access.Delegate.Has/Get 的自有属性语义取不到(实测浏览器 hasOwnProperty 为 false),
|
|
49
|
+
// 故此处直接访问 globalThis 并绑定接收者——剥离调用在 ESM 严格模式下
|
|
50
|
+
// 因 this 为 undefined 抛 Illegal invocation
|
|
51
|
+
if (typeof globalThis.addEventListener === 'function') {
|
|
52
|
+
const addEventListener = globalThis.addEventListener.bind(globalThis);
|
|
53
|
+
// 注册全局错误处理器和 Promise 拒绝处理器
|
|
54
|
+
this._jsErrorHandler = this.handleJavaScriptError.bind(this);
|
|
55
|
+
this._promiseRejectionHandler = this.handlePromiseRejection.bind(this);
|
|
56
|
+
addEventListener('error', this._jsErrorHandler);
|
|
57
|
+
addEventListener('unhandledrejection', this._promiseRejectionHandler);
|
|
58
|
+
}
|
|
59
|
+
if (sys.isNative) {
|
|
60
|
+
// 注册原生错误处理器:__errorHandler 由 jsb 运行时以自有属性注入,Access 语义可用;
|
|
61
|
+
// 原有处理器必须为函数才参与链式调用,避免错误发生时才抛 TypeError
|
|
62
|
+
const legacy = global.Get('__errorHandler');
|
|
63
|
+
this._nativeErrorHandler =
|
|
64
|
+
typeof legacy === 'function' ? legacy : null;
|
|
65
|
+
// Set 失败(返回 false)说明全局写入被拒:旧处理器保持原位不受影响,
|
|
66
|
+
// 但本插件的原生捕获未生效,回退链引用并告警
|
|
67
|
+
if (!global.Set('__errorHandler', this.handleNativeError.bind(this))) {
|
|
68
|
+
this._nativeErrorHandler = null;
|
|
69
|
+
this.Logger.Warn('原生错误处理器写入失败,原生捕获未生效');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 处理 JavaScript 错误
|
|
75
|
+
*/
|
|
76
|
+
handleJavaScriptError(event) {
|
|
77
|
+
var _a;
|
|
78
|
+
this._stats.Total++;
|
|
79
|
+
this._stats.Javascript++;
|
|
80
|
+
const error = {
|
|
81
|
+
Type: SentryType.JAVASCRIPT,
|
|
82
|
+
Code: 'JAVASCRIPT_ERROR',
|
|
83
|
+
Message: event.message || 'Unknown error',
|
|
84
|
+
Timestamp: Now(),
|
|
85
|
+
Stack: (_a = event.error) === null || _a === void 0 ? void 0 : _a.stack,
|
|
86
|
+
Filename: event.filename,
|
|
87
|
+
Lineno: event.lineno,
|
|
88
|
+
Colno: event.colno,
|
|
89
|
+
};
|
|
90
|
+
this.sendToReporter(error);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 处理 Promise 拒绝
|
|
94
|
+
*/
|
|
95
|
+
handlePromiseRejection(event) {
|
|
96
|
+
this._stats.Total++;
|
|
97
|
+
this._stats.Promise++;
|
|
98
|
+
const { reason } = event;
|
|
99
|
+
let message;
|
|
100
|
+
let stack;
|
|
101
|
+
if (reason instanceof Error) {
|
|
102
|
+
const { message: _message, stack: _stack } = reason;
|
|
103
|
+
message = _message;
|
|
104
|
+
stack = _stack;
|
|
105
|
+
}
|
|
106
|
+
else if (typeof reason === 'string') {
|
|
107
|
+
message = reason;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
message = this.stringifyReason(reason);
|
|
111
|
+
stack = this.extractStack(reason);
|
|
112
|
+
}
|
|
113
|
+
const error = {
|
|
114
|
+
Type: SentryType.PROMISE,
|
|
115
|
+
Code: 'PROMISE_REJECTION',
|
|
116
|
+
Message: message,
|
|
117
|
+
Timestamp: Now(),
|
|
118
|
+
Stack: stack,
|
|
119
|
+
};
|
|
120
|
+
this.sendToReporter(error);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 安全序列化拒绝原因
|
|
124
|
+
*
|
|
125
|
+
* @tip String(reason) 可因 toPrimitive/toString 抛出异常(异常将从事件监听器逃逸,
|
|
126
|
+
* 造成计数与转发不一致);普通对象还会退化为无信息量的 '[object Object]'。
|
|
127
|
+
* 故逐级回退:message 属性 → String → JSON.stringify → Object.prototype.toString
|
|
128
|
+
*/
|
|
129
|
+
stringifyReason(reason) {
|
|
130
|
+
// 跨 realm Error 实例 / 自带 message 的对象,优先提取 message
|
|
131
|
+
if (reason !== null && typeof reason === 'object') {
|
|
132
|
+
try {
|
|
133
|
+
const { message } = reason;
|
|
134
|
+
if (typeof message === 'string' && message.length > 0) {
|
|
135
|
+
return message;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
// message 读取器抛错,走后续回退
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// String 可抛(toPrimitive/toString);结果为 '[object Object]' 时无信息量
|
|
143
|
+
try {
|
|
144
|
+
const text = String(reason);
|
|
145
|
+
if (text !== '[object Object]') {
|
|
146
|
+
return text;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
// 忽略,走后续回退
|
|
151
|
+
}
|
|
152
|
+
// JSON 可抛(循环引用 / toJSON 抛错)
|
|
153
|
+
try {
|
|
154
|
+
const json = JSON.stringify(reason);
|
|
155
|
+
if (typeof json === 'string') {
|
|
156
|
+
return json;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
// 忽略,走末级回退
|
|
161
|
+
}
|
|
162
|
+
return Object.prototype.toString.call(reason);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 提取 Error 形态对象的堆栈
|
|
166
|
+
* @tip 跨 realm Error 实例不是 instanceof Error,其 message/stack 属性原样可取
|
|
167
|
+
*/
|
|
168
|
+
extractStack(reason) {
|
|
169
|
+
if (reason !== null && typeof reason === 'object') {
|
|
170
|
+
try {
|
|
171
|
+
const { stack } = reason;
|
|
172
|
+
if (typeof stack === 'string') {
|
|
173
|
+
return stack;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
// stack 读取器抛错,保持 undefined
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 处理原生错误
|
|
184
|
+
*/
|
|
185
|
+
handleNativeError(name, line, msg, stack) {
|
|
186
|
+
this._stats.Total++;
|
|
187
|
+
this._stats.Native++;
|
|
188
|
+
if (typeof this._nativeErrorHandler === 'function') {
|
|
189
|
+
this._nativeErrorHandler(name, line, msg, stack);
|
|
190
|
+
}
|
|
191
|
+
const error = {
|
|
192
|
+
Type: SentryType.NATIVE,
|
|
193
|
+
Code: 'NATIVE_ERROR',
|
|
194
|
+
Message: msg,
|
|
195
|
+
Filename: name,
|
|
196
|
+
Lineno: line,
|
|
197
|
+
Timestamp: Now(),
|
|
198
|
+
Stack: stack,
|
|
199
|
+
};
|
|
200
|
+
this.sendToReporter(error);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 报告给记者
|
|
204
|
+
*/
|
|
205
|
+
sendToReporter(error) {
|
|
206
|
+
this.Logger.Error('捕获到异常', error);
|
|
207
|
+
// 开发、测试环境不上报(与 @reporter.Collect 的 IsDev||IsAlpha 守卫对齐,双层防御)
|
|
208
|
+
if (this.Deps.Launcher.IsDev || this.Deps.Launcher.IsAlpha)
|
|
209
|
+
return;
|
|
210
|
+
this.Deps.Reporter.Collect(ReporterType.ERROR, error.Code, error.Message, Object.assign({}, error));
|
|
211
|
+
}
|
|
212
|
+
get Stats() {
|
|
213
|
+
return Object.assign({}, this._stats);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { SentryPlugin, SentryType };
|
package/package.json
CHANGED
|
@@ -1,6 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulaaa/sentry",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "哨兵(异常监控)插件",
|
|
5
|
+
"main": "./dist/sentry.cjs",
|
|
6
|
+
"module": "./dist/sentry.mjs",
|
|
7
|
+
"types": "./dist/sentry.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/sentry.d.ts",
|
|
11
|
+
"import": "./dist/sentry.mjs",
|
|
12
|
+
"require": "./dist/sentry.cjs",
|
|
13
|
+
"default": "./dist/sentry.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./min": {
|
|
16
|
+
"types": "./dist/sentry.d.ts",
|
|
17
|
+
"import": "./dist/sentry.min.mjs",
|
|
18
|
+
"require": "./dist/sentry.min.cjs",
|
|
19
|
+
"default": "./dist/sentry.min.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
27
|
+
"build": "npm run clean && rollup -c rollup.config.mjs && node ../scripts/write-dist-package.mjs sentry",
|
|
28
|
+
"test": "jest --config jest.config.mjs --passWithNoTests"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"sentry",
|
|
32
|
+
"nebulaaa"
|
|
33
|
+
],
|
|
34
|
+
"author": "doooreyn",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"devEngines": {
|
|
37
|
+
"packageManager": {
|
|
38
|
+
"name": "npm",
|
|
39
|
+
"version": ">=10.0.0",
|
|
40
|
+
"onFail": "warn"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=16.0.0",
|
|
45
|
+
"npm": ">=10.0.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"registry": "https://registry.npmjs.org/",
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"allowScripts": {
|
|
52
|
+
"unrs-resolver@1.12.2": true,
|
|
53
|
+
"@parcel/watcher@2.6.0": true
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@cocos/creator-types": "^3.8.8",
|
|
57
|
+
"@rollup/plugin-terser": "^1.0.2",
|
|
58
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
59
|
+
"@types/jest": "^30.0.0",
|
|
60
|
+
"@types/node": "^22.10.2",
|
|
61
|
+
"jest": "^30.5.2",
|
|
62
|
+
"rollup": "^4.28.1",
|
|
63
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
64
|
+
"ts-jest": "^29.1.2",
|
|
65
|
+
"ts-node": "^10.9.2",
|
|
66
|
+
"typescript": "5.7.3"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@nebulaaa/access": "^2.0.4",
|
|
70
|
+
"@nebulaaa/launcher": "^2.0.1",
|
|
71
|
+
"@nebulaaa/ms": "^1.1.0",
|
|
72
|
+
"@nebulaaa/plugin": "^1.0.0",
|
|
73
|
+
"@nebulaaa/reporter": "^1.0.2"
|
|
74
|
+
}
|
|
75
|
+
}
|