├── .gitignore
├── .npmignore
├── .npmrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── docs
├── cross-platform.md
├── qrcode
│ ├── sentry-miniapp.jpeg
│ └── zhiyao.jpeg
├── screenshot
│ ├── sentry-admin.png
│ ├── sentry-error-00.png
│ ├── sentry-error-01.png
│ └── sentry-error-02.png
├── sentry-core.png
├── sentry-hub.png
├── sentry-miniapp.drawio
├── sentry-miniapp.png
└── sentry.drawio
├── examples
├── ddapp
│ ├── app.acss
│ ├── app.js
│ ├── app.json
│ ├── pages
│ │ └── index
│ │ │ ├── index.acss
│ │ │ ├── index.axml
│ │ │ ├── index.js
│ │ │ └── index.json
│ ├── snapshot.png
│ └── vendor
│ │ ├── sentry-miniapp.dd.min.js
│ │ └── sentry-miniapp.dd.min.js.map
├── myapp
│ ├── .tea
│ │ ├── configuration
│ │ │ └── mini-program.json
│ │ ├── editorTabs.json
│ │ └── entryFiles-development
│ │ │ ├── config$.js
│ │ │ ├── importScripts$.js
│ │ │ ├── index$.web.js
│ │ │ └── index$.worker.js
│ ├── app.acss
│ ├── app.js
│ ├── app.json
│ ├── pages
│ │ └── index
│ │ │ ├── index.axml
│ │ │ ├── index.js
│ │ │ └── index.json
│ └── vendor
│ │ ├── sentry-miniapp.my.min.js
│ │ └── sentry-miniapp.my.min.js.map
├── qq
│ ├── app.js
│ ├── app.json
│ ├── app.qss
│ ├── pages
│ │ ├── index
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ ├── index.qml
│ │ │ └── index.qss
│ │ └── logs
│ │ │ ├── logs.js
│ │ │ ├── logs.json
│ │ │ ├── logs.qml
│ │ │ └── logs.qss
│ ├── project.config.json
│ ├── utils
│ │ └── util.js
│ └── vendor
│ │ ├── sentry-miniapp.qq.min.js
│ │ └── sentry-miniapp.qq.min.js.map
├── swan
│ ├── .swan
│ │ └── editor.json
│ ├── README.md
│ ├── app.css
│ ├── app.js
│ ├── app.json
│ ├── pages
│ │ ├── index
│ │ │ ├── index.css
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ └── index.swan
│ │ └── logs
│ │ │ ├── logs.css
│ │ │ ├── logs.js
│ │ │ ├── logs.json
│ │ │ └── logs.swan
│ ├── project.config.json
│ ├── project.swan.json
│ ├── sitemap.json
│ ├── utils
│ │ └── util.js
│ └── vendor
│ │ ├── sentry-miniapp.swan.min.js
│ │ └── sentry-miniapp.swan.min.js.map
├── ttapp
│ ├── app.js
│ ├── app.json
│ ├── app.ttss
│ ├── pages
│ │ └── index
│ │ │ ├── index.js
│ │ │ ├── index.ttml
│ │ │ └── index.ttss
│ ├── project.config.json
│ └── vendor
│ │ ├── sentry-miniapp.tt.min.js
│ │ └── sentry-miniapp.tt.min.js.map
├── weapp
│ ├── README.md
│ ├── app.js
│ ├── app.json
│ ├── app.wxss
│ ├── pages
│ │ ├── index
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ ├── index.wxml
│ │ │ └── index.wxss
│ │ └── logs
│ │ │ ├── logs.js
│ │ │ ├── logs.json
│ │ │ ├── logs.wxml
│ │ │ └── logs.wxss
│ ├── project.config.json
│ ├── project.private.config.json
│ ├── sitemap.json
│ ├── utils
│ │ └── util.js
│ └── vendor
│ │ ├── sentry-miniapp.wx.min.js
│ │ └── sentry-miniapp.wx.min.js.map
└── wegame
│ ├── README.md
│ ├── audio
│ ├── bgm.mp3
│ ├── boom.mp3
│ └── bullet.mp3
│ ├── game.js
│ ├── game.json
│ ├── images
│ ├── Common.png
│ ├── bg.jpg
│ ├── bullet.png
│ ├── enemy.png
│ ├── explosion1.png
│ ├── explosion10.png
│ ├── explosion11.png
│ ├── explosion12.png
│ ├── explosion13.png
│ ├── explosion14.png
│ ├── explosion15.png
│ ├── explosion16.png
│ ├── explosion17.png
│ ├── explosion18.png
│ ├── explosion19.png
│ ├── explosion2.png
│ ├── explosion3.png
│ ├── explosion4.png
│ ├── explosion5.png
│ ├── explosion6.png
│ ├── explosion7.png
│ ├── explosion8.png
│ ├── explosion9.png
│ └── hero.png
│ ├── js
│ ├── base
│ │ ├── animation.js
│ │ ├── pool.js
│ │ └── sprite.js
│ ├── databus.js
│ ├── libs
│ │ ├── symbol.js
│ │ └── weapp-adapter.js
│ ├── main.js
│ ├── npc
│ │ └── enemy.js
│ ├── player
│ │ ├── bullet.js
│ │ └── index.js
│ └── runtime
│ │ ├── background.js
│ │ ├── gameinfo.js
│ │ └── music.js
│ ├── project.config.json
│ └── vendor
│ ├── sentry-miniapp.wx.min.js
│ └── sentry-miniapp.wx.min.js.map
├── package.json
├── scripts
└── versionbump.js
├── src
├── backend.ts
├── client.ts
├── crossPlatform.ts
├── eventbuilder.ts
├── flags.ts
├── helpers.ts
├── index.ts
├── integrations
│ ├── globalhandlers.ts
│ ├── ignoreMpcrawlerErrors.ts
│ ├── index.ts
│ ├── linkederrors.ts
│ ├── router.ts
│ ├── system.ts
│ ├── trycatch.ts
│ └── tslint.json
├── parsers.ts
├── sdk.ts
├── tracekit.ts
├── transports
│ ├── base.ts
│ ├── index.ts
│ └── xhr.ts
└── version.ts
├── test
└── tslint.json
├── tsconfig.esm.json
├── tsconfig.json
├── tslint.json
├── webpack.common.js
├── webpack.config.dd.js
├── webpack.config.my.js
├── webpack.config.qq.js
├── webpack.config.swan.js
├── webpack.config.tt.js
├── webpack.config.wx.js
├── webpack.config.wxgame.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules/
3 | packages/*/package-lock.json
4 |
5 | # build and test
6 | dist/
7 | esm/
8 | build/
9 | packages/*/dist/
10 | packages/*/esm/
11 | coverage/
12 | scratch/
13 | *.pyc
14 | *.tsbuildinfo
15 |
16 | # logs
17 | yarn-error.log
18 | npm-debug.log
19 | lerna-debug.log
20 | local.log
21 |
22 | # ide
23 | .idea
24 | *.sublime-*
25 |
26 | # misc
27 | .DS_Store
28 | ._*
29 | .Spotlight-V100
30 | .Trashes
31 |
32 | .rpt2_cache
33 |
34 | lint-results.json
35 |
36 | # legacy
37 | tmp.js
38 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *
2 | !/dist/**/*
3 | !/esm/**/*
4 | *.tsbuildinfo
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # https://npmmirror.com/
2 | registry=https://registry.npmmirror.com
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Bugout",
4 | "Fundebug",
5 | "Mpcrawler",
6 | "SDKs",
7 | "alipay",
8 | "bytedance",
9 | "ddapp",
10 | "dingtalk",
11 | "globalhanders",
12 | "globalhandlers",
13 | "linkederrors",
14 | "myapp",
15 | "pagenotfound",
16 | "submatch",
17 | "tracekit",
18 | "trycatch",
19 | "ttapp",
20 | "versionbump",
21 | "wechat",
22 | "wegame",
23 | "wifi",
24 | "winjs",
25 | "wxgame"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, Sentry
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sentry 小程序 SDK
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 | 
9 |
10 | 用于小程序平台的 Sentry SDK
11 |
12 | ## 功能特点
13 |
14 | - [x] 基于 [sentry-javascript 最新的基础模块](https://www.yuque.com/lizhiyao/dxy/zevhf1#0GMCN) 封装
15 | - [x] 遵守[官方统一的 API 设计文档](https://www.yuque.com/lizhiyao/dxy/gc3b9r#vQdTs),使用方式和官方保持一致
16 | - [x] 使用 [TypeScript](https://www.typescriptlang.org/) 进行编写
17 | - [x] 包含 Sentry SDK(如:[@sentry/browser](https://github.com/getsentry/sentry-javascript/tree/master/packages/browser))的所有基础功能
18 | - [x] 支持 `ES6`、`CommonJS` 两种模块系统(支持小程序原生开发方式、使用小程序框架开发方式两种开发模式下使用)
19 | - [x] 默认监听并上报小程序的 onError、onUnhandledRejection、onPageNotFound、onMemoryWarning 事件返回的信息(各事件支持程度与对应各小程序官方保持一致)
20 | - [x] 默认上报运行小程序的设备、操作系统、应用版本信息
21 | - [x] 支持微信小程序
22 | - [x] 支持微信小游戏
23 | - [x] 支持字节跳动小程序
24 | - [x] 支持支付宝小程序
25 | - [x] 支持钉钉小程序
26 | - [x] 支持百度小程序
27 | - [x] 支持在 [Taro](https://taro.aotu.io/) 等第三方小程序框架中使用
28 | - [x] 默认上报异常发生时的路由栈
29 | - [ ] 完善的代码测试
30 |
31 | ## 用法
32 |
33 | 支持两种使用方式:
34 |
35 | - 直接引用
36 | - 通过 npm 方式使用(推荐)
37 |
38 | ### 注意
39 |
40 | 1. 无论选择哪种使用方式,都需要开启「微信开发者工具 - 设置 - 项目设置 - 增强编译」功能
41 | 2. 使用前需要确保有可用的 `Sentry Service`,比如:使用 [官方 Sentry Service](https://sentry.io/welcome/) 服务 或[自己搭建 Sentry Service](https://docs.sentry.io/server/)。如果想直接将异常信息上报到 ,由于其没有备案,可以先将异常信息上报给自己已备案域名下的服务端接口,由服务端进行请求转发。
42 | 3. 在小程序管理后台配置 `Sentry Service` 对应的 `request` 合法域名
43 |
44 | ### 直接引用
45 |
46 | 1. 微信小程序和微信小游戏下载 [sentry-miniapp.wx.min.js](https://github.com/lizhiyao/sentry-miniapp/blob/master/examples/weapp/vendor/sentry-miniapp.wx.min.js);字节跳动小程序下载 [sentry-miniapp.tt.min.js](https://github.com/lizhiyao/sentry-miniapp/blob/master/examples/ttapp/vendor/sentry-miniapp.tt.min.js);支付宝小程序下载 [sentry-miniapp.my.min.js](https://github.com/lizhiyao/sentry-miniapp/blob/master/examples/myapp/vendor/sentry-miniapp.my.min.js),钉钉小程序下载 [sentry-miniapp.dd.min.js](https://github.com/lizhiyao/sentry-miniapp/blob/master/examples/ddapp/vendor/sentry-miniapp.dd.min.js)
47 | 2. 参照 `/examples` 中各项目使用方式,将 `sentry-miniapp.xx.min.js` 放入项目的合适目录中,比如放入 `vendor` 文件夹
48 | 3. 参照 `/examples/app.js` 代码,进行 `Sentry` 的初始化
49 | 4. 对于提供了微信、字节跳动小程序 `sentry-miniapp` 会自动上报 `xx.onError()` 捕获的异常,对于支付宝小程序需要应用开发者在 `App.onError()` 中主动进行异常上报。详情可见 `/docs/cross-platform.md`。
50 |
51 | ### npm 方式
52 |
53 | 注意:目前字节跳动小程序不支持 npm 方式。
54 |
55 | 1. 安装依赖
56 |
57 | ```bash
58 | npm install sentry-miniapp --save
59 | # 或者
60 | yarn add sentry-miniapp
61 | ```
62 |
63 | 2. 使用「微信开发者工具 - 工具 - 构建 npm」进行构建,详情可参考[npm 支持](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)
64 |
65 | 3. 在 `app.js` 中引用并初始化 `Sentry`,根据实际需求设置上报到 Sentry 的元信息
66 |
67 | ```js
68 | import * as Sentry from "sentry-miniapp";
69 |
70 | // init Sentry
71 | // init options: https://github.com/getsentry/sentry-javascript/blob/master/packages/types/src/options.ts
72 | Sentry.init({
73 | dsn: "__DSN__",
74 | // ...
75 | });
76 |
77 | // Set user information, as well as tags and further extras
78 | Sentry.configureScope((scope) => {
79 | scope.setExtra("battery", 0.7);
80 | scope.setTag("user_mode", "admin");
81 | scope.setUser({ id: "4711" });
82 | // scope.clear();
83 | });
84 |
85 | // Add a breadcrumb for future events
86 | Sentry.addBreadcrumb({
87 | message: "My Breadcrumb",
88 | // ...
89 | });
90 |
91 | // Capture exceptions, messages or manual events
92 | Sentry.captureException(new Error("Good bye"));
93 | Sentry.captureMessage("Hello, world!");
94 | Sentry.captureEvent({
95 | message: "Manual",
96 | stacktrace: [
97 | // ...
98 | ],
99 | });
100 | ```
101 |
102 | ## 开发
103 |
104 | ### 知识储备
105 |
106 | 开发前请仔细阅读下面内容:
107 |
108 | - [sentry-javascript README 中文版](https://www.yuque.com/lizhiyao/dxydance/sentry-javascript-readme-cn)
109 | - [Sentry 开发指南](https://www.yuque.com/lizhiyao/dxydance/sentry-develop-guide)
110 | - [sentry-javascript 源码阅读](https://www.yuque.com/lizhiyao/dxydance/sentry-javascript-src)
111 |
112 | #### sentry-core 设计图
113 |
114 | 
115 |
116 | #### sentry-hub 设计图
117 |
118 | 
119 |
120 | #### sentry-miniapp 设计图
121 |
122 | 
123 |
124 | ### 相关命令
125 |
126 | ```bash
127 | # 根据 package.json 中的版本号更新 SDK 源码中的版本号
128 | npm run version
129 |
130 | # 构建供小程序直接引用的 sentry-miniapp.xx.min.js;在本地可直接使用开发者工具打开 examples 下具体项目进行调试
131 | npm run build:dist
132 |
133 | # 构建供微信小程序直接引用的 sentry-miniapp.wx.min.js
134 | npm run build:wx
135 |
136 | # 构建供支付宝小程序直接引用的 sentry-miniapp.my.min.js
137 | npm run build:my
138 |
139 | # 构建供钉钉小程序直接引用的 sentry-miniapp.dd.min.js
140 | npm run build:dd
141 |
142 | # 构建供字节跳动小程序直接引用的 sentry-miniapp.tt.min.js
143 | npm run build:tt
144 |
145 | # 构建供百度小程序直接引用的 sentry-miniapp.swan.min.js
146 | npm run build:swan
147 |
148 | # 构建用于发布到 npm 的 dist 资源
149 | npm run build
150 |
151 | # 构建用于发布到 npm 的 esm 资源
152 | npm run build:esm
153 |
154 | # 发布到 npm
155 | npm publish --registry=https://registry.npmjs.org/
156 | ```
157 |
158 | ## 效果图
159 |
160 | 
161 | 
162 | 
163 | 
164 |
165 | ## 谁在使用 sentry-miniapp
166 |
167 | ### 微信小程序
168 |
169 | - 丁香医生
170 | - 丁香医生医生端
171 | - 丁香人才
172 | - 丁香家
173 |
174 | ### 支付宝小程序
175 |
176 | - 丁香医生
177 |
178 | ### 字节跳动小程序
179 |
180 | - 丁香医生
181 |
182 | ## 参考资料
183 |
184 | - [sentry-javascript](https://github.com/getsentry/sentry-javascript)
185 | - [Sentry Getting Started](https://docs.sentry.io/error-reporting/quickstart/?platform=browsernpm)
186 | - [Sentry JavaScript SDKs](http://getsentry.github.io/sentry-javascript/)
187 | - [Sentry TypeScript Configuration](https://github.com/getsentry/sentry-javascript/tree/master/packages/typescript)
188 | - [wx.request](https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html)
189 | - [小程序 App](https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html)
190 | - [wx.onError、App.onError 疑惑及如何捕获 Promise 异常?](https://developers.weixin.qq.com/community/develop/doc/000c8cf5794770272709f38a756000)
191 | - [shields.io](https://shields.io/)
192 | - [字节跳动小程序文档](https://developer.toutiao.com/docs/framework/)
193 | - [支付宝小程序文档](https://docs.alipay.com/mini/developer)
194 | - [tt.onError 的疑问](http://forum.microapp.bytedance.com/topic/2806/tt-onerror-%E7%96%91%E9%97%AE)
195 |
196 | ## 其他小程序异常监控产品
197 |
198 | - [Fundebug](https://www.fundebug.com/)
199 | - [FrontJS](https://www.frontjs.com/home/tour)
200 | - [Bugout](https://bugout.testin.cn/)
201 |
202 | ## 贡献
203 |
204 | 欢迎通过 `issue`、`pull request`等方式贡献 `sentry-miniapp`。
205 |
206 | ## 联系作者
207 |
208 | PS. 由于微信群二维码有时效性限制,想入群的同学还可以加作者微信(添加时请备注 sentry-miniapp),由作者邀请入群
209 |
210 | ### sentry-miniapp 微信交流群
211 |
212 |
213 |
214 | ### 作者微信二维码
215 |
216 |
217 |
--------------------------------------------------------------------------------
/docs/cross-platform.md:
--------------------------------------------------------------------------------
1 | # 跨平台支持
2 |
3 | 核心需要跨平台兼容的地方有:
4 |
5 | - transports
6 | - integrations/system
7 | - integrations/globalhanders
8 | - integrations/router
9 |
10 | ## transports
11 |
12 | request
13 |
14 | - [微信小程序 wx.request()](https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html)
15 | - [字节跳动小程序 tt.request()](https://developer.toutiao.com/docs/api/request.html#request)
16 | - [支付宝小程序 my.request()](https://docs.alipay.com/mini/api/owycmh)
17 | - [钉钉小程序 dd.httpRequest()](https://ding-doc.dingtalk.com/doc#/dev/httprequest)
18 | - [QQ 小程序 qq.request()](https://q.qq.com/wiki/develop/game/API/network/request.html)
19 |
20 | ## integrations/system
21 |
22 | - [微信小程序 wx.getSystemInfoSync()](https://developers.weixin.qq.com/miniprogram/dev/api/base/system/system-info/wx.getSystemInfo.html)
23 | - [字节跳动小程序 tt.getSystemInfoSync()](https://developer.toutiao.com/docs/game/system/system-info/tt.getSystemInfoSync.html)
24 | - [支付宝小程序 my.getSystemInfoSync()](https://docs.alipay.com/mini/api/system-info)
25 | - [钉钉小程序 my.getSystemInfoSync()](https://ding-doc.dingtalk.com/doc#/dev/system-info)
26 | - [QQ 小程序 qq.getSystemInfoSync()](https://q.qq.com/wiki/develop/game/API/basic/system.html#qq-getsysteminfosync)
27 |
28 | ## integrations/globalhanders
29 |
30 | 错误监听函数
31 |
32 | - [微信小程序 App.onError(String error)](https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html)
33 | - [微信小程序 wx.onError(function callback)](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onError.html)
34 | - [字节跳动小程序 App.onError(msg)](https://developer.toutiao.com/docs/framework/startupApp.html)
35 | - [字节跳动小程序 tt.onError(function callback)](https://developer.toutiao.com/docs/game/system/system-event/tt.onError.html)
36 | - [支付宝小程序 App.onError(msg)](https://docs.alipay.com/mini/framework/app)
37 | - [钉钉小程序 App.onError(msg)](https://ding-doc.dingtalk.com/doc#/dev/framework-app)
38 | - 支付宝小程序不支持 my.onError(function callback)
39 | - [QQ 小程序 qq.onError()](https://q.qq.com/wiki/develop/game/API/basic/miniprogram.html#qq-onerror)
40 |
41 | 监听未处理的 Promise 拒绝事件
42 |
43 | - [微信小程序 wx.onUnhandledRejection(function callback)](https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html#onUnhandledRejection-Object-object)
44 |
45 | 页面不存在监听函数
46 |
47 | - [微信小程序 App.onPageNotFound(Object object)](https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html)
48 | - [微信小程序 wx.offPageNotFound(function callback)](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.offPageNotFound.html)
49 | - [字节跳动小程序 App.onPageNotFound(res)](https://developer.toutiao.com/docs/framework/startupApp.html)
50 | - 字节跳动小程序不支持 tt.onPageNotFound()
51 | - 支付宝小程序两种方式均不支持
52 | - 钉钉小程序两种方式均不支持
53 | - [QQ 小程序 qq.onPageNotFound()](https://q.qq.com/wiki/develop/miniprogram/API/basic/miniAPP.html#qq-onpagenotfound)
54 |
55 | 监听内存不足的告警事件
56 |
57 | - [微信小程序 wx.onMemoryWarning(function callback)](https://developers.weixin.qq.com/miniprogram/dev/api/device/performance/wx.onMemoryWarning.html)
58 | - [字节跳动小程序 暂不支持](https://developer.toutiao.com/docs/game/performance/onMemoryWarning.html)
59 | - [支付宝小程序 my.onMemoryWarning()](https://docs.alipay.com/mini/api/hszexr)
60 | - 钉钉小程序 暂不支持
61 | - [QQ 小程序 qq.onMemoryWarning()](https://q.qq.com/wiki/develop/miniprogram/API/equipment/ibeacon_memory.html#qq-onmemorywarning-function-callback)
62 |
63 | ## integrations/router
64 |
65 | - [微信小程序 PageObject[] getCurrentPages()](https://developers.weixin.qq.com/miniprogram/dev/reference/api/getCurrentPages.html)
66 | - [字节跳动小程序 getCurrentPages()](https://developer.toutiao.com/dev/cn/mini-app/develop/framework/logic-layer/page-path)
67 | - [支付宝小程序 getCurrentPages()](https://docs.alipay.com/mini/framework/getcurrentpages)
68 | - [钉钉小程序 getCurrentPages()](https://ding-doc.dingtalk.com/doc#/dev/framework-page)
69 | - [QQ 小程序 getCurrentPages()](https://q.qq.com/wiki/develop/miniprogram/frame/logic/logic_page_route.html#getcurrentpages)
70 |
71 | ## 总结
72 |
73 | | | 微信 | 字节跳动 | 支付宝 | 钉钉 | QQ |
74 | | -------------------- | ---- | -------- | ------ | --------------- | --- |
75 | | getSystemInfoSync | √ | √ | √ | √ | √ |
76 | | request | √ | √ | √ | √ (httpRequest) | √ |
77 | | App.onError | √ | √ | √ | √ | √ |
78 | | App.onPageNotFound | √ | √ | × | × | √ |
79 | | onError | √ | √ | × | × | √ |
80 | | onUnhandledRejection | √ | 待确认 | 待确认 | 待确认 | x |
81 | | onPageNotFound | √ | × | × | × | √ |
82 | | onMemoryWarning | √ | × | √ | × | √ |
83 | | getCurrentPages | √ | √ | √ | √ | √ |
84 | | getNetworkType | √ | 待确认 | 待确认 | 待确认 | √ |
85 |
--------------------------------------------------------------------------------
/docs/qrcode/sentry-miniapp.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/qrcode/sentry-miniapp.jpeg
--------------------------------------------------------------------------------
/docs/qrcode/zhiyao.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/qrcode/zhiyao.jpeg
--------------------------------------------------------------------------------
/docs/screenshot/sentry-admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/screenshot/sentry-admin.png
--------------------------------------------------------------------------------
/docs/screenshot/sentry-error-00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/screenshot/sentry-error-00.png
--------------------------------------------------------------------------------
/docs/screenshot/sentry-error-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/screenshot/sentry-error-01.png
--------------------------------------------------------------------------------
/docs/screenshot/sentry-error-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/screenshot/sentry-error-02.png
--------------------------------------------------------------------------------
/docs/sentry-core.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/sentry-core.png
--------------------------------------------------------------------------------
/docs/sentry-hub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/sentry-hub.png
--------------------------------------------------------------------------------
/docs/sentry-miniapp.drawio:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/docs/sentry-miniapp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/docs/sentry-miniapp.png
--------------------------------------------------------------------------------
/docs/sentry.drawio:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/examples/ddapp/app.acss:
--------------------------------------------------------------------------------
1 | page {
2 | flex: 1;
3 | display: flex;
4 | background: #f7f7f7;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/ddapp/app.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from "./vendor/sentry-miniapp.dd.min";
2 | console.log("Sentry", Sentry);
3 | // 初始化 Sentry
4 | Sentry.init({
5 | platform: "dd",
6 | dsn: "https://9d8f4b56ae4f4a48bc1e0974d7642a87@sentry.io/1534850"
7 | });
8 |
9 | App({
10 | onLaunch(options) {
11 | // 第一次打开
12 | // options.query == {number:1
13 | console.info("app.js", dd);
14 | console.info("App onLaunch");
15 | },
16 | onShow(options) {
17 | // 从后台被 scheme 重新打开
18 | // options.query == {number:1}
19 | Sentry.captureException(new Error("钉钉小程序"));
20 | Sentry.captureMessage("钉钉小程序Message");
21 | myrUndefinedFunctionInAsyncFunction();
22 | },
23 | onError(error) {
24 | console.warn("onError", error);
25 | Sentry.captureException(error);
26 | }
27 | });
28 |
--------------------------------------------------------------------------------
/examples/ddapp/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index"
4 | ],
5 | "window": {
6 | "defaultTitle": "My App"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/ddapp/pages/index/index.acss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/ddapp/pages/index/index.acss
--------------------------------------------------------------------------------
/examples/ddapp/pages/index/index.axml:
--------------------------------------------------------------------------------
1 |
2 | this is a blank page
3 |
4 |
--------------------------------------------------------------------------------
/examples/ddapp/pages/index/index.js:
--------------------------------------------------------------------------------
1 | Page({
2 | onLoad(query) {
3 | // 页面加载
4 | console.info(`Page onLoad with query: ${JSON.stringify(query)}`);
5 | },
6 | onReady() {
7 | // 页面加载完成
8 | },
9 | onShow() {
10 | // 页面显示
11 | },
12 | onHide() {
13 | // 页面隐藏
14 | },
15 | onUnload() {
16 | // 页面被关闭
17 | },
18 | onTitleClick() {
19 | // 标题被点击
20 | },
21 | onPullDownRefresh() {
22 | // 页面被下拉
23 | },
24 | onReachBottom() {
25 | // 页面被拉到底部
26 | },
27 | onShareAppMessage() {
28 | // 返回自定义分享信息
29 | return {
30 | title: 'My App',
31 | desc: 'My App description',
32 | path: 'pages/index/index',
33 | };
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/examples/ddapp/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/examples/ddapp/snapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/ddapp/snapshot.png
--------------------------------------------------------------------------------
/examples/myapp/.tea/configuration/mini-program.json:
--------------------------------------------------------------------------------
1 | {"tinyType":"alipay","tinyCnName":"支付宝","tinyProjectType":"alipay-mini","alipay:alipay-mini:tinyIcon":"https://gw.alipayobjects.com/zos/rmsportal/rcwFIauzePpMqlTzzanK.svg","alipay:alipay-mini:_appIdList":[{"appid":"2018030602324267","name":"丁香医生在线","logoUrl":"https://appstoreisvpic.alipayobjects.com/prod/a2f7f617-dfc3-42f1-85e2-32e22b17a716.png"}],"alipay:alipay-mini:uploadTime":"","alipay:alipay-mini:packageSize":0,"alipay:alipay-mini:auditLink":null,"alipay:alipay-mini:whiteListLink":null,"alipay:alipay-mini:component2":false,"alipay:alipay-mini:_enableAutoPush":false,"alipay:alipay-mini:lastDeviceName":"iPhone 5","alipay:alipay-mini:_appId":"2018030602324267","alipay:alipay-mini:_appName":"丁香医生在线","alipay:alipay-mini:remoteVersion":"0.6.0","alipay:alipay-mini:currentVersion":"0.6.1","alipay:alipay-mini:whiteList":["auth.dxy.cn","da.dxy.cn","img1.dxycdn.com","ask.dxy.com","auth.dxy.net","da.dxy.net","dxy.com","asktest.dxy.net","dotcomtest.dxy.net"],"alipay:alipay-mini:h5WhiteList":[],"alipay:alipay-mini:appLogo":"https://appstoreisvpic.alipayobjects.com/prod/a2f7f617-dfc3-42f1-85e2-32e22b17a716.png"}
--------------------------------------------------------------------------------
/examples/myapp/.tea/editorTabs.json:
--------------------------------------------------------------------------------
1 | {"tabs":[]}
--------------------------------------------------------------------------------
/examples/myapp/.tea/entryFiles-development/config$.js:
--------------------------------------------------------------------------------
1 |
2 | const g = typeof global !== 'undefined' ? global : self;
3 | g.appXAppJson = {
4 | "app": {
5 | "$homepage": "pages/index/index"
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/examples/myapp/.tea/entryFiles-development/importScripts$.js:
--------------------------------------------------------------------------------
1 | if(!self.Map || !self.Set || !self.Symbol) {
2 | importScripts('https://gw.alipayobjects.com/as/g/appx_release/deps/1.0.3/es6-set-map-symbol.js');
3 | }
4 |
--------------------------------------------------------------------------------
/examples/myapp/.tea/entryFiles-development/index$.web.js:
--------------------------------------------------------------------------------
1 | require('@alipay/appx-compiler/lib/sjsEnvInit');
2 | require('./config$');
3 |
4 | require('../../pages/index/index');
5 |
--------------------------------------------------------------------------------
/examples/myapp/.tea/entryFiles-development/index$.worker.js:
--------------------------------------------------------------------------------
1 | if (!self.__appxInited) {
2 | self.__appxInited = 1;
3 |
4 | require("./config$");
5 |
6 | var AFAppX = self.AFAppX.getAppContext
7 | ? self.AFAppX.getAppContext().AFAppX
8 | : self.AFAppX;
9 | self.getCurrentPages = AFAppX.getCurrentPages;
10 | self.getApp = AFAppX.getApp;
11 | self.Page = AFAppX.Page;
12 | self.App = AFAppX.App;
13 | self.my = AFAppX.bridge || AFAppX.abridge;
14 | self.abridge = self.my;
15 | self.Component = AFAppX.WorkerComponent || function () {};
16 | self.$global = AFAppX.$global;
17 | self.requirePlugin = AFAppX.requirePlugin;
18 |
19 | function success() {
20 | require("../../app");
21 | require("../../pages/index/index");
22 | }
23 | self.bootstrapApp ? self.bootstrapApp({ success }) : success();
24 | }
25 |
--------------------------------------------------------------------------------
/examples/myapp/app.acss:
--------------------------------------------------------------------------------
1 | page {
2 | background: #f7f7f7;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/myapp/app.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from "./vendor/sentry-miniapp.my.min";
2 |
3 | console.log(Sentry);
4 | // 初始化 Sentry
5 | Sentry.init({
6 | platform: "my",
7 | dsn: "https://9d8f4b56ae4f4a48bc1e0974d7642a87@sentry.io/1534850"
8 | });
9 |
10 | App({
11 | onLaunch(options) {
12 | // 第一次打开
13 | // options.query == {number:1}
14 | console.info("app.js", my);
15 | console.info("App onLaunch");
16 |
17 | // 测试非异步代码执行异常
18 | // myUndefinedFunction();
19 |
20 | // 测试 API 调用失败 ---> framework error: can not find page: pages/get-user-info/get-user-info---> onError 无法捕获
21 | // my.navigateTo({ url: '../get-user-info/get-user-info' });
22 | },
23 | onShow(options) {
24 | // 从后台被 scheme 重新打开
25 | // options.query == {number:1}
26 |
27 | // 测试 async 函数中异常是否可以被 onError 捕获 ---> onError 无法捕获
28 | // const ret = await new Promise((resolve) => {
29 | // setTimeout(() => {
30 | // resolve('this is await ret.');
31 | // }, 2000);
32 | // });
33 | // console.log(ret);
34 | myrUndefinedFunctionInAsyncFunction();
35 | },
36 | onError(error) {
37 | console.warn("onError", error);
38 | Sentry.captureException(error);
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/examples/myapp/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index"
4 | ],
5 | "window": {
6 | "defaultTitle": "My App"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/myapp/pages/index/index.axml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | this is a blank page
5 |
6 |
--------------------------------------------------------------------------------
/examples/myapp/pages/index/index.js:
--------------------------------------------------------------------------------
1 | Page({
2 | onLoad(query) {
3 | // 页面加载
4 | console.info(`Page onLoad with query: ${JSON.stringify(query)}`);
5 | },
6 | onReady() {
7 | // 页面加载完成
8 | },
9 | onShow() {
10 | // 页面显示
11 | },
12 | onHide() {
13 | // 页面隐藏
14 | },
15 | onUnload() {
16 | // 页面被关闭
17 | },
18 | onTitleClick() {
19 | // 标题被点击
20 | },
21 | onPullDownRefresh() {
22 | // 页面被下拉
23 | },
24 | onReachBottom() {
25 | // 页面被拉到底部
26 | },
27 | onShareAppMessage() {
28 | // 返回自定义分享信息
29 | return {
30 | title: 'My App',
31 | desc: 'My App description',
32 | path: 'pages/index/index',
33 | };
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/examples/myapp/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/examples/qq/app.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from "./vendor/sentry-miniapp.qq.min";
2 | // 初始化 Sentry
3 | Sentry.init({
4 | dsn:
5 | "https://252e9b6a87d4422787ff151dc3b10a66@o113510.ingest.sentry.io/5299999",
6 | });
7 |
8 | App({
9 | onLaunch: function () {
10 | // 展示本地存储能力
11 | var logs = qq.getStorageSync("logs") || [];
12 | logs.unshift(Date.now());
13 | qq.setStorageSync("logs", logs);
14 |
15 | // 登录
16 | qq.login({
17 | success: (res) => {
18 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
19 | },
20 | });
21 | // 获取用户信息
22 | qq.getSetting({
23 | success: (res) => {
24 | if (res.authSetting["scope.userInfo"]) {
25 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
26 | qq.getUserInfo({
27 | success: (res) => {
28 | // 可以将 res 发送给后台解码出 unionId
29 | this.globalData.userInfo = res.userInfo;
30 |
31 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
32 | // 所以此处加入 callback 以防止这种情况
33 | if (this.userInfoReadyCallback) {
34 | this.userInfoReadyCallback(res);
35 | }
36 | },
37 | });
38 | }
39 | },
40 | });
41 |
42 | myUndefinedFunction();
43 | },
44 | globalData: {
45 | userInfo: null,
46 | },
47 | });
48 |
--------------------------------------------------------------------------------
/examples/qq/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages":[
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window":{
7 | "backgroundTextStyle":"light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "navigationBarTitleText": "QQ小程序",
10 | "navigationBarTextStyle":"black"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/qq/app.qss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100%;
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | justify-content: space-between;
7 | padding: 200rpx 0;
8 | box-sizing: border-box;
9 | }
10 |
--------------------------------------------------------------------------------
/examples/qq/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp()
4 |
5 | Page({
6 | data: {
7 | motto: 'Hello World',
8 | userInfo: {},
9 | hasUserInfo: false,
10 | canIUse: qq.canIUse('button.open-type.getUserInfo')
11 | },
12 | //事件处理函数
13 | bindViewTap: function () {
14 | qq.navigateTo({
15 | url: '../logs/logs'
16 | })
17 | },
18 | onLoad: function () {
19 | if (app.globalData.userInfo) {
20 | this.setData({
21 | userInfo: app.globalData.userInfo,
22 | hasUserInfo: true
23 | })
24 | } else if (this.data.canIUse) {
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | app.userInfoReadyCallback = res => {
28 | this.setData({
29 | userInfo: res.userInfo,
30 | hasUserInfo: true
31 | })
32 | }
33 | } else {
34 | // 在没有 open-type=getUserInfo 版本的兼容处理
35 | qq.getUserInfo({
36 | success: res => {
37 | app.globalData.userInfo = res.userInfo
38 | this.setData({
39 | userInfo: res.userInfo,
40 | hasUserInfo: true
41 | })
42 | }
43 | })
44 | }
45 | },
46 | getUserInfo: function (e) {
47 | console.log(e)
48 | app.globalData.userInfo = e.detail.userInfo
49 | this.setData({
50 | userInfo: e.detail.userInfo,
51 | hasUserInfo: true
52 | })
53 | }
54 | })
55 |
--------------------------------------------------------------------------------
/examples/qq/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/examples/qq/pages/index/index.qml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{userInfo.nickName}}
7 |
8 |
9 |
10 | {{motto}}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/qq/pages/index/index.qss:
--------------------------------------------------------------------------------
1 | .userinfo {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
7 | .userinfo-avatar {
8 | width: 128rpx;
9 | height: 128rpx;
10 | margin: 20rpx;
11 | border-radius: 50%;
12 | }
13 |
14 | .userinfo-nickname {
15 | color: #aaa;
16 | }
17 |
18 | .usermotto {
19 | margin-top: 200px;
20 | }
21 |
--------------------------------------------------------------------------------
/examples/qq/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad: function () {
9 | this.setData({
10 | logs: (qq.getStorageSync('logs') || []).map(log => {
11 | return util.formatTime(new Date(log))
12 | })
13 | })
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/examples/qq/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志",
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/examples/qq/pages/logs/logs.qml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{index + 1}}. {{log}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/qq/pages/logs/logs.qss:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/qq/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectid": "1108100302_qq-miniapp-test",
3 | "setting": {
4 | "urlCheck": false,
5 | "es6": true,
6 | "postcss": false,
7 | "minified": true,
8 | "newFeature": true,
9 | "autoAudits": true,
10 | "nodeModules": true,
11 | "uploadWithSourceMap": true,
12 | "uglifyFileName": true,
13 | "remoteDebugLogEnable": false
14 | },
15 | "compileType": "miniprogram",
16 | "createTime": 1593338252378,
17 | "accessTime": 1593338252378,
18 | "packOptions": {
19 | "ignore": []
20 | },
21 | "debugOptions": {
22 | "hidedInDevtools": []
23 | },
24 | "qqappid": "1108100302",
25 | "projectname": "qq-miniapp-test",
26 | "scripts": {},
27 | "condition": {
28 | "search": {
29 | "current": -1,
30 | "list": []
31 | },
32 | "conversation": {
33 | "current": -1,
34 | "list": []
35 | },
36 | "game": {
37 | "currentL": -1,
38 | "list": []
39 | },
40 | "miniprogram": {
41 | "current": -1,
42 | "list": []
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/examples/qq/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------
/examples/swan/.swan/editor.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/swan/.swan/editor.json
--------------------------------------------------------------------------------
/examples/swan/README.md:
--------------------------------------------------------------------------------
1 | # 小程序示例项目
2 |
--------------------------------------------------------------------------------
/examples/swan/app.css:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | padding: 200rpx 0;
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/swan/app.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from "./vendor/sentry-miniapp.swan.min";
2 | // 初始化 Sentry
3 | Sentry.init({
4 | dsn: "https://47703e01ba4344b8b252c15e8fd980fd@sentry.io/1528228",
5 | });
6 |
7 | App({
8 | globalData: {
9 | userInfo: null,
10 | },
11 | onLaunch() {
12 | // 展示本地存储能力
13 | // 获取用户信息
14 | // console.log(systemInfo);
15 |
16 | // Sentry.captureException(new Error("test"));
17 |
18 | // 测试 异常是否可以上报
19 | // throw new Error("this is a test 4G error.");
20 | // throw new Error("lalalalalala");
21 | // myUndefinedFunction();
22 |
23 | // 测试 async 函数中异常是否可以被 onError 捕获
24 | // const ret = await new Promise((resolve) => {
25 | // setTimeout(() => {
26 | // resolve('this is await ret.');
27 | // }, 2000);
28 | // });
29 | // console.log(ret);
30 | // myrUndefinedFunctionInAsyncFunction();
31 |
32 | // 一种可以在 async 函数中进行主动上报异常的方式
33 | // try {
34 | // myrUndefinedFunctionInAsyncFunction();
35 | // } catch (e) {
36 | // Sentry.captureException(e)
37 | // }
38 |
39 | // 测试 swan API 调用失败是否会上报
40 | // swan.getStorage({
41 | // success(res) {
42 | // console.log(res);
43 | // },
44 | // // fail(error) {
45 | // // console.log('API 调用失败: ', error);
46 | // // }
47 | // })
48 |
49 | // throw new Error(`TypeError: Cannot read property 'd' of undefined
50 | // at UserInfo (index.js:1069:8)
51 | // at Jd (vendors.js:27859:98)
52 | // at ve (vendors.js:27878:273)
53 | // at ue (vendors.js:27878:103)
54 | // at se (vendors.js:27877:178)
55 | // at ig (vendors.js:27962:63)
56 | // at hg (vendors.js:27934:22)
57 | // at bg (vendors.js:27933:366)
58 | // at Of (vendors.js:27927:214)
59 | // at eval (vendors.js:27825:115)
60 | // at push../node_modules/scheduler/cjs/scheduler.production.min.js.exports.unstable_runWithPriority (vendors.js:30541:438)
61 | // at hc (vendors.js:27824:325)
62 | // at kc (vendors.js:27825:61)
63 | // at F (vendors.js:27824:495)
64 | // at Wc (vendors.js:27918:155)
65 | // at Object.enqueueForceUpdate (vendors.js:27839:76)
66 | // at AppWrapper.push../node_modules/react/cjs/react.production.min.js.F.forceUpdate (vendors.js:29646:456)
67 | // at AppWrapper.mount (taro.js:6414:14)
68 | // at Object.mount (taro.js:6459:15)
69 | // at Object.onLoad (taro.js:6016:19)
70 | // at tryCatch (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:21759)
71 | // at t.n.callMethod (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:27988)
72 | // at t.n.callLifetime (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:28250)
73 | // at t.n.onLoad (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:27063)
74 | // at swan (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:45935)
75 | // at eval (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:44306)
76 | // at yx (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:44316)
77 | // at WS.xx (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :15:47014)
78 | // at WS.emit (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :1:174337)
79 | // at Object.subscribeHandler (eval at window.loadTmaScript (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:293991), :1:176996)
80 | // at /Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:285984
81 | // at EventEmitter. (/Applications/bytedanceide.app/Contents/Resources/app.asar.unpacked/simulator-sdk/dist/preload/bgPreload.d23efdb4d49be26bcea4.js:277:285581)
82 | // at EventEmitter.emit (events.js:182:13)`);
83 | },
84 | onShow() {
85 | // 测试 Promise 中异常是否可以上报
86 | // new Promise((resovle, reject) => {
87 | // inPromiseFn();
88 | // resovle();
89 | // });
90 | // .then((res) => {
91 | // console.log(res);
92 | // }, (err) => {
93 | // console.log(err);
94 | // Sentry.captureException(err)
95 | // });
96 | },
97 | // 不需要显示调用 Sentry.captureException(error)
98 | onError(error) {
99 | // console.warn(error);
100 | // Sentry.captureException(error);
101 | },
102 | // onPageNotFound(res) {
103 | // console.warn(res);
104 | // }
105 | });
106 |
--------------------------------------------------------------------------------
/examples/swan/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window": {
7 | "backgroundTextStyle": "light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "navigationBarTitleText": "swan",
10 | "navigationBarTextStyle": "black"
11 | },
12 | "sitemapLocation": "sitemap.json"
13 | }
14 |
--------------------------------------------------------------------------------
/examples/swan/pages/index/index.css:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .userinfo {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | }
7 |
8 | .userinfo-avatar {
9 | width: 128rpx;
10 | height: 128rpx;
11 | margin: 20rpx;
12 | border-radius: 50%;
13 | }
14 |
15 | .userinfo-nickname {
16 | color: #aaa;
17 | }
18 |
19 | .usermotto {
20 | margin-top: 200px;
21 | }
--------------------------------------------------------------------------------
/examples/swan/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp();
4 |
5 | Page({
6 | data: {
7 | motto: "Hello World",
8 | userInfo: {},
9 | hasUserInfo: false,
10 | canIUse: swan.canIUse("button.open-type.getUserInfo")
11 | },
12 | //事件处理函数
13 | bindViewTap: function () {
14 | swan.navigateTo({
15 | url: "../logs/logs"
16 | });
17 | },
18 | onLoad: function () {
19 | throw new Error("吼吼吼");
20 | },
21 | getUserInfo: function (e) {
22 | console.log(e);
23 | app.globalData.userInfo = e.detail.userInfo;
24 | this.setData({
25 | userInfo: e.detail.userInfo,
26 | hasUserInfo: true
27 | });
28 | },
29 | // async onLoad() {
30 | // try {
31 | // // 所有业务逻辑
32 | // } catch (e) {
33 | // Sentry.captureException(e)
34 | // }
35 | // }
36 | });
37 |
--------------------------------------------------------------------------------
/examples/swan/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/examples/swan/pages/index/index.swan:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{userInfo.nickName}}
7 |
8 |
9 |
10 | {{motto}}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/swan/pages/logs/logs.css:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/swan/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const util = require("../../utils/util.js");
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad: function() {
9 | this.setData({
10 | logs: (wx.getStorageSync("logs") || []).map(log => {
11 | return util.formatTime(new Date(log));
12 | })
13 | });
14 |
15 | throw new Error("this is a getCurrentPages test.");
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/examples/swan/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志",
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/examples/swan/pages/logs/logs.swan:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | {{index + 1}}. {{log}}
7 |
8 |
--------------------------------------------------------------------------------
/examples/swan/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": false,
8 | "es6": true,
9 | "enhance": true,
10 | "postcss": true,
11 | "preloadBackgroundData": false,
12 | "minified": true,
13 | "newFeature": true,
14 | "coverView": true,
15 | "nodeModules": true,
16 | "autoAudits": false,
17 | "showShadowRootInWxmlPanel": true,
18 | "scopeDataCheck": false,
19 | "uglifyFileName": false,
20 | "checkInvalidKey": true,
21 | "checkSiteMap": false,
22 | "uploadWithSourceMap": true,
23 | "useMultiFrameRuntime": false,
24 | "useApiHook": true,
25 | "babelSetting": {
26 | "ignore": [],
27 | "disablePlugins": [],
28 | "outputPath": ""
29 | },
30 | "useIsolateContext": true,
31 | "useCompilerModule": true,
32 | "userConfirmedUseCompilerModuleSwitch": false,
33 | "packNpmManually": false,
34 | "packNpmRelationList": [],
35 | "minifyWXSS": true
36 | },
37 | "compileType": "miniprogram",
38 | "libVersion": "2.11.3",
39 | "appid": "wx5a8ef205c8e52b13",
40 | "projectname": "sentry-miniapp-test",
41 | "debugOptions": {
42 | "hidedInDevtools": []
43 | },
44 | "isGameTourist": false,
45 | "simulatorType": "wechat",
46 | "simulatorPluginLibVersion": {},
47 | "condition": {
48 | "search": {
49 | "list": []
50 | },
51 | "conversation": {
52 | "list": []
53 | },
54 | "plugin": {
55 | "list": []
56 | },
57 | "game": {
58 | "currentL": -1,
59 | "list": []
60 | },
61 | "gamePlugin": {
62 | "list": []
63 | },
64 | "miniprogram": {
65 | "list": [
66 | {
67 | "id": 0,
68 | "name": "测试页面不存在",
69 | "pathName": "pages/logs/index",
70 | "query": "name=apple",
71 | "scene": null
72 | },
73 | {
74 | "id": -1,
75 | "name": "pages/index/index",
76 | "pathName": "pages/index/index",
77 | "query": "name=test&sex=1",
78 | "scene": null
79 | }
80 | ]
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/examples/swan/project.swan.json:
--------------------------------------------------------------------------------
1 | {
2 | "host": "baiduboxapp",
3 | "projectname": "swan",
4 | "setting": {
5 | "urlCheck": true
6 | },
7 | "swan": {
8 | "baiduboxapp": {
9 | "extensionJsVersion": "1.19.2",
10 | "swanJsVersion": "3.230.27-rc"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/examples/swan/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/examples/swan/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------
/examples/ttapp/app.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from "./vendor/sentry-miniapp.tt.min";
2 |
3 | console.log(Sentry);
4 | // 初始化 Sentry
5 | Sentry.init({
6 | platform: "tt",
7 | dsn: "https://cd7838549e0d43e1bd6a83594ac7acb8@sentry.io/1534849"
8 | });
9 |
10 | App({
11 | onLaunch: function() {
12 | // myUndefinedFunction();
13 | },
14 | // 头条暂时不支持使用 async
15 | // async onShow() {
16 | // // 测试 async 函数中异常是否可以被 onError 捕获 ---> onError 无法捕获
17 | // const ret = await new Promise((resolve) => {
18 | // setTimeout(() => {
19 | // resolve('this is await ret.');
20 | // }, 2000);
21 | // });
22 | // console.log(ret);
23 | // myrUndefinedFunctionInAsyncFunction();
24 | // },
25 | onShow() {
26 | // 测试 Promise 中异常是否可以被 onError 捕获 ---> onError 无法捕获
27 | const ret = new Promise(resolve => {
28 | myrUndefinedFunctionInAsyncFunction();
29 | }).then(
30 | res => {
31 | console.log(res);
32 | },
33 | error => {
34 | console.log(error);
35 | Sentry.captureException(error);
36 | }
37 | );
38 | },
39 | onError(error) {
40 | console.warn("onError", error);
41 | Sentry.captureException(error);
42 | }
43 | });
44 |
--------------------------------------------------------------------------------
/examples/ttapp/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages":[
3 | "pages/index/index"
4 | ],
5 | "window":{
6 | "backgroundTextStyle":"light",
7 | "navigationBarBackgroundColor": "#fff",
8 | "navigationBarTitleText": "Mini Program",
9 | "navigationBarTextStyle":"black"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/ttapp/app.ttss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/ttapp/app.ttss
--------------------------------------------------------------------------------
/examples/ttapp/pages/index/index.js:
--------------------------------------------------------------------------------
1 | const app = getApp()
2 |
3 | Page({
4 | data: {
5 |
6 | },
7 | onLoad: function () {
8 | console.log('Welcome to Mini Code')
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/examples/ttapp/pages/index/index.ttml:
--------------------------------------------------------------------------------
1 | Welcome to Mini Program
--------------------------------------------------------------------------------
/examples/ttapp/pages/index/index.ttss:
--------------------------------------------------------------------------------
1 | .intro {
2 | margin: 30px;
3 | text-align: center;
4 | }
--------------------------------------------------------------------------------
/examples/ttapp/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "setting": {
3 | "urlCheck": false,
4 | "es6": true,
5 | "postcss": true,
6 | "minified": true,
7 | "newFeature": true
8 | },
9 | "appid": "testappId",
10 | "projectname": "ttapp"
11 | }
--------------------------------------------------------------------------------
/examples/weapp/README.md:
--------------------------------------------------------------------------------
1 | # 小程序示例项目
2 |
--------------------------------------------------------------------------------
/examples/weapp/app.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from "./vendor/sentry-miniapp.wx.min";
2 | // 初始化 Sentry
3 | Sentry.init({
4 | dsn: "https://47703e01ba4344b8b252c15e8fd980fd@sentry.io/1528228",
5 | });
6 |
7 | App({
8 | globalData: {
9 | userInfo: null,
10 | },
11 | onLaunch() {
12 | // 展示本地存储能力
13 | var logs = wx.getStorageSync("logs") || [];
14 | logs.unshift(Date.now());
15 | wx.setStorageSync("logs", logs);
16 |
17 | // 登录
18 | wx.login({
19 | success: (res) => {
20 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
21 | },
22 | });
23 |
24 | // 获取用户信息
25 | wx.getSetting({
26 | success: (res) => {
27 | if (res.authSetting["scope.userInfo"]) {
28 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
29 | wx.getUserInfo({
30 | success: (res) => {
31 | // 可以将 res 发送给后台解码出 unionId
32 | this.globalData.userInfo = res.userInfo;
33 | // console.log(res.userInfo);
34 | const {
35 | nickName,
36 | country,
37 | province,
38 | city,
39 | avatarUrl,
40 | } = res.userInfo;
41 |
42 | Sentry.setUser({
43 | id: nickName,
44 | });
45 | Sentry.setTag("country", country);
46 | Sentry.setExtra("province", province);
47 | Sentry.setExtras({
48 | city,
49 | avatarUrl,
50 | });
51 | // Sentry.captureException(
52 | // new Error("Good good stydy, day day up!")
53 | // );
54 | // Sentry.captureMessage("Hello, sentry-miniapp!");
55 |
56 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
57 | // 所以此处加入 callback 以防止这种情况
58 | if (this.userInfoReadyCallback) {
59 | this.userInfoReadyCallback(res);
60 | }
61 | },
62 | });
63 | }
64 | },
65 | });
66 |
67 | // const systemInfo = wx.getSystemInfoSync();
68 | // console.log(systemInfo);
69 |
70 | // Sentry.captureException(new Error("test"));
71 | // Sentry.captureException(new Error({ errMsg: "test" }));
72 | Sentry.captureException(new Error(JSON.stringify({ errMsg: "test" })));
73 |
74 | // 测试 异常是否可以上报
75 | // throw new Error("lalalalalala");
76 | // myUndefinedFunction();
77 |
78 | // 一种可以在 async 函数中进行主动上报异常的方式
79 | // try {
80 | // myrUndefinedFunctionInAsyncFunction();
81 | // } catch (e) {
82 | // Sentry.captureException(e)
83 | // }
84 |
85 | // 测试 WX API 调用失败是否会上报
86 | // wx.getStorage({
87 | // success(res) {
88 | // console.log(res);
89 | // },
90 | // fail(error) {
91 | // console.log("API 调用失败: ", error);
92 | // },
93 | // });
94 |
95 | // wx.showToast({
96 | // content: "test",
97 | // fail(err) {
98 | // console.log(err);
99 | // },
100 | // });
101 | // wx.chooseImage({
102 | // fail(err) {
103 | // console.log(err);
104 | // },
105 | // });
106 | },
107 | onShow() {
108 | // 测试 Promise 中异常是否可以上报
109 | // new Promise((resovle, reject) => {
110 | // inPromiseFn();
111 | // resovle();
112 | // });
113 | // .then((res) => {
114 | // console.log(res);
115 | // }, (err) => {
116 | // console.log(err);
117 | // Sentry.captureException(err)
118 | // });
119 | },
120 | // 不需要显示调用 Sentry.captureException(error)
121 | onError(error) {
122 | console.warn(error);
123 | // Sentry.captureException(error);
124 | },
125 | // onPageNotFound(res) {
126 | // console.warn(res);
127 | // }
128 | });
129 |
--------------------------------------------------------------------------------
/examples/weapp/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window": {
7 | "backgroundTextStyle": "light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "navigationBarTitleText": "WeChat",
10 | "navigationBarTextStyle": "black"
11 | },
12 | "sitemapLocation": "sitemap.json"
13 | }
--------------------------------------------------------------------------------
/examples/weapp/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | padding: 200rpx 0;
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/weapp/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp();
4 |
5 | Page({
6 | data: {
7 | motto: "Hello World",
8 | userInfo: {},
9 | hasUserInfo: false,
10 | canIUse: wx.canIUse("button.open-type.getUserInfo")
11 | },
12 | //事件处理函数
13 | bindViewTap: function () {
14 | wx.navigateTo({
15 | url: "../logs/logs"
16 | });
17 | },
18 | onLoad: function () {
19 | if (app.globalData.userInfo) {
20 | this.setData({
21 | userInfo: app.globalData.userInfo,
22 | hasUserInfo: true
23 | });
24 | } else if (this.data.canIUse) {
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | app.userInfoReadyCallback = res => {
28 | this.setData({
29 | userInfo: res.userInfo,
30 | hasUserInfo: true
31 | });
32 | };
33 | } else {
34 | // 在没有 open-type=getUserInfo 版本的兼容处理
35 | wx.getUserInfo({
36 | success: res => {
37 | app.globalData.userInfo = res.userInfo;
38 | this.setData({
39 | userInfo: res.userInfo,
40 | hasUserInfo: true
41 | });
42 | }
43 | });
44 | }
45 |
46 | // throw new Error("吼吼吼");
47 | },
48 | getUserInfo: function (e) {
49 | console.log(e);
50 | app.globalData.userInfo = e.detail.userInfo;
51 | this.setData({
52 | userInfo: e.detail.userInfo,
53 | hasUserInfo: true
54 | });
55 | },
56 | // async onLoad() {
57 | // try {
58 | // // 所有业务逻辑
59 | // } catch (e) {
60 | // Sentry.captureException(e)
61 | // }
62 | // }
63 | });
64 |
--------------------------------------------------------------------------------
/examples/weapp/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/examples/weapp/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{userInfo.nickName}}
8 |
9 |
10 |
11 | {{motto}}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/weapp/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .userinfo {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | }
7 |
8 | .userinfo-avatar {
9 | width: 128rpx;
10 | height: 128rpx;
11 | margin: 20rpx;
12 | border-radius: 50%;
13 | }
14 |
15 | .userinfo-nickname {
16 | color: #aaa;
17 | }
18 |
19 | .usermotto {
20 | margin-top: 200px;
21 | }
--------------------------------------------------------------------------------
/examples/weapp/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const util = require("../../utils/util.js");
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad: function() {
9 | this.setData({
10 | logs: (wx.getStorageSync("logs") || []).map(log => {
11 | return util.formatTime(new Date(log));
12 | })
13 | });
14 |
15 | throw new Error("this is a getCurrentPages test.");
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/examples/weapp/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志",
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/examples/weapp/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | {{index + 1}}. {{log}}
7 |
8 |
--------------------------------------------------------------------------------
/examples/weapp/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/weapp/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
3 | "packOptions": {
4 | "ignore": [],
5 | "include": []
6 | },
7 | "setting": {
8 | "urlCheck": false,
9 | "es6": true,
10 | "enhance": true,
11 | "postcss": true,
12 | "preloadBackgroundData": false,
13 | "minified": true,
14 | "newFeature": true,
15 | "coverView": true,
16 | "nodeModules": true,
17 | "autoAudits": false,
18 | "showShadowRootInWxmlPanel": true,
19 | "scopeDataCheck": false,
20 | "uglifyFileName": false,
21 | "checkInvalidKey": true,
22 | "checkSiteMap": false,
23 | "uploadWithSourceMap": true,
24 | "useMultiFrameRuntime": true,
25 | "useApiHook": true,
26 | "babelSetting": {
27 | "ignore": [],
28 | "disablePlugins": [],
29 | "outputPath": ""
30 | },
31 | "useIsolateContext": true,
32 | "useCompilerModule": true,
33 | "userConfirmedUseCompilerModuleSwitch": false,
34 | "userConfirmedBundleSwitch": false,
35 | "packNpmManually": false,
36 | "packNpmRelationList": [],
37 | "minifyWXSS": true,
38 | "bundle": false,
39 | "lazyloadPlaceholderEnable": false,
40 | "useStaticServer": true,
41 | "showES6CompileOption": false,
42 | "disableUseStrict": false,
43 | "useCompilerPlugins": false,
44 | "minifyWXML": true
45 | },
46 | "compileType": "miniprogram",
47 | "libVersion": "2.11.3",
48 | "appid": "wx5a8ef205c8e52b13",
49 | "projectname": "sentry-miniapp-test",
50 | "simulatorType": "wechat",
51 | "simulatorPluginLibVersion": {},
52 | "condition": {
53 | "miniprogram": {
54 | "list": [
55 | {
56 | "name": "测试页面不存在",
57 | "pathName": "pages/logs/index",
58 | "query": "name=apple",
59 | "scene": null
60 | },
61 | {
62 | "name": "pages/index/index",
63 | "pathName": "pages/index/index",
64 | "query": "name=test&sex=1",
65 | "scene": null
66 | }
67 | ]
68 | }
69 | },
70 | "editorSetting": {
71 | "tabIndent": "insertSpaces",
72 | "tabSize": 2
73 | }
74 | }
--------------------------------------------------------------------------------
/examples/weapp/project.private.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectname": "weapp",
3 | "setting": {
4 | "compileHotReLoad": true
5 | },
6 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
7 | }
--------------------------------------------------------------------------------
/examples/weapp/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/examples/weapp/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------
/examples/wegame/README.md:
--------------------------------------------------------------------------------
1 | ## quickstart
2 |
3 | ## 源码目录介绍
4 | ```
5 | ./js
6 | ├── base // 定义游戏开发基础类
7 | │ ├── animatoin.js // 帧动画的简易实现
8 | │ ├── pool.js // 对象池的简易实现
9 | │ └── sprite.js // 游戏基本元素精灵类
10 | ├── libs
11 | │ ├── symbol.js // ES6 Symbol简易兼容
12 | │ └── weapp-adapter.js // 小游戏适配器
13 | ├── npc
14 | │ └── enemy.js // 敌机类
15 | ├── player
16 | │ ├── bullet.js // 子弹类
17 | │ └── index.js // 玩家类
18 | ├── runtime
19 | │ ├── background.js // 背景类
20 | │ ├── gameinfo.js // 用于展示分数和结算界面
21 | │ └── music.js // 全局音效管理器
22 | ├── databus.js // 管控游戏状态
23 | └── main.js // 游戏入口主函数
24 |
25 | ```
--------------------------------------------------------------------------------
/examples/wegame/audio/bgm.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/audio/bgm.mp3
--------------------------------------------------------------------------------
/examples/wegame/audio/boom.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/audio/boom.mp3
--------------------------------------------------------------------------------
/examples/wegame/audio/bullet.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/audio/bullet.mp3
--------------------------------------------------------------------------------
/examples/wegame/game.js:
--------------------------------------------------------------------------------
1 | import "./js/libs/weapp-adapter";
2 | import "./js/libs/symbol";
3 |
4 | import * as Sentry from "./vendor/sentry-miniapp.wx.min";
5 | const {
6 | Integrations: { Router },
7 | } = Sentry;
8 |
9 | import Main from "./js/main";
10 |
11 | // console.log(Sentry);
12 |
13 | Sentry.init({
14 | dsn: "https://47703e01ba4344b8b252c15e8fd980fd@sentry.io/1528228",
15 | integrations: [new Router({ enable: false })],
16 | });
17 |
18 | new Main();
19 |
20 | Sentry.captureException("123");
21 |
22 | // throw new Error("wegame test");
23 |
--------------------------------------------------------------------------------
/examples/wegame/game.json:
--------------------------------------------------------------------------------
1 | {
2 | "deviceOrientation": "portrait"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/wegame/images/Common.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/Common.png
--------------------------------------------------------------------------------
/examples/wegame/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/bg.jpg
--------------------------------------------------------------------------------
/examples/wegame/images/bullet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/bullet.png
--------------------------------------------------------------------------------
/examples/wegame/images/enemy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/enemy.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion1.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion10.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion11.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion12.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion13.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion14.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion15.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion16.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion17.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion18.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion19.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion2.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion3.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion4.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion5.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion6.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion7.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion8.png
--------------------------------------------------------------------------------
/examples/wegame/images/explosion9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/explosion9.png
--------------------------------------------------------------------------------
/examples/wegame/images/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizhiyao/sentry-miniapp/ada8cbe160c0d61478ea0c8a3d85146f14a42509/examples/wegame/images/hero.png
--------------------------------------------------------------------------------
/examples/wegame/js/base/animation.js:
--------------------------------------------------------------------------------
1 | import Sprite from './sprite'
2 | import DataBus from '../databus'
3 |
4 | let databus = new DataBus()
5 |
6 | const __ = {
7 | timer: Symbol('timer'),
8 | }
9 |
10 | /**
11 | * 简易的帧动画类实现
12 | */
13 | export default class Animation extends Sprite {
14 | constructor(imgSrc, width, height) {
15 | super(imgSrc, width, height)
16 |
17 | // 当前动画是否播放中
18 | this.isPlaying = false
19 |
20 | // 动画是否需要循环播放
21 | this.loop = false
22 |
23 | // 每一帧的时间间隔
24 | this.interval = 1000 / 60
25 |
26 | // 帧定时器
27 | this[__.timer] = null
28 |
29 | // 当前播放的帧
30 | this.index = -1
31 |
32 | // 总帧数
33 | this.count = 0
34 |
35 | // 帧图片集合
36 | this.imgList = []
37 |
38 | /**
39 | * 推入到全局动画池里面
40 | * 便于全局绘图的时候遍历和绘制当前动画帧
41 | */
42 | databus.animations.push(this)
43 | }
44 |
45 | /**
46 | * 初始化帧动画的所有帧
47 | * 为了简单,只支持一个帧动画
48 | */
49 | initFrames(imgList) {
50 | imgList.forEach((imgSrc) => {
51 | let img = new Image()
52 | img.src = imgSrc
53 |
54 | this.imgList.push(img)
55 | })
56 |
57 | this.count = imgList.length
58 | }
59 |
60 | // 将播放中的帧绘制到canvas上
61 | aniRender(ctx) {
62 | ctx.drawImage(
63 | this.imgList[this.index],
64 | this.x,
65 | this.y,
66 | this.width * 1.2,
67 | this.height * 1.2
68 | )
69 | }
70 |
71 | // 播放预定的帧动画
72 | playAnimation(index = 0, loop = false) {
73 | // 动画播放的时候精灵图不再展示,播放帧动画的具体帧
74 | this.visible = false
75 |
76 | this.isPlaying = true
77 | this.loop = loop
78 |
79 | this.index = index
80 |
81 | if ( this.interval > 0 && this.count ) {
82 | this[__.timer] = setInterval(
83 | this.frameLoop.bind(this),
84 | this.interval
85 | )
86 | }
87 | }
88 |
89 | // 停止帧动画播放
90 | stop() {
91 | this.isPlaying = false
92 |
93 | if ( this[__.timer] )
94 | clearInterval(this[__.timer])
95 | }
96 |
97 | // 帧遍历
98 | frameLoop() {
99 | this.index++
100 |
101 | if ( this.index > this.count - 1 ) {
102 | if ( this.loop ) {
103 | this.index = 0
104 | }
105 |
106 | else {
107 | this.index--
108 | this.stop()
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/examples/wegame/js/base/pool.js:
--------------------------------------------------------------------------------
1 | const __ = {
2 | poolDic: Symbol('poolDic')
3 | }
4 |
5 | /**
6 | * 简易的对象池实现
7 | * 用于对象的存贮和重复使用
8 | * 可以有效减少对象创建开销和避免频繁的垃圾回收
9 | * 提高游戏性能
10 | */
11 | export default class Pool {
12 | constructor() {
13 | this[__.poolDic] = {}
14 | }
15 |
16 | /**
17 | * 根据对象标识符
18 | * 获取对应的对象池
19 | */
20 | getPoolBySign(name) {
21 | return this[__.poolDic][name] || ( this[__.poolDic][name] = [] )
22 | }
23 |
24 | /**
25 | * 根据传入的对象标识符,查询对象池
26 | * 对象池为空创建新的类,否则从对象池中取
27 | */
28 | getItemByClass(name, className) {
29 | let pool = this.getPoolBySign(name)
30 |
31 | let result = ( pool.length
32 | ? pool.shift()
33 | : new className() )
34 |
35 | return result
36 | }
37 |
38 | /**
39 | * 将对象回收到对象池
40 | * 方便后续继续使用
41 | */
42 | recover(name, instance) {
43 | this.getPoolBySign(name).push(instance)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/wegame/js/base/sprite.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 游戏基础的精灵类
3 | */
4 | export default class Sprite {
5 | constructor(imgSrc = '', width= 0, height = 0, x = 0, y = 0) {
6 | this.img = new Image()
7 | this.img.src = imgSrc
8 |
9 | this.width = width
10 | this.height = height
11 |
12 | this.x = x
13 | this.y = y
14 |
15 | this.visible = true
16 | }
17 |
18 | /**
19 | * 将精灵图绘制在canvas上
20 | */
21 | drawToCanvas(ctx) {
22 | if ( !this.visible )
23 | return
24 |
25 | ctx.drawImage(
26 | this.img,
27 | this.x,
28 | this.y,
29 | this.width,
30 | this.height
31 | )
32 | }
33 |
34 | /**
35 | * 简单的碰撞检测定义:
36 | * 另一个精灵的中心点处于本精灵所在的矩形内即可
37 | * @param{Sprite} sp: Sptite的实例
38 | */
39 | isCollideWith(sp) {
40 | let spX = sp.x + sp.width / 2
41 | let spY = sp.y + sp.height / 2
42 |
43 | if ( !this.visible || !sp.visible )
44 | return false
45 |
46 | return !!( spX >= this.x
47 | && spX <= this.x + this.width
48 | && spY >= this.y
49 | && spY <= this.y + this.height )
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/wegame/js/databus.js:
--------------------------------------------------------------------------------
1 | import Pool from './base/pool'
2 |
3 | let instance
4 |
5 | /**
6 | * 全局状态管理器
7 | */
8 | export default class DataBus {
9 | constructor() {
10 | if ( instance )
11 | return instance
12 |
13 | instance = this
14 |
15 | this.pool = new Pool()
16 |
17 | this.reset()
18 | }
19 |
20 | reset() {
21 | this.frame = 0
22 | this.score = 0
23 | this.bullets = []
24 | this.enemys = []
25 | this.animations = []
26 | this.gameOver = false
27 | }
28 |
29 | /**
30 | * 回收敌人,进入对象池
31 | * 此后不进入帧循环
32 | */
33 | removeEnemey(enemy) {
34 | let temp = this.enemys.shift()
35 |
36 | temp.visible = false
37 |
38 | this.pool.recover('enemy', enemy)
39 | }
40 |
41 | /**
42 | * 回收子弹,进入对象池
43 | * 此后不进入帧循环
44 | */
45 | removeBullets(bullet) {
46 | let temp = this.bullets.shift()
47 |
48 | temp.visible = false
49 |
50 | this.pool.recover('bullet', bullet)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/wegame/js/libs/symbol.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 对于ES6中Symbol的极简兼容
3 | * 方便模拟私有变量
4 | */
5 |
6 | let Symbol = window.Symbol
7 | let idCounter = 0
8 |
9 | if (!Symbol) {
10 | Symbol = function Symbol(key) {
11 | return `__${key}_${Math.floor(Math.random() * 1e9)}_${++idCounter}__`
12 | }
13 |
14 | Symbol.iterator = Symbol('Symbol.iterator')
15 | }
16 |
17 | window.Symbol = Symbol
18 |
--------------------------------------------------------------------------------
/examples/wegame/js/main.js:
--------------------------------------------------------------------------------
1 | import Player from './player/index'
2 | import Enemy from './npc/enemy'
3 | import BackGround from './runtime/background'
4 | import GameInfo from './runtime/gameinfo'
5 | import Music from './runtime/music'
6 | import DataBus from './databus'
7 |
8 | let ctx = canvas.getContext('2d')
9 | let databus = new DataBus()
10 |
11 | /**
12 | * 游戏主函数
13 | */
14 | export default class Main {
15 | constructor() {
16 | // 维护当前requestAnimationFrame的id
17 | this.aniId = 0
18 |
19 | this.restart()
20 | }
21 |
22 | restart() {
23 | databus.reset()
24 |
25 | canvas.removeEventListener(
26 | 'touchstart',
27 | this.touchHandler
28 | )
29 |
30 | this.bg = new BackGround(ctx)
31 | this.player = new Player(ctx)
32 | this.gameinfo = new GameInfo()
33 | this.music = new Music()
34 |
35 | this.bindLoop = this.loop.bind(this)
36 | this.hasEventBind = false
37 |
38 | // 清除上一局的动画
39 | window.cancelAnimationFrame(this.aniId);
40 |
41 | this.aniId = window.requestAnimationFrame(
42 | this.bindLoop,
43 | canvas
44 | )
45 | }
46 |
47 | /**
48 | * 随着帧数变化的敌机生成逻辑
49 | * 帧数取模定义成生成的频率
50 | */
51 | enemyGenerate() {
52 | if ( databus.frame % 30 === 0 ) {
53 | let enemy = databus.pool.getItemByClass('enemy', Enemy)
54 | enemy.init(6)
55 | databus.enemys.push(enemy)
56 | }
57 | }
58 |
59 | // 全局碰撞检测
60 | collisionDetection() {
61 | let that = this
62 |
63 | databus.bullets.forEach((bullet) => {
64 | for ( let i = 0, il = databus.enemys.length; i < il;i++ ) {
65 | let enemy = databus.enemys[i]
66 |
67 | if ( !enemy.isPlaying && enemy.isCollideWith(bullet) ) {
68 | enemy.playAnimation()
69 | that.music.playExplosion()
70 |
71 | bullet.visible = false
72 | databus.score += 1
73 |
74 | break
75 | }
76 | }
77 | })
78 |
79 | for ( let i = 0, il = databus.enemys.length; i < il;i++ ) {
80 | let enemy = databus.enemys[i]
81 |
82 | if ( this.player.isCollideWith(enemy) ) {
83 | databus.gameOver = true
84 |
85 | break
86 | }
87 | }
88 | }
89 |
90 | // 游戏结束后的触摸事件处理逻辑
91 | touchEventHandler(e) {
92 | e.preventDefault()
93 |
94 | let x = e.touches[0].clientX
95 | let y = e.touches[0].clientY
96 |
97 | let area = this.gameinfo.btnArea
98 |
99 | if ( x >= area.startX
100 | && x <= area.endX
101 | && y >= area.startY
102 | && y <= area.endY )
103 | this.restart()
104 | }
105 |
106 | /**
107 | * canvas重绘函数
108 | * 每一帧重新绘制所有的需要展示的元素
109 | */
110 | render() {
111 | ctx.clearRect(0, 0, canvas.width, canvas.height)
112 |
113 | this.bg.render(ctx)
114 |
115 | databus.bullets
116 | .concat(databus.enemys)
117 | .forEach((item) => {
118 | item.drawToCanvas(ctx)
119 | })
120 |
121 | this.player.drawToCanvas(ctx)
122 |
123 | databus.animations.forEach((ani) => {
124 | if ( ani.isPlaying ) {
125 | ani.aniRender(ctx)
126 | }
127 | })
128 |
129 | this.gameinfo.renderGameScore(ctx, databus.score)
130 |
131 | // 游戏结束停止帧循环
132 | if ( databus.gameOver ) {
133 | this.gameinfo.renderGameOver(ctx, databus.score)
134 |
135 | if ( !this.hasEventBind ) {
136 | this.hasEventBind = true
137 | this.touchHandler = this.touchEventHandler.bind(this)
138 | canvas.addEventListener('touchstart', this.touchHandler)
139 | }
140 | }
141 | }
142 |
143 | // 游戏逻辑更新主函数
144 | update() {
145 | if ( databus.gameOver )
146 | return;
147 |
148 | this.bg.update()
149 |
150 | databus.bullets
151 | .concat(databus.enemys)
152 | .forEach((item) => {
153 | item.update()
154 | })
155 |
156 | this.enemyGenerate()
157 |
158 | this.collisionDetection()
159 |
160 | if ( databus.frame % 20 === 0 ) {
161 | this.player.shoot()
162 | this.music.playShoot()
163 | }
164 | }
165 |
166 | // 实现游戏帧循环
167 | loop() {
168 | databus.frame++
169 |
170 | this.update()
171 | this.render()
172 |
173 | this.aniId = window.requestAnimationFrame(
174 | this.bindLoop,
175 | canvas
176 | )
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/examples/wegame/js/npc/enemy.js:
--------------------------------------------------------------------------------
1 | import Animation from '../base/animation'
2 | import DataBus from '../databus'
3 |
4 | const ENEMY_IMG_SRC = 'images/enemy.png'
5 | const ENEMY_WIDTH = 60
6 | const ENEMY_HEIGHT = 60
7 |
8 | const __ = {
9 | speed: Symbol('speed')
10 | }
11 |
12 | let databus = new DataBus()
13 |
14 | function rnd(start, end){
15 | return Math.floor(Math.random() * (end - start) + start)
16 | }
17 |
18 | export default class Enemy extends Animation {
19 | constructor() {
20 | super(ENEMY_IMG_SRC, ENEMY_WIDTH, ENEMY_HEIGHT)
21 |
22 | this.initExplosionAnimation()
23 | }
24 |
25 | init(speed) {
26 | this.x = rnd(0, window.innerWidth - ENEMY_WIDTH)
27 | this.y = -this.height
28 |
29 | this[__.speed] = speed
30 |
31 | this.visible = true
32 | }
33 |
34 | // 预定义爆炸的帧动画
35 | initExplosionAnimation() {
36 | let frames = []
37 |
38 | const EXPLO_IMG_PREFIX = 'images/explosion'
39 | const EXPLO_FRAME_COUNT = 19
40 |
41 | for ( let i = 0;i < EXPLO_FRAME_COUNT;i++ ) {
42 | frames.push(EXPLO_IMG_PREFIX + (i + 1) + '.png')
43 | }
44 |
45 | this.initFrames(frames)
46 | }
47 |
48 | // 每一帧更新子弹位置
49 | update() {
50 | this.y += this[__.speed]
51 |
52 | // 对象回收
53 | if ( this.y > window.innerHeight + this.height )
54 | databus.removeEnemey(this)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/wegame/js/player/bullet.js:
--------------------------------------------------------------------------------
1 | import Sprite from '../base/sprite'
2 | import DataBus from '../databus'
3 |
4 | const BULLET_IMG_SRC = 'images/bullet.png'
5 | const BULLET_WIDTH = 16
6 | const BULLET_HEIGHT = 30
7 |
8 | const __ = {
9 | speed: Symbol('speed')
10 | }
11 |
12 | let databus = new DataBus()
13 |
14 | export default class Bullet extends Sprite {
15 | constructor() {
16 | super(BULLET_IMG_SRC, BULLET_WIDTH, BULLET_HEIGHT)
17 | }
18 |
19 | init(x, y, speed) {
20 | this.x = x
21 | this.y = y
22 |
23 | this[__.speed] = speed
24 |
25 | this.visible = true
26 | }
27 |
28 | // 每一帧更新子弹位置
29 | update() {
30 | this.y -= this[__.speed]
31 |
32 | // 超出屏幕外回收自身
33 | if ( this.y < -this.height )
34 | databus.removeBullets(this)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/wegame/js/player/index.js:
--------------------------------------------------------------------------------
1 | import Sprite from '../base/sprite'
2 | import Bullet from './bullet'
3 | import DataBus from '../databus'
4 |
5 | const screenWidth = window.innerWidth
6 | const screenHeight = window.innerHeight
7 |
8 | // 玩家相关常量设置
9 | const PLAYER_IMG_SRC = 'images/hero.png'
10 | const PLAYER_WIDTH = 80
11 | const PLAYER_HEIGHT = 80
12 |
13 | let databus = new DataBus()
14 |
15 | export default class Player extends Sprite {
16 | constructor() {
17 | super(PLAYER_IMG_SRC, PLAYER_WIDTH, PLAYER_HEIGHT)
18 |
19 | // 玩家默认处于屏幕底部居中位置
20 | this.x = screenWidth / 2 - this.width / 2
21 | this.y = screenHeight - this.height - 30
22 |
23 | // 用于在手指移动的时候标识手指是否已经在飞机上了
24 | this.touched = false
25 |
26 | this.bullets = []
27 |
28 | // 初始化事件监听
29 | this.initEvent()
30 | }
31 |
32 | /**
33 | * 当手指触摸屏幕的时候
34 | * 判断手指是否在飞机上
35 | * @param {Number} x: 手指的X轴坐标
36 | * @param {Number} y: 手指的Y轴坐标
37 | * @return {Boolean}: 用于标识手指是否在飞机上的布尔值
38 | */
39 | checkIsFingerOnAir(x, y) {
40 | const deviation = 30
41 |
42 | return !!( x >= this.x - deviation
43 | && y >= this.y - deviation
44 | && x <= this.x + this.width + deviation
45 | && y <= this.y + this.height + deviation )
46 | }
47 |
48 | /**
49 | * 根据手指的位置设置飞机的位置
50 | * 保证手指处于飞机中间
51 | * 同时限定飞机的活动范围限制在屏幕中
52 | */
53 | setAirPosAcrossFingerPosZ(x, y) {
54 | let disX = x - this.width / 2
55 | let disY = y - this.height / 2
56 |
57 | if ( disX < 0 )
58 | disX = 0
59 |
60 | else if ( disX > screenWidth - this.width )
61 | disX = screenWidth - this.width
62 |
63 | if ( disY <= 0 )
64 | disY = 0
65 |
66 | else if ( disY > screenHeight - this.height )
67 | disY = screenHeight - this.height
68 |
69 | this.x = disX
70 | this.y = disY
71 | }
72 |
73 | /**
74 | * 玩家响应手指的触摸事件
75 | * 改变战机的位置
76 | */
77 | initEvent() {
78 | canvas.addEventListener('touchstart', ((e) => {
79 | e.preventDefault()
80 |
81 | let x = e.touches[0].clientX
82 | let y = e.touches[0].clientY
83 |
84 | //
85 | if ( this.checkIsFingerOnAir(x, y) ) {
86 | this.touched = true
87 |
88 | this.setAirPosAcrossFingerPosZ(x, y)
89 | }
90 |
91 | }).bind(this))
92 |
93 | canvas.addEventListener('touchmove', ((e) => {
94 | e.preventDefault()
95 |
96 | let x = e.touches[0].clientX
97 | let y = e.touches[0].clientY
98 |
99 | if ( this.touched )
100 | this.setAirPosAcrossFingerPosZ(x, y)
101 |
102 | }).bind(this))
103 |
104 | canvas.addEventListener('touchend', ((e) => {
105 | e.preventDefault()
106 |
107 | this.touched = false
108 | }).bind(this))
109 | }
110 |
111 | /**
112 | * 玩家射击操作
113 | * 射击时机由外部决定
114 | */
115 | shoot() {
116 | let bullet = databus.pool.getItemByClass('bullet', Bullet)
117 |
118 | bullet.init(
119 | this.x + this.width / 2 - bullet.width / 2,
120 | this.y - 10,
121 | 10
122 | )
123 |
124 | databus.bullets.push(bullet)
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/examples/wegame/js/runtime/background.js:
--------------------------------------------------------------------------------
1 | import Sprite from '../base/sprite'
2 |
3 | const screenWidth = window.innerWidth
4 | const screenHeight = window.innerHeight
5 |
6 | const BG_IMG_SRC = 'images/bg.jpg'
7 | const BG_WIDTH = 512
8 | const BG_HEIGHT = 512
9 |
10 | /**
11 | * 游戏背景类
12 | * 提供update和render函数实现无限滚动的背景功能
13 | */
14 | export default class BackGround extends Sprite {
15 | constructor(ctx) {
16 | super(BG_IMG_SRC, BG_WIDTH, BG_HEIGHT)
17 |
18 | this.top = 0
19 |
20 | this.render(ctx)
21 | }
22 |
23 | update() {
24 | this.top += 2
25 |
26 | if ( this.top >= screenHeight )
27 | this.top = 0
28 | }
29 |
30 | /**
31 | * 背景图重绘函数
32 | * 绘制两张图片,两张图片大小和屏幕一致
33 | * 第一张漏出高度为top部分,其余的隐藏在屏幕上面
34 | * 第二张补全除了top高度之外的部分,其余的隐藏在屏幕下面
35 | */
36 | render(ctx) {
37 | ctx.drawImage(
38 | this.img,
39 | 0,
40 | 0,
41 | this.width,
42 | this.height,
43 | 0,
44 | -screenHeight + this.top,
45 | screenWidth,
46 | screenHeight
47 | )
48 |
49 | ctx.drawImage(
50 | this.img,
51 | 0,
52 | 0,
53 | this.width,
54 | this.height,
55 | 0,
56 | this.top,
57 | screenWidth,
58 | screenHeight
59 | )
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/examples/wegame/js/runtime/gameinfo.js:
--------------------------------------------------------------------------------
1 | const screenWidth = window.innerWidth
2 | const screenHeight = window.innerHeight
3 |
4 | let atlas = new Image()
5 | atlas.src = 'images/Common.png'
6 |
7 | export default class GameInfo {
8 | renderGameScore(ctx, score) {
9 | ctx.fillStyle = "#ffffff"
10 | ctx.font = "20px Arial"
11 |
12 | ctx.fillText(
13 | score,
14 | 10,
15 | 30
16 | )
17 | }
18 |
19 | renderGameOver(ctx, score) {
20 | ctx.drawImage(atlas, 0, 0, 119, 108, screenWidth / 2 - 150, screenHeight / 2 - 100, 300, 300)
21 |
22 | ctx.fillStyle = "#ffffff"
23 | ctx.font = "20px Arial"
24 |
25 | ctx.fillText(
26 | '游戏结束',
27 | screenWidth / 2 - 40,
28 | screenHeight / 2 - 100 + 50
29 | )
30 |
31 | ctx.fillText(
32 | '得分: ' + score,
33 | screenWidth / 2 - 40,
34 | screenHeight / 2 - 100 + 130
35 | )
36 |
37 | ctx.drawImage(
38 | atlas,
39 | 120, 6, 39, 24,
40 | screenWidth / 2 - 60,
41 | screenHeight / 2 - 100 + 180,
42 | 120, 40
43 | )
44 |
45 | ctx.fillText(
46 | '重新开始',
47 | screenWidth / 2 - 40,
48 | screenHeight / 2 - 100 + 205
49 | )
50 |
51 | /**
52 | * 重新开始按钮区域
53 | * 方便简易判断按钮点击
54 | */
55 | this.btnArea = {
56 | startX: screenWidth / 2 - 40,
57 | startY: screenHeight / 2 - 100 + 180,
58 | endX : screenWidth / 2 + 50,
59 | endY : screenHeight / 2 - 100 + 255
60 | }
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/examples/wegame/js/runtime/music.js:
--------------------------------------------------------------------------------
1 | let instance
2 |
3 | /**
4 | * 统一的音效管理器
5 | */
6 | export default class Music {
7 | constructor() {
8 | if ( instance )
9 | return instance
10 |
11 | instance = this
12 |
13 | this.bgmAudio = new Audio()
14 | this.bgmAudio.loop = true
15 | this.bgmAudio.src = 'audio/bgm.mp3'
16 |
17 | this.shootAudio = new Audio()
18 | this.shootAudio.src = 'audio/bullet.mp3'
19 |
20 | this.boomAudio = new Audio()
21 | this.boomAudio.src = 'audio/boom.mp3'
22 |
23 | this.playBgm()
24 | }
25 |
26 | playBgm() {
27 | this.bgmAudio.play()
28 | }
29 |
30 | playShoot() {
31 | this.shootAudio.currentTime = 0
32 | this.shootAudio.play()
33 | }
34 |
35 | playExplosion() {
36 | this.boomAudio.currentTime = 0
37 | this.boomAudio.play()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/wegame/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "setting": {
4 | "urlCheck": false,
5 | "es6": true,
6 | "postcss": true,
7 | "minified": true,
8 | "newFeature": true
9 | },
10 | "compileType": "game",
11 | "libVersion": "1.9.94",
12 | "appid": "wx5eaebb1160f63019",
13 | "projectname": "wegame",
14 | "simulatorType": "wechat",
15 | "simulatorPluginLibVersion": {},
16 | "condition": {
17 | "search": {
18 | "current": -1,
19 | "list": []
20 | },
21 | "conversation": {
22 | "current": -1,
23 | "list": []
24 | },
25 | "game": {
26 | "currentL": -1,
27 | "list": []
28 | },
29 | "miniprogram": {
30 | "current": -1,
31 | "list": []
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sentry-miniapp",
3 | "version": "0.12.0",
4 | "description": "用于小程序/小游戏平台的 Sentry SDK",
5 | "repository": "git://github.com/lizhiyao/sentry-miniapp.git",
6 | "homepage": "https://github.com/lizhiyao/sentry-miniapp",
7 | "miniprogram": "dist",
8 | "main": "dist/index.js",
9 | "module": "esm/index.js",
10 | "types": "dist/index.d.ts",
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1",
13 | "build:dist": "npm-run-all --parallel build:wx build:wxgame build:my build:tt build:dd build:qq build:swan",
14 | "build:wx": "webpack --config ./webpack.config.wx.js",
15 | "build:wxgame": "webpack --config ./webpack.config.wxgame.js",
16 | "build:my": "webpack --config ./webpack.config.my.js",
17 | "build:tt": "webpack --config ./webpack.config.tt.js",
18 | "build:dd": "webpack --config ./webpack.config.dd.js",
19 | "build:qq": "webpack --config ./webpack.config.qq.js",
20 | "build:swan": "webpack --config ./webpack.config.swan.js",
21 | "build:watch": "webpack --watch --config ./webpack.config.wx.js",
22 | "build": "tsc -p tsconfig.json",
23 | "build:esm": "tsc -p tsconfig.esm.json",
24 | "version": "node ./scripts/versionbump.js src/version.ts"
25 | },
26 | "keywords": [
27 | "sentry",
28 | "weapp",
29 | "miniapp",
30 | "Sentry SDK",
31 | "Sentry 小程序 SDK",
32 | "小程序 Sentry SDK",
33 | "异常监控",
34 | "异常上报",
35 | "小程序异常监控",
36 | "微信小程序",
37 | "支付宝小程序",
38 | "钉钉小程序",
39 | "字节跳动小程序"
40 | ],
41 | "author": "dancerlzy@gmail.com",
42 | "license": "BSD-3-Clause",
43 | "engines": {
44 | "node": ">=8"
45 | },
46 | "devDependencies": {
47 | "@sentry/typescript": "^5.3.0",
48 | "@types/node": "^12.7.1",
49 | "install": "^0.13.0",
50 | "miniprogram-api-typings": "^2.7.7-2",
51 | "npm": "^6.11.1",
52 | "npm-run-all": "^4.1.5",
53 | "replace-in-file": "^4.1.3",
54 | "ts-loader": "^6.0.4",
55 | "tslint": "^5.18.0",
56 | "typescript": "^3.5.3",
57 | "webpack": "^4.39.1",
58 | "webpack-cli": "^3.3.10",
59 | "webpack-merge": "^4.2.1"
60 | },
61 | "dependencies": {
62 | "@sentry/core": "6.19.7",
63 | "@sentry/types": "6.19.7",
64 | "@sentry/utils": "6.19.7",
65 | "tslib": "^1.10.0"
66 | }
67 | }
--------------------------------------------------------------------------------
/scripts/versionbump.js:
--------------------------------------------------------------------------------
1 | const replace = require('replace-in-file');
2 | const packageJson = require(`${process.cwd()}/package.json`);
3 |
4 | const files = process.argv.slice(2);
5 | if (files.length === 0) {
6 | console.error('Please provide files to bump');
7 | process.exit(1);
8 | }
9 |
10 | replace({
11 | files: files,
12 | from: /\d+\.\d+.\d+(?:-\w+(?:\.\w+)?)?/g,
13 | to: packageJson.version,
14 | })
15 | .then(([{ file, hasChanged }]) => {
16 | console.log('Modified files:', file, hasChanged);
17 | })
18 | .catch(error => {
19 | console.error('Error occurred:', error);
20 | process.exit(1);
21 | });
22 |
--------------------------------------------------------------------------------
/src/backend.ts:
--------------------------------------------------------------------------------
1 | import { BaseBackend } from "@sentry/core";
2 | import { Event, EventHint, Options, Severity, Transport } from "@sentry/types";
3 | import { addExceptionMechanism, resolvedSyncPromise } from '@sentry/utils';
4 |
5 | import { eventFromString, eventFromUnknownInput } from './eventbuilder';
6 | import { XHRTransport } from "./transports/index";
7 |
8 | /**
9 | * Configuration options for the Sentry Miniapp SDK.
10 | * Sentry Miniapp SDK 的配置选项。
11 | * @see MiniappClient for more information.
12 | */
13 | export interface MiniappOptions extends Options {
14 | /**
15 | * A pattern for error URLs which should not be sent to Sentry.
16 | * To whitelist certain errors instead, use {@link Options.whitelistUrls}.
17 | * By default, all errors will be sent.
18 | */
19 | blacklistUrls?: Array;
20 |
21 | /**
22 | * A pattern for error URLs which should exclusively be sent to Sentry.
23 | * This is the opposite of {@link Options.blacklistUrls}.
24 | * By default, all errors will be sent.
25 | */
26 | whitelistUrls?: Array;
27 | }
28 |
29 | /**
30 | * The Sentry Browser SDK Backend.
31 | * @hidden
32 | */
33 | export class MiniappBackend extends BaseBackend {
34 | /**
35 | * @inheritDoc
36 | */
37 | protected _setupTransport(): Transport {
38 | if (!this._options.dsn) {
39 | // We return the noop transport here in case there is no Dsn.
40 | return super._setupTransport();
41 | }
42 |
43 | const transportOptions = {
44 | ...this._options.transportOptions,
45 | dsn: this._options.dsn
46 | };
47 |
48 | if (this._options.transport) {
49 | return new this._options.transport(transportOptions);
50 | }
51 |
52 | return new XHRTransport(transportOptions);
53 | }
54 |
55 | /**
56 | * @inheritDoc
57 | */
58 | public eventFromException(exception: any, hint?: EventHint): PromiseLike {
59 | const syntheticException = (hint && hint.syntheticException) || undefined;
60 | const event = eventFromUnknownInput(exception, syntheticException, {
61 | attachStacktrace: this._options.attachStacktrace,
62 | });
63 | addExceptionMechanism(event, {
64 | handled: true,
65 | type: 'generic',
66 | });
67 | event.level = Severity.Error;
68 | if (hint && hint.event_id) {
69 | event.event_id = hint.event_id;
70 | }
71 | return resolvedSyncPromise(event);
72 | }
73 | /**
74 | * @inheritDoc
75 | */
76 | public eventFromMessage(message: string, level: Severity = Severity.Info, hint?: EventHint): PromiseLike {
77 | const syntheticException = (hint && hint.syntheticException) || undefined;
78 | const event = eventFromString(message, syntheticException, {
79 | attachStacktrace: this._options.attachStacktrace,
80 | });
81 | event.level = level;
82 | if (hint && hint.event_id) {
83 | event.event_id = hint.event_id;
84 | }
85 | return resolvedSyncPromise(event);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/client.ts:
--------------------------------------------------------------------------------
1 | import { BaseClient, Scope } from "@sentry/core";
2 | import { DsnLike, Event, EventHint } from "@sentry/types";
3 |
4 | import { MiniappBackend, MiniappOptions } from "./backend";
5 | import { SDK_NAME, SDK_VERSION } from "./version";
6 |
7 | /**
8 | * All properties the report dialog supports
9 | */
10 | export interface ReportDialogOptions {
11 | [key: string]: any;
12 | eventId?: string;
13 | dsn?: DsnLike;
14 | user?: {
15 | email?: string;
16 | name?: string;
17 | };
18 | lang?: string;
19 | title?: string;
20 | subtitle?: string;
21 | subtitle2?: string;
22 | labelName?: string;
23 | labelEmail?: string;
24 | labelComments?: string;
25 | labelClose?: string;
26 | labelSubmit?: string;
27 | errorGeneric?: string;
28 | errorFormEntry?: string;
29 | successMessage?: string;
30 | /** Callback after reportDialog showed up */
31 | onLoad?(): void;
32 | }
33 |
34 | /**
35 | * The Sentry Miniapp SDK Client.
36 | *
37 | * @see MiniappOptions for documentation on configuration options.
38 | * @see SentryClient for usage documentation.
39 | */
40 | export class MiniappClient extends BaseClient {
41 | /**
42 | * Creates a new Miniapp SDK instance.
43 | *
44 | * @param options Configuration options for this SDK.
45 | */
46 | public constructor(options: MiniappOptions = {}) {
47 | super(MiniappBackend, options);
48 | }
49 |
50 | /**
51 | * @inheritDoc
52 | */
53 | protected _prepareEvent(event: Event, scope?: Scope, hint?: EventHint): PromiseLike {
54 | event.platform = event.platform || "javascript";
55 | event.sdk = {
56 | ...event.sdk,
57 | name: SDK_NAME,
58 | packages: [
59 | ...((event.sdk && event.sdk.packages) || []),
60 | {
61 | name: "npm:@sentry/miniapp",
62 | version: SDK_VERSION
63 | }
64 | ],
65 | version: SDK_VERSION
66 | };
67 |
68 | return super._prepareEvent(event, scope, hint);
69 | }
70 |
71 | /**
72 | * Show a report dialog to the user to send feedback to a specific event.
73 | * 向用户显示报告对话框以将反馈发送到特定事件。---> 小程序上暂时用不到&不考虑。
74 | *
75 | * @param options Set individual options for the dialog
76 | */
77 | public showReportDialog(options: ReportDialogOptions = {}): void {
78 | // doesn't work without a document (React Native)
79 | console.log('sentry-miniapp 暂未实现该方法', options);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/crossPlatform.ts:
--------------------------------------------------------------------------------
1 | declare const wx: any; // 微信小程序、微信小游戏
2 | declare const my: any; // 支付宝小程序
3 | declare const tt: any; // 字节跳动小程序
4 | declare const dd: any; // 钉钉小程序
5 | declare const qq: any; // QQ 小程序、QQ 小游戏
6 | declare const swan: any; // 百度小程序
7 |
8 | /**
9 | * 小程序平台 SDK 接口
10 | */
11 | interface SDK {
12 | request: Function;
13 | httpRequest?: Function; // 针对钉钉小程序
14 | getSystemInfoSync: Function;
15 | onError?: Function;
16 | onUnhandledRejection?: Function;
17 | onPageNotFound?: Function;
18 | onMemoryWarning?: Function;
19 | getLaunchOptionsSync?: Function;
20 | }
21 |
22 | /**
23 | * 小程序平台 接口
24 | */
25 | type AppName =
26 | | "wechat"
27 | | "alipay"
28 | | "bytedance"
29 | | "dingtalk"
30 | | "qq"
31 | | "swan"
32 | | "unknown";
33 |
34 | /**
35 | * 获取跨平台的 SDK
36 | */
37 | const getSDK = () => {
38 | let currentSdk: SDK = {
39 | // tslint:disable-next-line: no-empty
40 | request: () => {},
41 | // tslint:disable-next-line: no-empty
42 | httpRequest: () => {},
43 | // tslint:disable-next-line: no-empty
44 | getSystemInfoSync: () => {},
45 | };
46 |
47 | if (typeof wx === "object") {
48 | // tslint:disable-next-line: no-unsafe-any
49 | currentSdk = wx;
50 | } else if (typeof my === "object") {
51 | // tslint:disable-next-line: no-unsafe-any
52 | currentSdk = my;
53 | } else if (typeof tt === "object") {
54 | // tslint:disable-next-line: no-unsafe-any
55 | currentSdk = tt;
56 | } else if (typeof dd === "object") {
57 | // tslint:disable-next-line: no-unsafe-any
58 | currentSdk = dd;
59 | } else if (typeof qq === "object") {
60 | // tslint:disable-next-line: no-unsafe-any
61 | currentSdk = qq;
62 | } else if (typeof swan === "object") {
63 | // tslint:disable-next-line: no-unsafe-any
64 | currentSdk = swan;
65 | } else {
66 | throw new Error("sentry-miniapp 暂不支持此平台");
67 | }
68 |
69 | return currentSdk;
70 | };
71 |
72 | /**
73 | * 获取平台名称
74 | */
75 | const getAppName = () => {
76 | let currentAppName: AppName = "unknown";
77 |
78 | if (typeof wx === "object") {
79 | currentAppName = "wechat";
80 | } else if (typeof my === "object") {
81 | currentAppName = "alipay";
82 | } else if (typeof tt === "object") {
83 | currentAppName = "bytedance";
84 | } else if (typeof dd === "object") {
85 | currentAppName = "dingtalk";
86 | } else if (typeof qq === "object") {
87 | currentAppName = "qq";
88 | } else if (typeof swan === "object") {
89 | currentAppName = "swan";
90 | }
91 |
92 | return currentAppName;
93 | };
94 |
95 | const sdk = getSDK();
96 | const appName = getAppName();
97 |
98 | export { sdk, appName };
99 |
--------------------------------------------------------------------------------
/src/eventbuilder.ts:
--------------------------------------------------------------------------------
1 | import { Event } from '@sentry/types';
2 | import {
3 | addExceptionMechanism,
4 | addExceptionTypeValue,
5 | isDOMError,
6 | isDOMException,
7 | isError,
8 | isErrorEvent,
9 | isEvent,
10 | isPlainObject,
11 | } from '@sentry/utils';
12 |
13 | import { eventFromPlainObject, eventFromStacktrace, prepareFramesForEvent } from './parsers';
14 | import { computeStackTrace } from './tracekit';
15 |
16 | /** JSDoc */
17 | export function eventFromUnknownInput(
18 | exception: unknown,
19 | syntheticException?: Error,
20 | options: {
21 | rejection?: boolean;
22 | attachStacktrace?: boolean;
23 | } = {},
24 | ): Event {
25 | let event: Event;
26 |
27 | if (isErrorEvent(exception as ErrorEvent) && (exception as ErrorEvent).error) {
28 | // If it is an ErrorEvent with `error` property, extract it to get actual Error
29 | const errorEvent = exception as ErrorEvent;
30 | exception = errorEvent.error; // tslint:disable-line:no-parameter-reassignment
31 | event = eventFromStacktrace(computeStackTrace(exception as Error));
32 | return event;
33 | }
34 | if (isDOMError(exception as DOMError) || isDOMException(exception as DOMException)) {
35 | // If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers)
36 | // then we just extract the name and message, as they don't provide anything else
37 | // https://developer.mozilla.org/en-US/docs/Web/API/DOMError
38 | // https://developer.mozilla.org/en-US/docs/Web/API/DOMException
39 | const domException = exception as DOMException;
40 | const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
41 | const message = domException.message ? `${name}: ${domException.message}` : name;
42 |
43 | event = eventFromString(message, syntheticException, options);
44 | addExceptionTypeValue(event, message);
45 | return event;
46 | }
47 | if (isError(exception as Error)) {
48 | // we have a real Error object, do nothing
49 | event = eventFromStacktrace(computeStackTrace(exception as Error));
50 | return event;
51 | }
52 | if (isPlainObject(exception) || isEvent(exception)) {
53 | // If it is plain Object or Event, serialize it manually and extract options
54 | // This will allow us to group events based on top-level keys
55 | // which is much better than creating new group when any key/value change
56 | const objectException = exception as {};
57 | event = eventFromPlainObject(objectException, syntheticException, options.rejection);
58 | addExceptionMechanism(event, {
59 | synthetic: true,
60 | });
61 | return event;
62 | }
63 |
64 | // If none of previous checks were valid, then it means that it's not:
65 | // - an instance of DOMError
66 | // - an instance of DOMException
67 | // - an instance of Event
68 | // - an instance of Error
69 | // - a valid ErrorEvent (one with an error property)
70 | // - a plain Object
71 | //
72 | // So bail out and capture it as a simple message:
73 | event = eventFromString(exception as string, syntheticException, options);
74 | addExceptionTypeValue(event, `${exception}`, undefined);
75 | addExceptionMechanism(event, {
76 | synthetic: true,
77 | });
78 |
79 | return event;
80 | }
81 |
82 | // this._options.attachStacktrace
83 | /** JSDoc */
84 | export function eventFromString(
85 | input: string,
86 | syntheticException?: Error,
87 | options: {
88 | attachStacktrace?: boolean;
89 | } = {},
90 | ): Event {
91 | const event: Event = {
92 | message: input,
93 | };
94 |
95 | if (options.attachStacktrace && syntheticException) {
96 | const stacktrace = computeStackTrace(syntheticException);
97 | const frames = prepareFramesForEvent(stacktrace.stack);
98 | event.stacktrace = {
99 | frames,
100 | };
101 | }
102 |
103 | return event;
104 | }
105 |
--------------------------------------------------------------------------------
/src/flags.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file defines flags and constants that can be modified during compile time in order to facilitate tree shaking
3 | * for users.
4 | *
5 | * Debug flags need to be declared in each package individually and must not be imported across package boundaries,
6 | * because some build tools have trouble tree-shaking imported guards.
7 | *
8 | * As a convention, we define debug flags in a `flags.ts` file in the root of a package's `src` folder.
9 | *
10 | * Debug flag files will contain "magic strings" like `__SENTRY_DEBUG__` that may get replaced with actual values during
11 | * our, or the user's build process. Take care when introducing new flags - they must not throw if they are not
12 | * replaced.
13 | */
14 |
15 | declare const __SENTRY_DEBUG__: boolean;
16 |
17 | /** Flag that is true for debug builds, false otherwise. */
18 | export const IS_DEBUG_BUILD = typeof __SENTRY_DEBUG__ === 'undefined' ? true : __SENTRY_DEBUG__;
19 |
--------------------------------------------------------------------------------
/src/helpers.ts:
--------------------------------------------------------------------------------
1 | import { captureException, getCurrentHub, withScope } from '@sentry/core';
2 | import { Event as SentryEvent, Mechanism, Scope, WrappedFunction } from '@sentry/types';
3 | import { addExceptionMechanism, addExceptionTypeValue, htmlTreeAsString, normalize } from '@sentry/utils';
4 |
5 | const debounceDuration: number = 1000;
6 | let keypressTimeout: number | undefined;
7 | let lastCapturedEvent: Event | undefined;
8 | let ignoreOnError: number = 0;
9 |
10 | /**
11 | * @hidden
12 | */
13 | export function shouldIgnoreOnError(): boolean {
14 | return ignoreOnError > 0;
15 | }
16 |
17 | /**
18 | * @hidden
19 | */
20 | export function ignoreNextOnError(): void {
21 | // onerror should trigger before setTimeout
22 | ignoreOnError += 1;
23 | setTimeout(() => {
24 | ignoreOnError -= 1;
25 | });
26 | }
27 |
28 | /**
29 | * Instruments the given function and sends an event to Sentry every time the
30 | * function throws an exception.
31 | *
32 | * @param fn A function to wrap.
33 | * @returns The wrapped function.
34 | * @hidden
35 | */
36 | export function wrap(
37 | fn: WrappedFunction,
38 | options: {
39 | mechanism?: Mechanism;
40 | capture?: boolean;
41 | } = {},
42 | before?: WrappedFunction,
43 | ): any {
44 | // tslint:disable-next-line:strict-type-predicates
45 | if (typeof fn !== 'function') {
46 | return fn;
47 | }
48 |
49 | try {
50 | // We don't wanna wrap it twice
51 | if (fn.__sentry__) {
52 | return fn;
53 | }
54 |
55 | // If this has already been wrapped in the past, return that wrapped function
56 | if (fn.__sentry_wrapped__) {
57 | return fn.__sentry_wrapped__;
58 | }
59 | } catch (e) {
60 | // Just accessing custom props in some Selenium environments
61 | // can cause a "Permission denied" exception (see raven-js#495).
62 | // Bail on wrapping and return the function as-is (defers to window.onerror).
63 | return fn;
64 | }
65 |
66 | const sentryWrapped: WrappedFunction = function (this: any): void {
67 | // tslint:disable-next-line:strict-type-predicates
68 | if (before && typeof before === 'function') {
69 | before.apply(this, arguments);
70 | }
71 |
72 | const args = Array.prototype.slice.call(arguments);
73 |
74 | // tslint:disable:no-unsafe-any
75 | try {
76 | const wrappedArguments = args.map((arg: any) => wrap(arg, options));
77 |
78 | if (fn.handleEvent) {
79 | // Attempt to invoke user-land function
80 | // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
81 | // means the sentry.javascript SDK caught an error invoking your application code. This
82 | // is expected behavior and NOT indicative of a bug with sentry.javascript.
83 | return fn.handleEvent.apply(this, wrappedArguments);
84 | }
85 |
86 | // Attempt to invoke user-land function
87 | // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
88 | // means the sentry.javascript SDK caught an error invoking your application code. This
89 | // is expected behavior and NOT indicative of a bug with sentry.javascript.
90 | return fn.apply(this, wrappedArguments);
91 | // tslint:enable:no-unsafe-any
92 | } catch (ex) {
93 | ignoreNextOnError();
94 |
95 | withScope((scope: Scope) => {
96 | scope.addEventProcessor((event: SentryEvent) => {
97 | const processedEvent = { ...event };
98 |
99 | if (options.mechanism) {
100 | addExceptionTypeValue(processedEvent, undefined, undefined);
101 | addExceptionMechanism(processedEvent, options.mechanism);
102 | }
103 |
104 | processedEvent.extra = {
105 | ...processedEvent.extra,
106 | arguments: normalize(args, 3),
107 | };
108 |
109 | return processedEvent;
110 | });
111 |
112 | captureException(ex);
113 | });
114 |
115 | throw ex;
116 | }
117 | };
118 |
119 | // Accessing some objects may throw
120 | // ref: https://github.com/getsentry/sentry-javascript/issues/1168
121 | try {
122 | // tslint:disable-next-line: no-for-in
123 | for (const property in fn) {
124 | if (Object.prototype.hasOwnProperty.call(fn, property)) {
125 | sentryWrapped[property] = fn[property];
126 | }
127 | }
128 | } catch (_oO) { } // tslint:disable-line:no-empty
129 |
130 | fn.prototype = fn.prototype || {};
131 | sentryWrapped.prototype = fn.prototype;
132 |
133 | Object.defineProperty(fn, '__sentry_wrapped__', {
134 | enumerable: false,
135 | value: sentryWrapped,
136 | });
137 |
138 | // Signal that this function has been wrapped/filled already
139 | // for both debugging and to prevent it to being wrapped/filled twice
140 | Object.defineProperties(sentryWrapped, {
141 | __sentry__: {
142 | enumerable: false,
143 | value: true,
144 | },
145 | __sentry_original__: {
146 | enumerable: false,
147 | value: fn,
148 | },
149 | });
150 |
151 | // Restore original function name (not all browsers allow that)
152 | try {
153 | const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name') as PropertyDescriptor;
154 | if (descriptor.configurable) {
155 | Object.defineProperty(sentryWrapped, 'name', {
156 | get(): string {
157 | return fn.name;
158 | },
159 | });
160 | }
161 | } catch (_oO) {
162 | /*no-empty*/
163 | }
164 |
165 | return sentryWrapped;
166 | }
167 |
168 | let debounceTimer: number = 0;
169 |
170 | /**
171 | * Wraps addEventListener to capture UI breadcrumbs
172 | * @param eventName the event name (e.g. "click")
173 | * @returns wrapped breadcrumb events handler
174 | * @hidden
175 | */
176 | export function breadcrumbEventHandler(eventName: string, debounce: boolean = false): (event: Event) => void {
177 | return (event: Event) => {
178 | // reset keypress timeout; e.g. triggering a 'click' after
179 | // a 'keypress' will reset the keypress debounce so that a new
180 | // set of keypresses can be recorded
181 | keypressTimeout = undefined;
182 | // It's possible this handler might trigger multiple times for the same
183 | // event (e.g. event propagation through node ancestors). Ignore if we've
184 | // already captured the event.
185 | // tslint:disable-next-line: strict-comparisons
186 | if (!event || lastCapturedEvent === event) {
187 | return;
188 | }
189 |
190 | lastCapturedEvent = event;
191 |
192 | const captureBreadcrumb = () => {
193 | let target;
194 |
195 | // Accessing event.target can throw (see getsentry/raven-js#838, #768)
196 | try {
197 | target = event.target ? htmlTreeAsString(event.target as Node) : htmlTreeAsString((event as unknown) as Node);
198 | } catch (e) {
199 | target = '';
200 | }
201 |
202 | if (target.length === 0) {
203 | return;
204 | }
205 |
206 | getCurrentHub().addBreadcrumb(
207 | {
208 | category: `ui.${eventName}`, // e.g. ui.click, ui.input
209 | message: target,
210 | },
211 | {
212 | event,
213 | name: eventName,
214 | },
215 | );
216 | };
217 |
218 | if (debounceTimer) {
219 | clearTimeout(debounceTimer);
220 | }
221 |
222 | if (debounce) {
223 | debounceTimer = setTimeout(captureBreadcrumb);
224 | } else {
225 | captureBreadcrumb();
226 | }
227 | };
228 | }
229 |
230 | /**
231 | * Wraps addEventListener to capture keypress UI events
232 | * @returns wrapped keypress events handler
233 | * @hidden
234 | */
235 | export function keypressEventHandler(): (event: Event) => void {
236 | // TODO: if somehow user switches keypress target before
237 | // debounce timeout is triggered, we will only capture
238 | // a single breadcrumb from the FIRST target (acceptable?)
239 | return (event: Event) => {
240 | let target;
241 |
242 | try {
243 | target = event.target;
244 | } catch (e) {
245 | // just accessing event properties can throw an exception in some rare circumstances
246 | // see: https://github.com/getsentry/raven-js/issues/838
247 | return;
248 | }
249 |
250 | const tagName = target && (target as HTMLElement).tagName;
251 |
252 | // only consider keypress events on actual input elements
253 | // this will disregard keypresses targeting body (e.g. tabbing
254 | // through elements, hotkeys, etc)
255 | if (!tagName || (tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !(target as HTMLElement).isContentEditable)) {
256 | return;
257 | }
258 |
259 | // record first keypress in a series, but ignore subsequent
260 | // keypresses until debounce clears
261 | if (!keypressTimeout) {
262 | breadcrumbEventHandler('input')(event);
263 | }
264 | clearTimeout(keypressTimeout);
265 |
266 | keypressTimeout = (setTimeout(() => {
267 | keypressTimeout = undefined;
268 | }, debounceDuration) as any) as number;
269 | };
270 | }
271 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Breadcrumb,
3 | BreadcrumbHint,
4 | Request,
5 | SdkInfo,
6 | Event,
7 | EventHint,
8 | EventStatus,
9 | Exception,
10 | Response,
11 | Severity,
12 | StackFrame,
13 | Stacktrace,
14 | Thread,
15 | User,
16 | } from "@sentry/types";
17 |
18 | export {
19 | addGlobalEventProcessor,
20 | addBreadcrumb,
21 | captureException,
22 | captureEvent,
23 | captureMessage,
24 | configureScope,
25 | getHubFromCarrier,
26 | getCurrentHub,
27 | Hub,
28 | Scope,
29 | setContext,
30 | setExtra,
31 | setExtras,
32 | setTag,
33 | setTags,
34 | setUser,
35 | withScope
36 | } from "@sentry/core";
37 |
38 | export { SDK_NAME, SDK_VERSION } from "./version";
39 | export {
40 | defaultIntegrations,
41 | init,
42 | lastEventId,
43 | showReportDialog,
44 | flush,
45 | close,
46 | wrap
47 | } from "./sdk";
48 | export { MiniappOptions } from "./backend";
49 | export { MiniappClient, ReportDialogOptions } from "./client";
50 |
51 | import * as Integrations from "./integrations/index";
52 | import * as Transports from "./transports/index";
53 |
54 | export { Integrations, Transports };
55 |
--------------------------------------------------------------------------------
/src/integrations/globalhandlers.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentHub } from "@sentry/core";
2 | import { Integration } from "@sentry/types";
3 | import { logger } from "@sentry/utils";
4 |
5 | import { sdk } from "../crossPlatform";
6 |
7 | /** JSDoc */
8 | interface GlobalHandlersIntegrations {
9 | onerror: boolean;
10 | onunhandledrejection: boolean;
11 | onpagenotfound: boolean;
12 | onmemorywarning: boolean;
13 | }
14 |
15 | /** Global handlers */
16 | export class GlobalHandlers implements Integration {
17 | /**
18 | * @inheritDoc
19 | */
20 | public name: string = GlobalHandlers.id;
21 |
22 | /**
23 | * @inheritDoc
24 | */
25 | public static id: string = "GlobalHandlers";
26 |
27 | /** JSDoc */
28 | private readonly _options: GlobalHandlersIntegrations;
29 |
30 | /** JSDoc */
31 | private _onErrorHandlerInstalled: boolean = false;
32 |
33 | /** JSDoc */
34 | private _onUnhandledRejectionHandlerInstalled: boolean = false;
35 |
36 | /** JSDoc */
37 | private _onPageNotFoundHandlerInstalled: boolean = false;
38 |
39 | /** JSDoc */
40 | private _onMemoryWarningHandlerInstalled: boolean = false;
41 |
42 | /** JSDoc */
43 | public constructor(options?: GlobalHandlersIntegrations) {
44 | this._options = {
45 | onerror: true,
46 | onunhandledrejection: true,
47 | onpagenotfound: true,
48 | onmemorywarning: true,
49 | ...options,
50 | };
51 | }
52 | /**
53 | * @inheritDoc
54 | */
55 | public setupOnce(): void {
56 | Error.stackTraceLimit = 50;
57 |
58 | if (this._options.onerror) {
59 | logger.log("Global Handler attached: onError");
60 | this._installGlobalOnErrorHandler();
61 | }
62 |
63 | if (this._options.onunhandledrejection) {
64 | logger.log("Global Handler attached: onunhandledrejection");
65 | this._installGlobalOnUnhandledRejectionHandler();
66 | }
67 |
68 | if (this._options.onpagenotfound) {
69 | logger.log("Global Handler attached: onPageNotFound");
70 | this._installGlobalOnPageNotFoundHandler();
71 | }
72 |
73 | if (this._options.onmemorywarning) {
74 | logger.log("Global Handler attached: onMemoryWarning");
75 | this._installGlobalOnMemoryWarningHandler();
76 | }
77 | }
78 |
79 | /** JSDoc */
80 | private _installGlobalOnErrorHandler(): void {
81 | if (this._onErrorHandlerInstalled) {
82 | return;
83 | }
84 |
85 | if (!!sdk.onError) {
86 | const currentHub = getCurrentHub();
87 |
88 | // https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onError.html
89 | sdk.onError((err: string | object) => {
90 | // console.info("sentry-miniapp", error);
91 | const error = typeof err === 'string' ? new Error(err) : err
92 | currentHub.captureException(error);
93 | });
94 | }
95 |
96 | this._onErrorHandlerInstalled = true;
97 | }
98 |
99 | /** JSDoc */
100 | private _installGlobalOnUnhandledRejectionHandler(): void {
101 | if (this._onUnhandledRejectionHandlerInstalled) {
102 | return;
103 | }
104 |
105 | if (!!sdk.onUnhandledRejection) {
106 | const currentHub = getCurrentHub();
107 | /** JSDoc */
108 | interface OnUnhandledRejectionRes {
109 | reason: string | object;
110 | promise: Promise;
111 | }
112 |
113 | // https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html
114 | sdk.onUnhandledRejection(
115 | ({ reason, promise }: OnUnhandledRejectionRes) => {
116 | // console.log(reason, typeof reason, promise)
117 | // 为什么官方文档上说 reason 是 string 类型,但是实际返回的确实 object 类型
118 | const error = typeof reason === 'string' ? new Error(reason) : reason
119 | currentHub.captureException(error, {
120 | data: promise,
121 | });
122 | }
123 | );
124 | }
125 |
126 | this._onUnhandledRejectionHandlerInstalled = true;
127 | }
128 |
129 | /** JSDoc */
130 | private _installGlobalOnPageNotFoundHandler(): void {
131 | if (this._onPageNotFoundHandlerInstalled) {
132 | return;
133 | }
134 |
135 | if (!!sdk.onPageNotFound) {
136 | const currentHub = getCurrentHub();
137 |
138 | sdk.onPageNotFound((res: { path: string }) => {
139 | const url = res.path.split("?")[0];
140 |
141 | currentHub.setTag("pagenotfound", url);
142 | currentHub.setExtra("message", JSON.stringify(res));
143 | currentHub.captureMessage(`页面无法找到: ${url}`);
144 | });
145 | }
146 |
147 | this._onPageNotFoundHandlerInstalled = true;
148 | }
149 |
150 | /** JSDoc */
151 | private _installGlobalOnMemoryWarningHandler(): void {
152 | if (this._onMemoryWarningHandlerInstalled) {
153 | return;
154 | }
155 |
156 | if (!!sdk.onMemoryWarning) {
157 | const currentHub = getCurrentHub();
158 |
159 | sdk.onMemoryWarning(({ level = -1 }: { level: number }) => {
160 | let levelMessage = "没有获取到告警级别信息";
161 |
162 | switch (level) {
163 | case 5:
164 | levelMessage = "TRIM_MEMORY_RUNNING_MODERATE";
165 | break;
166 | case 10:
167 | levelMessage = "TRIM_MEMORY_RUNNING_LOW";
168 | break;
169 | case 15:
170 | levelMessage = "TRIM_MEMORY_RUNNING_CRITICAL";
171 | break;
172 | default:
173 | return;
174 | }
175 |
176 | currentHub.setTag("memory-warning", String(level));
177 | currentHub.setExtra("message", levelMessage);
178 | currentHub.captureMessage(`内存不足告警`);
179 | });
180 | }
181 |
182 | this._onMemoryWarningHandlerInstalled = true;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/integrations/ignoreMpcrawlerErrors.ts:
--------------------------------------------------------------------------------
1 | import { addGlobalEventProcessor, getCurrentHub } from "@sentry/core";
2 | import { Event, Integration } from "@sentry/types";
3 |
4 | import { appName, sdk } from "../crossPlatform";
5 |
6 | /**
7 | * IgnoreMpcrawlerErrors
8 | *
9 | * https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/sitemap.html
10 | */
11 | export class IgnoreMpcrawlerErrors implements Integration {
12 | /**
13 | * @inheritDoc
14 | */
15 | public name: string = IgnoreMpcrawlerErrors.id;
16 |
17 | /**
18 | * @inheritDoc
19 | */
20 | public static id: string = "IgnoreMpcrawlerErrors";
21 |
22 | /**
23 | * @inheritDoc
24 | */
25 | public setupOnce(): void {
26 | addGlobalEventProcessor((event: Event) => {
27 | if (
28 | getCurrentHub().getIntegration(IgnoreMpcrawlerErrors) &&
29 | appName === "wechat" &&
30 | sdk.getLaunchOptionsSync
31 | ) {
32 | const options = sdk.getLaunchOptionsSync();
33 |
34 | if (options.scene === 1129) {
35 | return null;
36 | }
37 | }
38 |
39 | return event;
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/integrations/index.ts:
--------------------------------------------------------------------------------
1 | export { GlobalHandlers } from "./globalhandlers";
2 | export { TryCatch } from "./trycatch";
3 | export { LinkedErrors } from "./linkederrors";
4 |
5 | export { System } from "./system";
6 | export { Router } from "./router";
7 | export { IgnoreMpcrawlerErrors } from "./ignoreMpcrawlerErrors";
8 |
--------------------------------------------------------------------------------
/src/integrations/linkederrors.ts:
--------------------------------------------------------------------------------
1 | import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
2 | import { Event, EventHint, Exception, ExtendedError, Integration } from '@sentry/types';
3 |
4 | import { exceptionFromStacktrace } from '../parsers';
5 | import { computeStackTrace } from '../tracekit';
6 |
7 | const DEFAULT_KEY = 'cause';
8 | const DEFAULT_LIMIT = 5;
9 |
10 | /** Adds SDK info to an event. */
11 | export class LinkedErrors implements Integration {
12 | /**
13 | * @inheritDoc
14 | */
15 | public readonly name: string = LinkedErrors.id;
16 |
17 | /**
18 | * @inheritDoc
19 | */
20 | public static id: string = 'LinkedErrors';
21 |
22 | /**
23 | * @inheritDoc
24 | */
25 | private readonly _key: string;
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | private readonly _limit: number;
31 |
32 | /**
33 | * @inheritDoc
34 | */
35 | public constructor(options: { key?: string; limit?: number } = {}) {
36 | this._key = options.key || DEFAULT_KEY;
37 | this._limit = options.limit || DEFAULT_LIMIT;
38 | }
39 |
40 | /**
41 | * @inheritDoc
42 | */
43 | public setupOnce(): void {
44 | addGlobalEventProcessor((event: Event, hint?: EventHint) => {
45 | const self = getCurrentHub().getIntegration(LinkedErrors);
46 | if (self) {
47 | return self._handler(event, hint);
48 | }
49 | return event;
50 | });
51 | }
52 |
53 | /**
54 | * @inheritDoc
55 | */
56 | private _handler(event: Event, hint?: EventHint): Event | null {
57 | if (!event.exception || !event.exception.values || !hint || !(hint.originalException instanceof Error)) {
58 | return event;
59 | }
60 | const linkedErrors = this._walkErrorTree(hint.originalException, this._key);
61 | event.exception.values = [...linkedErrors, ...event.exception.values];
62 | return event;
63 | }
64 |
65 | /**
66 | * @inheritDoc
67 | */
68 | private _walkErrorTree(error: ExtendedError, key: string, stack: Exception[] = []): Exception[] {
69 | if (!(error[key] instanceof Error) || stack.length + 1 >= this._limit) {
70 | return stack;
71 | }
72 | const stacktrace = computeStackTrace(error[key]);
73 | const exception = exceptionFromStacktrace(stacktrace);
74 | return this._walkErrorTree(error[key], key, [exception, ...stack]);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/integrations/router.ts:
--------------------------------------------------------------------------------
1 | import { addGlobalEventProcessor, getCurrentHub } from "@sentry/core";
2 | import { Event, Integration } from "@sentry/types";
3 |
4 | declare const getCurrentPages: any;
5 |
6 | /** JSDoc */
7 | interface RouterIntegrations {
8 | enable?: boolean;
9 | }
10 |
11 | /** UserAgent */
12 | export class Router implements Integration {
13 | /**
14 | * @inheritDoc
15 | */
16 | public name: string = Router.id;
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public static id: string = "Router";
22 |
23 | /** JSDoc */
24 | private readonly _options: RouterIntegrations;
25 |
26 | /**
27 | * @inheritDoc
28 | */
29 | public constructor(options?: RouterIntegrations) {
30 | this._options = {
31 | enable: true,
32 | ...options,
33 | };
34 | }
35 |
36 | /**
37 | * @inheritDoc
38 | */
39 | public setupOnce(): void {
40 | addGlobalEventProcessor((event: Event) => {
41 | if (getCurrentHub().getIntegration(Router)) {
42 | if (this._options.enable) {
43 | try {
44 | const routers = getCurrentPages().map(
45 | (route: { route: string; options: object }) => ({
46 | route: route.route,
47 | options: route.options,
48 | })
49 | );
50 |
51 | return {
52 | ...event,
53 | extra: {
54 | ...event.extra,
55 | routers,
56 | },
57 | };
58 | } catch (e) {
59 | console.warn(`sentry-miniapp get router info fail: ${e}`);
60 | }
61 | }
62 | }
63 |
64 | return event;
65 | });
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/integrations/system.ts:
--------------------------------------------------------------------------------
1 | import { addGlobalEventProcessor, getCurrentHub } from "@sentry/core";
2 | import { Event, Integration } from "@sentry/types";
3 |
4 | import { appName as currentAppName, sdk } from "../crossPlatform";
5 |
6 | /** UserAgent */
7 | export class System implements Integration {
8 | /**
9 | * @inheritDoc
10 | */
11 | public name: string = System.id;
12 |
13 | /**
14 | * @inheritDoc
15 | */
16 | public static id: string = "System";
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public setupOnce(): void {
22 | addGlobalEventProcessor((event: Event) => {
23 | if (getCurrentHub().getIntegration(System)) {
24 | try {
25 | const systemInfo = sdk.getSystemInfoSync();
26 | const {
27 | SDKVersion = "0.0.0",
28 | batteryLevel, // 微信小程序
29 | currentBattery, // 支付宝小程序、 钉钉小程序
30 | battery, // 字节跳动小程序
31 | brand,
32 | language,
33 | model,
34 | pixelRatio,
35 | platform,
36 | screenHeight,
37 | screenWidth,
38 | statusBarHeight,
39 | system,
40 | version,
41 | windowHeight,
42 | windowWidth,
43 | app, // 支付宝小程序
44 | appName, // 字节跳动小程序
45 | fontSizeSetting, // 支付宝小程序、 钉钉小程序、微信小程序
46 | } = systemInfo;
47 | const [systemName, systemVersion] = system.split(" ");
48 |
49 | return {
50 | ...event,
51 | contexts: {
52 | ...event.contexts,
53 | device: {
54 | brand,
55 | battery_level: batteryLevel || currentBattery || battery,
56 | model,
57 | screen_dpi: pixelRatio
58 | },
59 | os: {
60 | name: systemName || system,
61 | version: systemVersion || system
62 | },
63 | extra: {
64 | SDKVersion,
65 | language,
66 | platform,
67 | screenHeight,
68 | screenWidth,
69 | statusBarHeight,
70 | version,
71 | windowHeight,
72 | windowWidth,
73 | fontSizeSetting,
74 | app: app || appName || currentAppName,
75 | ...systemInfo,
76 | }
77 | }
78 | };
79 | } catch (e) {
80 | console.warn(`sentry-miniapp get system info fail: ${e}`);
81 | }
82 | }
83 |
84 | return event;
85 | });
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/integrations/trycatch.ts:
--------------------------------------------------------------------------------
1 | import { Integration, WrappedFunction } from "@sentry/types";
2 | import { fill, getGlobalObject } from "@sentry/utils";
3 |
4 | import { wrap } from "../helpers";
5 |
6 | /** Wrap timer functions and event targets to catch errors and provide better meta data */
7 | export class TryCatch implements Integration {
8 | /** JSDoc */
9 | private _ignoreOnError: number = 0;
10 |
11 | /**
12 | * @inheritDoc
13 | */
14 | public name: string = TryCatch.id;
15 |
16 | /**
17 | * @inheritDoc
18 | */
19 | public static id: string = "TryCatch";
20 |
21 | /** JSDoc */
22 | private _wrapTimeFunction(original: () => void): () => number {
23 | return function(this: any, ...args: any[]): number {
24 | const originalCallback = args[0];
25 | args[0] = wrap(originalCallback, {
26 | mechanism: {
27 | data: { function: getFunctionName(original) },
28 | handled: true,
29 | type: "instrument"
30 | }
31 | });
32 | return original.apply(this, args);
33 | };
34 | }
35 |
36 | /** JSDoc */
37 | private _wrapRAF(original: any): (callback: () => void) => any {
38 | return function(this: any, callback: () => void): () => void {
39 | return original(
40 | wrap(callback, {
41 | mechanism: {
42 | data: {
43 | function: "requestAnimationFrame",
44 | handler: getFunctionName(original)
45 | },
46 | handled: true,
47 | type: "instrument"
48 | }
49 | })
50 | );
51 | };
52 | }
53 |
54 | /** JSDoc */
55 | private _wrapEventTarget(target: string): void {
56 | const global = getGlobalObject() as { [key: string]: any };
57 | const proto = global[target] && global[target].prototype;
58 |
59 | if (
60 | !proto ||
61 | !proto.hasOwnProperty ||
62 | !proto.hasOwnProperty("addEventListener")
63 | ) {
64 | return;
65 | }
66 |
67 | fill(proto, "addEventListener", function(
68 | original: () => void
69 | ): (
70 | eventName: string,
71 | fn: EventListenerObject,
72 | options?: boolean | AddEventListenerOptions
73 | ) => void {
74 | return function(
75 | this: any,
76 | eventName: string,
77 | fn: EventListenerObject,
78 | options?: boolean | AddEventListenerOptions
79 | ): (
80 | eventName: string,
81 | fn: EventListenerObject,
82 | capture?: boolean,
83 | secure?: boolean
84 | ) => void {
85 | try {
86 | // tslint:disable-next-line:no-unbound-method strict-type-predicates
87 | if (typeof fn.handleEvent === "function") {
88 | fn.handleEvent = wrap(fn.handleEvent.bind(fn), {
89 | mechanism: {
90 | data: {
91 | function: "handleEvent",
92 | handler: getFunctionName(fn),
93 | target
94 | },
95 | handled: true,
96 | type: "instrument"
97 | }
98 | });
99 | }
100 | } catch (err) {
101 | // can sometimes get 'Permission denied to access property "handle Event'
102 | }
103 |
104 | return original.call(
105 | this,
106 | eventName,
107 | wrap((fn as any) as WrappedFunction, {
108 | mechanism: {
109 | data: {
110 | function: "addEventListener",
111 | handler: getFunctionName(fn),
112 | target
113 | },
114 | handled: true,
115 | type: "instrument"
116 | }
117 | }),
118 | options
119 | );
120 | };
121 | });
122 |
123 | fill(proto, "removeEventListener", function(
124 | original: () => void
125 | ): (
126 | this: any,
127 | eventName: string,
128 | fn: EventListenerObject,
129 | options?: boolean | EventListenerOptions
130 | ) => () => void {
131 | return function(
132 | this: any,
133 | eventName: string,
134 | fn: EventListenerObject,
135 | options?: boolean | EventListenerOptions
136 | ): () => void {
137 | let callback = (fn as any) as WrappedFunction;
138 | try {
139 | callback = callback && (callback.__sentry_wrapped__ || callback);
140 | } catch (e) {
141 | // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
142 | }
143 | return original.call(this, eventName, callback, options);
144 | };
145 | });
146 | }
147 |
148 | /**
149 | * Wrap timer functions and event targets to catch errors
150 | * and provide better metadata.
151 | */
152 | public setupOnce(): void {
153 | this._ignoreOnError = this._ignoreOnError;
154 |
155 | const global = getGlobalObject();
156 |
157 | fill(global, "setTimeout", this._wrapTimeFunction.bind(this));
158 | fill(global, "setInterval", this._wrapTimeFunction.bind(this));
159 | fill(global, "requestAnimationFrame", this._wrapRAF.bind(this));
160 |
161 | [
162 | "EventTarget",
163 | "Window",
164 | "Node",
165 | "ApplicationCache",
166 | "AudioTrackList",
167 | "ChannelMergerNode",
168 | "CryptoOperation",
169 | "EventSource",
170 | "FileReader",
171 | "HTMLUnknownElement",
172 | "IDBDatabase",
173 | "IDBRequest",
174 | "IDBTransaction",
175 | "KeyOperation",
176 | "MediaController",
177 | "MessagePort",
178 | "ModalWindow",
179 | "Notification",
180 | "SVGElementInstance",
181 | "Screen",
182 | "TextTrack",
183 | "TextTrackCue",
184 | "TextTrackList",
185 | "WebSocket",
186 | "WebSocketWorker",
187 | "Worker",
188 | "XMLHttpRequest",
189 | "XMLHttpRequestEventTarget",
190 | "XMLHttpRequestUpload"
191 | ].forEach(this._wrapEventTarget.bind(this));
192 | }
193 | }
194 |
195 | /**
196 | * Safely extract function name from itself
197 | */
198 | function getFunctionName(fn: any): string {
199 | try {
200 | return (fn && fn.name) || "";
201 | } catch (e) {
202 | // Just accessing custom props in some Selenium environments
203 | // can cause a "Permission denied" exception (see raven-js#495).
204 | return "";
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/integrations/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../tslint.json"],
3 | "rules": {
4 | "no-unsafe-any": false,
5 | "only-arrow-functions": false
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/parsers.ts:
--------------------------------------------------------------------------------
1 | import { Event, Exception, StackFrame } from '@sentry/types';
2 | import { extractExceptionKeysForMessage, isEvent, normalizeToSize } from '@sentry/utils';
3 |
4 | import { computeStackTrace, StackFrame as TraceKitStackFrame, StackTrace as TraceKitStackTrace } from './tracekit';
5 |
6 | const STACKTRACE_LIMIT = 100;
7 |
8 | /**
9 | * This function creates an exception from an TraceKitStackTrace
10 | * @param stacktrace TraceKitStackTrace that will be converted to an exception
11 | * @hidden
12 | */
13 | export function exceptionFromStacktrace(stacktrace: TraceKitStackTrace): Exception {
14 | const frames = prepareFramesForEvent(stacktrace.stack);
15 |
16 | const exception: Exception = {
17 | type: stacktrace.name,
18 | value: stacktrace.message,
19 | };
20 |
21 | if (frames && frames.length) {
22 | exception.stacktrace = { frames };
23 | }
24 |
25 | // tslint:disable-next-line:strict-type-predicates
26 | if (exception.type === undefined && exception.value === '') {
27 | exception.value = 'Unrecoverable error caught';
28 | }
29 |
30 | return exception;
31 | }
32 |
33 | /**
34 | * @hidden
35 | */
36 | export function eventFromPlainObject(exception: {}, syntheticException?: Error, rejection?: boolean): Event {
37 | const event: Event = {
38 | exception: {
39 | values: [
40 | {
41 | type: isEvent(exception) ? exception.constructor.name : rejection ? 'UnhandledRejection' : 'Error',
42 | value: `Non-Error ${rejection ? 'promise rejection' : 'exception'
43 | } captured with keys: ${extractExceptionKeysForMessage(exception)}`,
44 | },
45 | ],
46 | },
47 | extra: {
48 | __serialized__: normalizeToSize(exception),
49 | },
50 | };
51 |
52 | if (syntheticException) {
53 | const stacktrace = computeStackTrace(syntheticException);
54 | const frames = prepareFramesForEvent(stacktrace.stack);
55 | event.stacktrace = {
56 | frames,
57 | };
58 | }
59 |
60 | return event;
61 | }
62 |
63 | /**
64 | * @hidden
65 | */
66 | export function eventFromStacktrace(stacktrace: TraceKitStackTrace): Event {
67 | const exception = exceptionFromStacktrace(stacktrace);
68 |
69 | return {
70 | exception: {
71 | values: [exception],
72 | },
73 | };
74 | }
75 |
76 | /**
77 | * @hidden
78 | */
79 | export function prepareFramesForEvent(stack: TraceKitStackFrame[]): StackFrame[] {
80 | if (!stack || !stack.length) {
81 | return [];
82 | }
83 |
84 | let localStack = stack;
85 |
86 | const firstFrameFunction = localStack[0].func || '';
87 | const lastFrameFunction = localStack[localStack.length - 1].func || '';
88 |
89 | // If stack starts with one of our API calls, remove it (starts, meaning it's the top of the stack - aka last call)
90 | if (firstFrameFunction.indexOf('captureMessage') !== -1 || firstFrameFunction.indexOf('captureException') !== -1) {
91 | localStack = localStack.slice(1);
92 | }
93 |
94 | // If stack ends with one of our internal API calls, remove it (ends, meaning it's the bottom of the stack - aka top-most call)
95 | if (lastFrameFunction.indexOf('sentryWrapped') !== -1) {
96 | localStack = localStack.slice(0, -1);
97 | }
98 |
99 | // The frame where the crash happened, should be the last entry in the array
100 | return localStack
101 | .map(
102 | (frame: TraceKitStackFrame): StackFrame => ({
103 | colno: frame.column === null ? undefined : frame.column,
104 | filename: frame.url || localStack[0].url,
105 | function: frame.func || '?',
106 | in_app: true,
107 | lineno: frame.line === null ? undefined : frame.line,
108 | }),
109 | )
110 | .slice(0, STACKTRACE_LIMIT)
111 | .reverse();
112 | }
113 |
--------------------------------------------------------------------------------
/src/sdk.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getCurrentHub,
3 | initAndBind,
4 | Integrations as CoreIntegrations,
5 | } from "@sentry/core";
6 | import { resolvedSyncPromise } from "@sentry/utils";
7 |
8 | import { MiniappOptions } from "./backend";
9 | import { MiniappClient, ReportDialogOptions } from "./client";
10 | import { wrap as internalWrap } from "./helpers";
11 | import {
12 | GlobalHandlers,
13 | IgnoreMpcrawlerErrors,
14 | LinkedErrors,
15 | Router,
16 | System,
17 | TryCatch,
18 | } from "./integrations/index";
19 |
20 | export const defaultIntegrations = [
21 | new CoreIntegrations.InboundFilters(),
22 | new CoreIntegrations.FunctionToString(),
23 | new TryCatch(),
24 | new GlobalHandlers(),
25 | new LinkedErrors(),
26 |
27 | new System(),
28 | new Router(),
29 | new IgnoreMpcrawlerErrors(),
30 | ];
31 |
32 | /**
33 | * The Sentry Miniapp SDK Client.
34 | *
35 | * To use this SDK, call the {@link init} function as early as possible when
36 | * launching the app. To set context information or send manual events, use
37 | * the provided methods.
38 | *
39 | * @example
40 | * ```
41 | * import { init } from '@sentry/miniapp';
42 | *
43 | * init({
44 | * dsn: '__DSN__',
45 | * // ...
46 | * });
47 | * ```
48 | *
49 | * @example
50 | * ```
51 | * import { configureScope } from '@sentry/miniapp';
52 | *
53 | * configureScope((scope: Scope) => {
54 | * scope.setExtra({ battery: 0.7 });
55 | * scope.setTag({ user_mode: 'admin' });
56 | * scope.setUser({ id: '4711' });
57 | * });
58 | * ```
59 | *
60 | * @example
61 | * ```
62 | * import { addBreadcrumb } from '@sentry/miniapp';
63 | *
64 | * addBreadcrumb({
65 | * message: 'My Breadcrumb',
66 | * // ...
67 | * });
68 | * ```
69 | *
70 | * @example
71 | * ```
72 | * import * as Sentry from '@sentry/miniapp';
73 | *
74 | * Sentry.captureMessage('Hello, world!');
75 | * Sentry.captureException(new Error('Good bye'));
76 | * Sentry.captureEvent({
77 | * message: 'Manual',
78 | * stacktrace: [
79 | * // ...
80 | * ],
81 | * });
82 | * ```
83 | *
84 | * @see {@link MiniappOptions} for documentation on configuration options.
85 | */
86 | export function init(options: MiniappOptions = {}): void {
87 | // 如果将 options.defaultIntegrations 设置为 false,则不会添加默认集成,否则将在内部将其设置为建议的默认集成。
88 | // tslint:disable-next-line: strict-comparisons
89 | if (options.defaultIntegrations === undefined) {
90 | options.defaultIntegrations = defaultIntegrations;
91 | }
92 |
93 | // https://github.com/lizhiyao/sentry-miniapp/issues/23
94 | options.normalizeDepth = options.normalizeDepth || 5;
95 |
96 | initAndBind(MiniappClient, options);
97 | }
98 |
99 | /**
100 | * Present the user with a report dialog.
101 | * 向用户显示报告对话框。小程序上暂时不考虑实现该功能。
102 | *
103 | * @param options Everything is optional, we try to fetch all info need from the global scope.
104 | */
105 | export function showReportDialog(options: ReportDialogOptions = {}): void {
106 | if (!options.eventId) {
107 | options.eventId = getCurrentHub().lastEventId();
108 | }
109 | const client = getCurrentHub().getClient();
110 | if (client) {
111 | client.showReportDialog(options);
112 | }
113 | }
114 |
115 | /**
116 | * This is the getter for lastEventId. 获取 lastEventId。
117 | *
118 | * @returns The last event id of a captured event.
119 | */
120 | export function lastEventId(): string | undefined {
121 | return getCurrentHub().lastEventId();
122 | }
123 |
124 | /**
125 | * A promise that resolves when all current events have been sent.
126 | * If you provide a timeout and the queue takes longer to drain the promise returns false.
127 | * 在发送所有当前事件时会变为 resolved 状态的 promise。如果提供了一个超时时间并且队列需要更长时间来消耗,则 promise 将返回 false。
128 | *
129 | * @param timeout Maximum time in ms the client should wait.
130 | */
131 | export function flush(timeout?: number): PromiseLike {
132 | const client = getCurrentHub().getClient();
133 | if (client) {
134 | return client.flush(timeout);
135 | }
136 | return resolvedSyncPromise(false);
137 | }
138 |
139 | /**
140 | * A promise that resolves when all current events have been sent.
141 | * If you provide a timeout and the queue takes longer to drain the promise returns false.
142 | *
143 | * @param timeout Maximum time in ms the client should wait.
144 | */
145 | export function close(timeout?: number): PromiseLike {
146 | const client = getCurrentHub().getClient();
147 | if (client) {
148 | return client.close(timeout);
149 | }
150 | return resolvedSyncPromise(false);
151 | }
152 |
153 | /**
154 | * Wrap code within a try/catch block so the SDK is able to capture errors.
155 | * 在 try / catch 块中包装代码,以便 SDK 能够捕获错误。
156 | * 实际上是 ./helpers 文件中 warp 方法的进一步封装。
157 | *
158 | * @param fn A function to wrap.
159 | *
160 | * @returns The result of wrapped function call.
161 | */
162 | export function wrap(fn: Function): any {
163 | // tslint:disable-next-line: no-unsafe-any
164 | return internalWrap(fn)();
165 | }
166 |
--------------------------------------------------------------------------------
/src/tracekit.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:object-literal-sort-keys
2 |
3 | /**
4 | * This was originally forked from https://github.com/occ/TraceKit, but has since been
5 | * largely modified and is now maintained as part of Sentry JS SDK.
6 | */
7 |
8 | /**
9 | * An object representing a single stack frame.
10 | * {Object} StackFrame
11 | * {string} url The JavaScript or HTML file URL.
12 | * {string} func The function name, or empty for anonymous functions (if guessing did not work).
13 | * {string[]?} args The arguments passed to the function, if known.
14 | * {number=} line The line number, if known.
15 | * {number=} column The column number, if known.
16 | * {string[]} context An array of source code lines; the middle element corresponds to the correct line#.
17 | */
18 | export interface StackFrame {
19 | url: string;
20 | func: string;
21 | args: string[];
22 | line: number | null;
23 | column: number | null;
24 | }
25 |
26 | /**
27 | * An object representing a JavaScript stack trace.
28 | * {Object} StackTrace
29 | * {string} name The name of the thrown exception.
30 | * {string} message The exception error message.
31 | * {TraceKit.StackFrame[]} stack An array of stack frames.
32 | */
33 | export interface StackTrace {
34 | name: string;
35 | message: string;
36 | mechanism?: string;
37 | stack: StackFrame[];
38 | failed?: boolean;
39 | }
40 |
41 | // global reference to slice
42 | const UNKNOWN_FUNCTION = "?";
43 |
44 | // Chromium based browsers: Chrome, Brave, new Opera, new Edge
45 | const chrome = /^\s*at (?:(.*?) ?\()?((?:file|https?|blob|chrome-extension|native|eval|webpack||[-a-z]+:|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
46 | // gecko regex: `(?:bundle|\d+\.js)`: `bundle` is for react native, `\d+\.js` also but specifically for ram bundles because it
47 | // generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js
48 | // We need this specific case for now because we want no other regex to match.
49 | const gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:file|https?|blob|chrome|webpack|resource|moz-extension).*?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js))(?::(\d+))?(?::(\d+))?\s*$/i;
50 | const winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
51 | const geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
52 | const chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/;
53 | const miniapp = /^\s*at (\w.*) \((\w*.js):(\d*):(\d*)/i;
54 |
55 | /** JSDoc */
56 | export function computeStackTrace(ex: any): StackTrace {
57 | // console.log('computeStackTrace', ex)
58 | // tslint:disable:no-unsafe-any
59 |
60 | let stack = null;
61 | const popSize: number = ex && ex.framesToPop;
62 |
63 | try {
64 | // This must be tried first because Opera 10 *destroys*
65 | // its stacktrace property if you try to access the stack
66 | // property first!!
67 | stack = computeStackTraceFromStacktraceProp(ex);
68 | if (stack) {
69 | // console.log('computeStackTraceFromStacktraceProp', stack, popSize, popFrames(stack, popSize))
70 | return popFrames(stack, popSize);
71 | }
72 | } catch (e) {
73 | // no-empty
74 | }
75 |
76 | try {
77 | stack = computeStackTraceFromStackProp(ex);
78 | if (stack) {
79 | // console.log('computeStackTraceFromStackProp', stack, popSize, popFrames(stack, popSize))
80 | return popFrames(stack, popSize);
81 | }
82 | } catch (e) {
83 | // no-empty
84 | }
85 |
86 | return {
87 | message: extractMessage(ex),
88 | name: ex && ex.name,
89 | stack: [],
90 | failed: true,
91 | };
92 | }
93 |
94 | /** JSDoc */
95 | // tslint:disable-next-line:cyclomatic-complexity
96 | function computeStackTraceFromStackProp(ex: any): StackTrace | null {
97 | // tslint:disable:no-conditional-assignment
98 | if (!ex || !ex.stack) {
99 | return null;
100 | }
101 |
102 | const stack = [];
103 | const lines = ex.stack.split("\n");
104 | let isEval;
105 | let submatch;
106 | let parts;
107 | let element;
108 | // console.log('lines', lines)
109 |
110 | for (let i = 0; i < lines.length; ++i) {
111 | // console.log(lines[i], chrome.exec(lines[i]), winjs.exec(lines[i]), gecko.exec(lines[i]))
112 | if ((parts = chrome.exec(lines[i]))) {
113 | const isNative = parts[2] && parts[2].indexOf("native") === 0; // start of line
114 | isEval = parts[2] && parts[2].indexOf("eval") === 0; // start of line
115 | if (isEval && (submatch = chromeEval.exec(parts[2]))) {
116 | // throw out eval line/column and use top-most line/column number
117 | parts[2] = submatch[1]; // url
118 | parts[3] = submatch[2]; // line
119 | parts[4] = submatch[3]; // column
120 | }
121 | element = {
122 | url: parts[2],
123 | func: parts[1] || UNKNOWN_FUNCTION,
124 | args: isNative ? [parts[2]] : [],
125 | line: parts[3] ? +parts[3] : null,
126 | column: parts[4] ? +parts[4] : null,
127 | };
128 | } else if ((parts = winjs.exec(lines[i]))) {
129 | element = {
130 | url: parts[2],
131 | func: parts[1] || UNKNOWN_FUNCTION,
132 | args: [],
133 | line: +parts[3],
134 | column: parts[4] ? +parts[4] : null,
135 | };
136 | } else if ((parts = gecko.exec(lines[i]))) {
137 | isEval = parts[3] && parts[3].indexOf(" > eval") > -1;
138 | if (isEval && (submatch = geckoEval.exec(parts[3]))) {
139 | // throw out eval line/column and use top-most line number
140 | parts[1] = parts[1] || `eval`;
141 | parts[3] = submatch[1];
142 | parts[4] = submatch[2];
143 | parts[5] = ""; // no column when eval
144 | } else if (i === 0 && !parts[5] && ex.columnNumber !== void 0) {
145 | // FireFox uses this awesome columnNumber property for its top frame
146 | // Also note, Firefox's column number is 0-based and everything else expects 1-based,
147 | // so adding 1
148 | // NOTE: this hack doesn't work if top-most frame is eval
149 | stack[0].column = (ex.columnNumber as number) + 1;
150 | }
151 | element = {
152 | url: parts[3],
153 | func: parts[1] || UNKNOWN_FUNCTION,
154 | args: parts[2] ? parts[2].split(",") : [],
155 | line: parts[4] ? +parts[4] : null,
156 | column: parts[5] ? +parts[5] : null,
157 | };
158 | } else if ((parts = miniapp.exec(lines[i]))) {
159 | element = {
160 | url: parts[2],
161 | func: parts[1] || UNKNOWN_FUNCTION,
162 | args: [],
163 | line: parts[3] ? +parts[3] : null,
164 | column: parts[4] ? +parts[4] : null,
165 | };
166 | } else {
167 | continue;
168 | }
169 |
170 | if (!element.func && element.line) {
171 | element.func = UNKNOWN_FUNCTION;
172 | }
173 |
174 | stack.push(element);
175 | }
176 |
177 | if (!stack.length) {
178 | return null;
179 | }
180 |
181 | return {
182 | message: extractMessage(ex),
183 | name: ex.name,
184 | stack,
185 | };
186 | }
187 |
188 | /** JSDoc */
189 | function computeStackTraceFromStacktraceProp(ex: any): StackTrace | null {
190 | if (!ex || !ex.stacktrace) {
191 | return null;
192 | }
193 | // Access and store the stacktrace property before doing ANYTHING
194 | // else to it because Opera is not very good at providing it
195 | // reliably in other circumstances.
196 | const stacktrace = ex.stacktrace;
197 | const opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i;
198 | const opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i;
199 | const lines = stacktrace.split("\n");
200 | const stack = [];
201 | let parts;
202 |
203 | for (let line = 0; line < lines.length; line += 2) {
204 | // tslint:disable:no-conditional-assignment
205 | let element = null;
206 | if ((parts = opera10Regex.exec(lines[line]))) {
207 | element = {
208 | url: parts[2],
209 | func: parts[3],
210 | args: [],
211 | line: +parts[1],
212 | column: null,
213 | };
214 | } else if ((parts = opera11Regex.exec(lines[line]))) {
215 | element = {
216 | url: parts[6],
217 | func: parts[3] || parts[4],
218 | args: parts[5] ? parts[5].split(",") : [],
219 | line: +parts[1],
220 | column: +parts[2],
221 | };
222 | }
223 |
224 | if (element) {
225 | if (!element.func && element.line) {
226 | element.func = UNKNOWN_FUNCTION;
227 | }
228 | stack.push(element);
229 | }
230 | }
231 |
232 | if (!stack.length) {
233 | return null;
234 | }
235 |
236 | return {
237 | message: extractMessage(ex),
238 | name: ex.name,
239 | stack,
240 | };
241 | }
242 |
243 | /** Remove N number of frames from the stack */
244 | function popFrames(stacktrace: StackTrace, popSize: number): StackTrace {
245 | try {
246 | return {
247 | ...stacktrace,
248 | stack: stacktrace.stack.slice(popSize),
249 | };
250 | } catch (e) {
251 | return stacktrace;
252 | }
253 | }
254 |
255 | /**
256 | * There are cases where stacktrace.message is an Event object
257 | * https://github.com/getsentry/sentry-javascript/issues/1949
258 | * In this specific case we try to extract stacktrace.message.error.message
259 | */
260 | function extractMessage(ex: any): string {
261 | const message = ex && ex.message;
262 | // console.log('message',message)
263 | if (!message) {
264 | return "No error message";
265 | }
266 | if (message.error && typeof message.error.message === "string") {
267 | return message.error.message;
268 | }
269 | return message;
270 | }
271 |
--------------------------------------------------------------------------------
/src/transports/base.ts:
--------------------------------------------------------------------------------
1 | import { API } from "@sentry/core";
2 | import { Event, Response, Transport, TransportOptions } from "@sentry/types";
3 | import { makePromiseBuffer, PromiseBuffer, SentryError } from "@sentry/utils";
4 |
5 | /** Base Transport class implementation */
6 | export abstract class BaseTransport implements Transport {
7 | /**
8 | * @inheritDoc
9 | */
10 | public url: string;
11 |
12 | /** A simple buffer holding all requests. */
13 | protected readonly _buffer: PromiseBuffer = makePromiseBuffer(30);
14 |
15 | public constructor(public options: TransportOptions) {
16 | this.url = new API(this.options.dsn).getStoreEndpointWithUrlEncodedAuth();
17 | }
18 |
19 | /**
20 | * @inheritDoc
21 | */
22 | public sendEvent(_: Event): PromiseLike {
23 | throw new SentryError(
24 | "Transport Class has to implement `sendEvent` method"
25 | );
26 | }
27 |
28 | /**
29 | * @inheritDoc
30 | */
31 | public close(timeout?: number): PromiseLike {
32 | return this._buffer.drain(timeout);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/transports/index.ts:
--------------------------------------------------------------------------------
1 | export { BaseTransport } from "./base";
2 | export { XHRTransport } from "./xhr";
3 |
--------------------------------------------------------------------------------
/src/transports/xhr.ts:
--------------------------------------------------------------------------------
1 | import { Event, Response } from "@sentry/types";
2 | import { eventStatusFromHttpCode } from '@sentry/utils';
3 |
4 | import { sdk } from "../crossPlatform";
5 |
6 | import { BaseTransport } from "./base";
7 |
8 | /** `XHR` based transport */
9 | export class XHRTransport extends BaseTransport {
10 | /**
11 | * @inheritDoc
12 | */
13 | public sendEvent(event: Event): PromiseLike {
14 | const request = sdk.request || sdk.httpRequest;
15 |
16 | return this._buffer.add(
17 | () => new Promise((resolve, reject) => {
18 | // tslint:disable-next-line: no-unsafe-any
19 | request({
20 | url: this.url,
21 | method: "POST",
22 | data: JSON.stringify(event),
23 | header: {
24 | "content-type": "application/json"
25 | },
26 | success(res: { statusCode: number }): void {
27 | resolve({
28 | status: eventStatusFromHttpCode(res.statusCode)
29 | });
30 | },
31 | fail(error: object): void {
32 | reject(error);
33 | }
34 | });
35 | })
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/version.ts:
--------------------------------------------------------------------------------
1 | export const SDK_NAME = "sentry.javascript.miniapp";
2 | export const SDK_VERSION = "0.12.0";
3 |
--------------------------------------------------------------------------------
/test/tslint.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/@sentry/typescript/tsconfig.json",
3 | "include": [
4 | "src/**/*",
5 | "test/**/*"
6 | ],
7 | "exclude": [
8 | "./examples"
9 | ],
10 | "compilerOptions": {
11 | "outDir": "esm",
12 | "module": "es6",
13 | "target": "es5"
14 | }
15 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/@sentry/typescript/tsconfig.json",
3 | "include": ["src/**/*", "test/**/*"],
4 | "exclude": ["./examples"],
5 | "compilerOptions": {
6 | "outDir": "dist",
7 | "module": "commonjs",
8 | "target": "es5",
9 | "sourceMap": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sentry/typescript/tslint",
3 | "rules": {
4 | "object-literal-sort-keys": false
5 | }
6 | }
--------------------------------------------------------------------------------
/webpack.common.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: "production",
3 | entry: "./src/index.ts",
4 | output: {
5 | filename: "sentry-miniapp.min.js",
6 | library: "Sentry",
7 | libraryTarget: "commonjs2"
8 | },
9 | resolve: {
10 | extensions: [".tsx", ".ts", ".js"]
11 | },
12 | watchOptions: {
13 | ignored: /node_modules|examples/, //忽略不用监听变更的目录
14 | aggregateTimeout: 300, // 文件发生改变后多长时间后再重新编译(Add a delay before rebuilding once the first file changed )
15 | poll: 1000 //每秒询问的文件变更的次数
16 | },
17 | plugins: [],
18 | module: {
19 | rules: [
20 | {
21 | test: /\.tsx?$/,
22 | loader: "ts-loader",
23 | exclude: /node_modules/
24 | }
25 | ]
26 | },
27 | devtool: "source-map"
28 | };
29 |
--------------------------------------------------------------------------------
/webpack.config.dd.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require("webpack-merge");
3 | const common = require("./webpack.common.js");
4 |
5 | module.exports = merge(common, {
6 | output: {
7 | filename: "sentry-miniapp.dd.min.js",
8 | path: path.resolve(__dirname, "./examples/ddapp/vendor")
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/webpack.config.my.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require("webpack-merge");
3 | const common = require("./webpack.common.js");
4 |
5 | module.exports = merge(common, {
6 | output: {
7 | filename: "sentry-miniapp.my.min.js",
8 | path: path.resolve(__dirname, "./examples/myapp/vendor")
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/webpack.config.qq.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require("webpack-merge");
3 | const common = require("./webpack.common.js");
4 |
5 | module.exports = merge(common, {
6 | output: {
7 | filename: "sentry-miniapp.qq.min.js",
8 | path: path.resolve(__dirname, "./examples/qq/vendor"),
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/webpack.config.swan.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require("webpack-merge");
3 | const common = require("./webpack.common.js");
4 |
5 | module.exports = merge(common, {
6 | output: {
7 | filename: "sentry-miniapp.swan.min.js",
8 | path: path.resolve(__dirname, "./examples/swan/vendor"),
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/webpack.config.tt.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require("webpack-merge");
3 | const common = require("./webpack.common.js");
4 |
5 | module.exports = merge(common, {
6 | output: {
7 | filename: "sentry-miniapp.tt.min.js",
8 | path: path.resolve(__dirname, "./examples/ttapp/vendor")
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/webpack.config.wx.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require("webpack-merge");
3 | const common = require("./webpack.common.js");
4 |
5 | module.exports = merge(common, {
6 | output: {
7 | filename: "sentry-miniapp.wx.min.js",
8 | path: path.resolve(__dirname, "./examples/weapp/vendor"),
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/webpack.config.wxgame.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require("webpack-merge");
3 | const common = require("./webpack.common.js");
4 |
5 | module.exports = merge(common, {
6 | output: {
7 | filename: "sentry-miniapp.wx.min.js",
8 | path: path.resolve(__dirname, "./examples/wegame/vendor"),
9 | },
10 | });
11 |
--------------------------------------------------------------------------------