├── .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 | ![npm version](https://img.shields.io/npm/v/sentry-miniapp) 4 | ![npm download](https://img.shields.io/npm/dm/sentry-miniapp) 5 | ![github forks](https://img.shields.io/github/forks/lizhiyao/sentry-miniapp?style=social) 6 | ![github stars](https://img.shields.io/github/stars/lizhiyao/sentry-miniapp?style=social) 7 | ![github watchers](https://img.shields.io/github/watchers/lizhiyao/sentry-miniapp?style=social) 8 | ![github license](https://img.shields.io/github/license/lizhiyao/sentry-miniapp) 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 | ![Dashboard](docs/sentry-core.png) 115 | 116 | #### sentry-hub 设计图 117 | 118 | ![Dashboard](docs/sentry-hub.png) 119 | 120 | #### sentry-miniapp 设计图 121 | 122 | ![Dashboard](docs/sentry-miniapp.png) 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 | ![Dashboard](docs/screenshot/sentry-admin.png) 161 | ![Error00](docs/screenshot/sentry-error-00.png) 162 | ![Error01](docs/screenshot/sentry-error-01.png) 163 | ![Error02](docs/screenshot/sentry-error-02.png) 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 | --------------------------------------------------------------------------------