├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── babel.cjs.json ├── babel.esm.json ├── demo ├── app.js ├── app.json ├── app.wxss ├── miniprogram_npm │ └── @cloudcare │ │ └── rum-miniapp │ │ ├── boot │ │ ├── buildEnv.js │ │ ├── rum.entry.js │ │ └── rum.js │ │ ├── core │ │ ├── baseInfo.js │ │ ├── configuration.js │ │ ├── dataMap.js │ │ ├── downloadProxy.js │ │ ├── errorCollection.js │ │ ├── errorTools.js │ │ ├── lifeCycle.js │ │ ├── observable.js │ │ ├── sdk.js │ │ ├── transport.js │ │ └── xhrProxy.js │ │ ├── helper │ │ ├── enums.js │ │ ├── tracekit.js │ │ └── utils.js │ │ ├── index.js │ │ └── rumEventsCollection │ │ ├── app │ │ ├── appCollection.js │ │ └── index.js │ │ ├── assembly.js │ │ ├── error │ │ └── errorCollection.js │ │ ├── page │ │ ├── index.js │ │ └── viewCollection.js │ │ ├── parentContexts.js │ │ ├── performanceCollection.js │ │ ├── requestCollection.js │ │ ├── resource │ │ ├── resourceCollection.js │ │ └── resourceUtils.js │ │ ├── trackEventCounts.js │ │ └── transport │ │ └── batch.js ├── package.json ├── pages │ ├── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── logs │ │ ├── logs.js │ │ ├── logs.json │ │ ├── logs.wxml │ │ └── logs.wxss │ ├── rum.js │ ├── rum.json │ ├── rum.wxml │ └── rum.wxss ├── project.config.json ├── sitemap.json └── utils │ └── util.js ├── demo2 ├── .hbuilderx │ └── launch.json ├── App.vue ├── demo2 │ ├── App.vue │ ├── main.js │ ├── manifest.json │ ├── pages.json │ ├── pages │ │ └── index │ │ │ └── index.vue │ ├── static │ │ └── logo.png │ └── uni.scss ├── main.js ├── manifest.json ├── miniprogram │ ├── dataflux-rum-miniapp.js │ └── dataflux-rum-miniapp.js.map ├── pages.json ├── pages │ ├── home │ │ └── home.vue │ └── index │ │ └── index.vue ├── static │ └── logo.png ├── test-sdk │ ├── App.vue │ ├── main.js │ ├── manifest.json │ ├── pages.json │ ├── pages │ │ └── index │ │ │ └── index.vue │ ├── static │ │ └── logo.png │ └── uni.scss ├── uni.scss └── unpackage │ └── dist │ └── dev │ ├── .automator │ ├── mp-alipay │ │ └── .automator.json │ └── mp-weixin │ │ └── .automator.json │ ├── .sourcemap │ └── mp-weixin │ │ ├── common │ │ ├── main.js.map │ │ ├── runtime.js.map │ │ └── vendor.js.map │ │ └── pages │ │ ├── home │ │ └── home.js.map │ │ └── index │ │ └── index.js.map │ └── mp-weixin │ ├── app.js │ ├── app.json │ ├── app.wxss │ ├── common │ ├── main.js │ ├── main.wxss │ ├── runtime.js │ └── vendor.js │ ├── pages │ ├── home │ │ ├── home.js │ │ ├── home.json │ │ └── home.wxml │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── project.config.json │ ├── sitemap.json │ └── static │ └── logo.png ├── package.json ├── scripts ├── build-env.js └── replace-build-env.js ├── src ├── boot │ ├── buildEnv.js │ ├── rum.entry.js │ └── rum.js ├── core │ ├── baseInfo.js │ ├── configuration.js │ ├── dataMap.js │ ├── downloadProxy.js │ ├── errorCollection.js │ ├── errorTools.js │ ├── lifeCycle.js │ ├── miniaTouch.js │ ├── observable.js │ ├── sdk.js │ ├── transport.js │ └── xhrProxy.js ├── helper │ ├── enums.js │ ├── tracekit.js │ └── utils.js ├── index.js └── rumEventsCollection │ ├── action │ ├── actionCollection.js │ └── trackActions.js │ ├── app │ ├── appCollection.js │ └── index.js │ ├── assembly.js │ ├── error │ └── errorCollection.js │ ├── page │ ├── index.js │ └── viewCollection.js │ ├── parentContexts.js │ ├── performanceCollection.js │ ├── requestCollection.js │ ├── resource │ ├── resourceCollection.js │ └── resourceUtils.js │ ├── setDataCollection.js │ ├── tracing │ ├── ddtraceTracer.js │ ├── jaegerTracer.js │ ├── skywalkingTracer.js │ ├── tracer.js │ ├── w3cTraceParentTracer.js │ ├── zipkinMultiTracer.js │ └── zipkinSingleTracer.js │ ├── trackEventCounts.js │ ├── trackPageActiveites.js │ └── transport │ └── batch.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm 3 | bundle 4 | demo 5 | demo2 6 | node_modules 7 | .vscode 8 | *.pyc 9 | *.tsbuildinfo 10 | scripts/publish-oss.js 11 | # logs 12 | yarn-error.log 13 | npm-debug.log 14 | lerna-debug.log 15 | local.log 16 | 17 | # ide 18 | 19 | .idea 20 | *.sublime-* 21 | # misc 22 | .DS_Store 23 | ._* 24 | .Spotlight-V100 25 | .Trashes 26 | 27 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !/cjs/**/* 3 | !/esm/**/* 4 | *.tsbuildinfo -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 CloudCare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信小程序DataFlux RUM 数据采集SDK 2 | 通过引入sdk文件,监控小程序性能指标,错误log,以及资源请求情况数据,上报到DataFlux 平台datakit 3 | 4 | ## 使用方法 5 | ### 在小程序的app.js文件以如下方式引入代码 6 | ### npm 引入(可参考微信官方[npm引入方式](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)) 7 | ```javascript 8 | const { datafluxRum } = require('@cloudcare/rum-miniapp') 9 | // 初始化 Rum 10 | datafluxRum.init({ 11 | datakitOrigin: 'https://datakit.xxx.com/',// 必填,Datakit域名地址 需要在微信小程序管理后台加上域名白名单 12 | applicationId: 'appid_xxxxxxx', // 必填,dataflux 平台生成的应用ID 13 | env: 'testing', // 选填,小程序的环境 14 | version: '1.0.0' // 选填,小程序版本 15 | }) 16 | ``` 17 | ### CDN 下载文件本地方式引入([下载地址](https://static.dataflux.cn/miniapp-sdk/v2/dataflux-rum-miniapp.js)) 18 | 19 | ```javascript 20 | const { datafluxRum } = require('./lib/dataflux-rum-miniapp.js') 21 | // 初始化 Rum 22 | datafluxRum.init({ 23 | datakitOrigin: 'https://datakit.xxx.com/',// 必填,Datakit域名地址 需要在微信小程序管理后台加上域名白名单 24 | applicationId: 'appid_xxxxxxx', // 必填,dataflux 平台生成的应用ID 25 | env: 'testing', // 选填,小程序的环境 26 | version: '1.0.0' // 选填,小程序版本 27 | }) 28 | ``` 29 | 30 | ## 配置 31 | 32 | ### 初始化参数 33 | 34 | | 参数 | 类型 | 是否必须 | 默认值 | 描述 | 35 | | ----------------------------------------------- | ------- | -------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 36 | | `applicationId` | String | 是 | | 从 dataflux 创建的应用 ID | 37 | | `datakitOrigin` | String | 是 | | datakit 数据上报 Origin;`注意:需要在小程序管理后台加上request白名单` | 38 | | `env` | String | 否 | | 小程序 应用当前环境, 如 prod:线上环境;gray:灰度环境;pre:预发布环境 common:日常环境;local:本地环境; | 39 | | `version` | String | 否 | | 小程序 应用的版本号 | 40 | | `sampleRate` | Number | 否 | `100` | 指标数据收集百分比: `100`表示全收集,`0`表示不收集 | 41 | | `traceType` $\color{#FF0000}{新增}$ | Enum | 否 | `ddtrace` | 与 APM 采集工具连接的请求header类型,目前兼容的类型包括:`ddtrace`、`zipkin`、`skywalking_v3`、`jaeger`、`zipkin_single_header`、`w3c_traceparent`。*注: opentelemetry 支持 `zipkin_single_header`,`w3c_traceparent`,`zipkin`三种类型* | 42 | | `traceId128Bit` $\color{#FF0000}{新增}$ | Boolean | 否 | `false` | 是否以128位的方式生成 `traceID`,与`traceType` 对应,目前支持类型 `zipkin`、`jaeger` | 43 | | `allowedTracingOrigins` $\color{#FF0000}{新增}$ | Array | 否 | `[]` | 允许注入 `trace` 采集器所需header头部的所有请求列表。可以是请求的origin,也可以是是正则,origin: `协议(包括://),域名(或IP地址)[和端口号]` 例如:`["https://api.example.com", /https:\/\/.*\.my-api-domain\.com/]` | 44 | | `trackInteractions` | Boolean | 否 | `false` | 是否开启用户行为采集 | 45 | 46 | ## 注意事项 47 | 48 | 1. `datakitOrigin` 所对应的datakit域名必须在小程序管理后台加上request白名单 49 | 2. 因为目前微信小程序请求资源API`wx.request`、`wx.downloadFile`返回数据中`profile`字段目前ios系统不支持返回,所以会导致收集的资源信息中和timing相关的数据收集不全。目前暂无解决方案,[request](https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html), [downloadFile](https://developers.weixin.qq.com/miniprogram/dev/api/network/download/wx.downloadFile.html) ;[API支持情况](https://developers.weixin.qq.com/community/develop/doc/000ecaa8b580c80601cac8e6f56000?highLine=%2520request%2520profile) 50 | 3. `trackInteractions` 用户行为采集开启后,因为微信小程序的限制,无法采集到控件的内容和结构数据,所以在小程序 SDK 里面我们采取的是声明式编程,通过在 wxml 文件里面设置 data-name 属性,可以给 交互元素 添加名称,方便后续统计是定位操作记录, 例如: 51 | ```js 52 | 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /babel.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": { 7 | "esmodules": false 8 | }, 9 | "modules": "cjs" 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /babel.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": { 7 | "esmodules": true 8 | }, 9 | "modules": false 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /demo/app.js: -------------------------------------------------------------------------------- 1 | // //app.js 2 | const { datafluxRum } = require('./miniprogram/dataflux-rum-miniapp') 3 | // // 初始化 Sentry 4 | // const { datafluxRum } = require('@cloudcare/rum-miniapp') 5 | 6 | datafluxRum.init({ 7 | datakitOrigin: 'http://172.16.2.201:31845', 8 | applicationId: 'appid_6cb4c98eba9143c88c83e544407b1c74', 9 | env: 'prod', 10 | version: '1.0.0', 11 | trackInteractions: true, 12 | allowedTracingOrigins: ['http://testing-ft2x-api.cloudcare.cn','http://172.16.5.9:5001'], 13 | traceType: 'skywalking_v3' 14 | }) 15 | // datafluxRum.setUser({ 16 | // id: '1234', 17 | // name: 'John Doe', 18 | // email: 'john@doe.com', 19 | // }) 20 | // datafluxRum && datafluxRum.addRumGlobalContext('isvip', 'xxxx'); 21 | // datafluxRum.addRumGlobalContext('activity', { 22 | // hasPaid: true, 23 | // amount: 23.42 24 | // }); 25 | // const { datafluxRum } = require('@cloudcare/rum-miniapp') 26 | // // // 初始化 Sentry 27 | // datafluxRum.init({ 28 | // datakitOrigin: 'http://10.100.64.161:9529/',// 必填,Datakit域名地址 需要在微信小程序管理后台加上域名白名单 29 | // applicationId: 'appid_14eae490469e11eba9eb920038d3be75', // 必填,dataflux 平台生成的应用ID 30 | // env: 'testing', // 选填,小程序的环境 31 | // version: '1.0.0', // 选填,小程序版本 32 | // }) 33 | // wx.request({ 34 | // url: 'url', 35 | // }) 36 | // var UNKNOWN_FUNCTION = '?' 37 | // function computeStackTraceFromStackProp(ex) { 38 | // if (!ex.stack) { 39 | // return 40 | // } 41 | 42 | // // tslint:disable-next-line max-line-length 43 | // var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i 44 | // // tslint:disable-next-line max-line-length 45 | // var gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i 46 | // // tslint:disable-next-line max-line-length 47 | // var winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i 48 | 49 | // // Used to additionally parse URL/line/column from eval frames 50 | // var isEval 51 | // var geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i 52 | // var chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/ 53 | // var lines = ex.stack.split('\n') 54 | // var stack = [] 55 | // var submatch 56 | // var parts 57 | // var element 58 | 59 | // for (var i = 0, j = lines.length; i < j; i += 1) { 60 | // if (chrome.exec(lines[i])) { 61 | // parts = chrome.exec(lines[i]) 62 | // var isNative = parts[2] && parts[2].indexOf('native') === 0 // start of line 63 | // isEval = parts[2] && parts[2].indexOf('eval') === 0 // start of line 64 | // submatch = chromeEval.exec(parts[2]) 65 | // if (isEval && submatch) { 66 | // // throw out eval line/column and use top-most line/column number 67 | // parts[2] = submatch[1] // url 68 | // parts[3] = submatch[2] // line 69 | // parts[4] = submatch[3] // column 70 | // } 71 | // element = { 72 | // args: isNative ? [parts[2]] : [], 73 | // column: parts[4] ? +parts[4] : undefined, 74 | // func: parts[1] || UNKNOWN_FUNCTION, 75 | // line: parts[3] ? +parts[3] : undefined, 76 | // url: !isNative ? parts[2] : undefined, 77 | // } 78 | // } else if (winjs.exec(lines[i])) { 79 | // parts = winjs.exec(lines[i]) 80 | // element = { 81 | // args: [], 82 | // column: parts[4] ? +parts[4] : undefined, 83 | // func: parts[1] || UNKNOWN_FUNCTION, 84 | // line: +parts[3], 85 | // url: parts[2], 86 | // } 87 | // } else if (gecko.exec(lines[i])) { 88 | // parts = gecko.exec(lines[i]) 89 | // isEval = parts[3] && parts[3].indexOf(' > eval') > -1 90 | // submatch = geckoEval.exec(parts[3]) 91 | // if (isEval && submatch) { 92 | // // throw out eval line/column and use top-most line number 93 | // parts[3] = submatch[1] 94 | // parts[4] = submatch[2] 95 | // parts[5] = undefined // no column when eval 96 | // } else if (i === 0 && !parts[5] && !ex.columnNumber) { 97 | // // FireFox uses this awesome columnNumber property for its top frame 98 | // // Also note, Firefox's column number is 0-based and everything else expects 1-based, 99 | // // so adding 1 100 | // // NOTE: this hack doesn't work if top-most frame is eval 101 | // stack[0].column = ex.columnNumber + 1 102 | // } 103 | // element = { 104 | // args: parts[2] ? parts[2].split(',') : [], 105 | // column: parts[5] ? +parts[5] : undefined, 106 | // func: parts[1] || UNKNOWN_FUNCTION, 107 | // line: parts[4] ? +parts[4] : undefined, 108 | // url: parts[3], 109 | // } 110 | // } else { 111 | // continue 112 | // } 113 | 114 | // if (!element.func && element.line) { 115 | // element.func = UNKNOWN_FUNCTION 116 | // } 117 | // stack.push(element) 118 | // } 119 | 120 | // if (!stack.length) { 121 | // return 122 | // } 123 | 124 | // return { 125 | // stack, 126 | // message: ex.message, 127 | // name: ex.name, 128 | // } 129 | // } 130 | // wx.onError((err) => { 131 | // var error = new Error(err) 132 | // var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/ 133 | // console.log(error.message,'message') 134 | // console.log(error.message.split('\n')[2].match(ERROR_TYPES_RE)) 135 | // }) 136 | App({ 137 | // onError: function(t) { 138 | // var error = new Error(t) 139 | // var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/ 140 | // console.log(Object.getOwnPropertyDescriptors(error),'error') 141 | // var groups = error.message.match(ERROR_TYPES_RE) 142 | // console.log(groups,error.message,'arguments') 143 | // }, 144 | onLaunch: function () { 145 | // 展示本地存储能力 146 | var logs = wx.getStorageSync('logs') || [] 147 | logs.unshift(Date.now()) 148 | wx.setStorageSync('logs', logs) 149 | 150 | // 登录 151 | wx.login({ 152 | success: (res) => { 153 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 154 | }, 155 | }) 156 | // 获取用户信息 157 | wx.getSetting({ 158 | success: (res) => { 159 | if (res.authSetting['scope.userInfo']) { 160 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 161 | wx.getUserInfo({ 162 | success: (res) => { 163 | // 可以将 res 发送给后台解码出 unionId 164 | this.globalData.userInfo = res.userInfo 165 | 166 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 167 | // 所以此处加入 callback 以防止这种情况 168 | if (this.userInfoReadyCallback) { 169 | this.userInfoReadyCallback(res) 170 | } 171 | }, 172 | }) 173 | } 174 | }, 175 | }) 176 | }, 177 | globalData: { 178 | userInfo: null, 179 | }, 180 | }) 181 | -------------------------------------------------------------------------------- /demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/logs/logs", 5 | "pages/rum" 6 | ], 7 | "window": { 8 | "backgroundTextStyle": "light", 9 | "navigationBarBackgroundColor": "#fff", 10 | "navigationBarTitleText": "Weixin", 11 | "navigationBarTextStyle": "black" 12 | }, 13 | "style": "v2", 14 | "sitemapLocation": "sitemap.json" 15 | } -------------------------------------------------------------------------------- /demo/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 | -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/boot/buildEnv.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.buildEnv = void 0; 7 | var buildEnv = { 8 | sdkVersion: '2.0.3', 9 | sdkName: 'df_miniapp_rum_sdk' 10 | }; 11 | exports.buildEnv = buildEnv; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/boot/rum.entry.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.datafluxRum = exports.makeRum = void 0; 7 | 8 | var _utils = require("../helper/utils"); 9 | 10 | var _rum = require("./rum"); 11 | 12 | var makeRum = function makeRum(startRumImpl) { 13 | var isAlreadyInitialized = false; 14 | var rumGlobal = { 15 | init: function init(userConfiguration) { 16 | if (typeof userConfiguration === 'undefined') { 17 | userConfiguration = {}; 18 | } 19 | 20 | if (!canInitRum(userConfiguration)) { 21 | return; 22 | } 23 | 24 | startRumImpl(userConfiguration); 25 | isAlreadyInitialized = true; 26 | } 27 | }; 28 | return rumGlobal; 29 | 30 | function canInitRum(userConfiguration) { 31 | if (isAlreadyInitialized) { 32 | console.error('DATAFLUX_RUM is already initialized.'); 33 | return false; 34 | } 35 | 36 | if (!userConfiguration.applicationId) { 37 | console.error('Application ID is not configured, no RUM data will be collected.'); 38 | return false; 39 | } 40 | 41 | if (!userConfiguration.datakitOrigin) { 42 | console.error('datakitOrigin is not configured, no RUM data will be collected.'); 43 | return false; 44 | } 45 | 46 | if (userConfiguration.sampleRate !== undefined && !(0, _utils.isPercentage)(userConfiguration.sampleRate)) { 47 | console.error('Sample Rate should be a number between 0 and 100'); 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | }; 54 | 55 | exports.makeRum = makeRum; 56 | var datafluxRum = makeRum(_rum.startRum); 57 | exports.datafluxRum = datafluxRum; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/boot/rum.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startRum = void 0; 7 | 8 | var _buildEnv = require("./buildEnv"); 9 | 10 | var _lifeCycle = require("../core/lifeCycle"); 11 | 12 | var _configuration = require("../core/configuration"); 13 | 14 | var _errorCollection = require("../rumEventsCollection/error/errorCollection"); 15 | 16 | var _assembly = require("../rumEventsCollection/assembly"); 17 | 18 | var _parentContexts = require("../rumEventsCollection/parentContexts"); 19 | 20 | var _batch = require("../rumEventsCollection/transport/batch"); 21 | 22 | var _viewCollection = require("../rumEventsCollection/page/viewCollection"); 23 | 24 | var _requestCollection = require("../rumEventsCollection/requestCollection"); 25 | 26 | var _resourceCollection = require("../rumEventsCollection/resource/resourceCollection"); 27 | 28 | var _appCollection = require("../rumEventsCollection/app/appCollection"); 29 | 30 | var _performanceCollection = require("../rumEventsCollection/performanceCollection"); 31 | 32 | var _setDataCollection = require("../rumEventsCollection/setDataCollection"); 33 | 34 | var _actionCollection = require("../rumEventsCollection/action/actionCollection"); 35 | 36 | var _sdk = require("../core/sdk"); 37 | 38 | var startRum = function startRum(userConfiguration) { 39 | var configuration = (0, _configuration.commonInit)(userConfiguration, _buildEnv.buildEnv); 40 | var lifeCycle = new _lifeCycle.LifeCycle(); 41 | var parentContexts = (0, _parentContexts.startParentContexts)(lifeCycle); 42 | var batch = (0, _batch.startRumBatch)(configuration, lifeCycle); 43 | (0, _assembly.startRumAssembly)(userConfiguration.applicationId, configuration, lifeCycle, parentContexts); 44 | (0, _appCollection.startAppCollection)(lifeCycle, configuration); 45 | (0, _resourceCollection.startResourceCollection)(lifeCycle, configuration); 46 | (0, _viewCollection.startViewCollection)(lifeCycle, configuration); 47 | (0, _errorCollection.startErrorCollection)(lifeCycle, configuration); 48 | (0, _requestCollection.startRequestCollection)(lifeCycle, configuration); 49 | (0, _performanceCollection.startPagePerformanceObservable)(lifeCycle, configuration); 50 | (0, _setDataCollection.startSetDataColloction)(lifeCycle); 51 | (0, _actionCollection.startActionCollection)(lifeCycle, configuration); 52 | }; 53 | 54 | exports.startRum = startRum; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/baseInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _sdk = require("../core/sdk"); 9 | 10 | var _utils = require("../helper/utils"); 11 | 12 | var _enums = require("../helper/enums"); 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 17 | 18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 19 | 20 | var BaseInfo = /*#__PURE__*/function () { 21 | function BaseInfo() { 22 | _classCallCheck(this, BaseInfo); 23 | 24 | this.sessionId = (0, _utils.UUID)(); 25 | this.getDeviceInfo(); 26 | this.getNetWork(); 27 | } 28 | 29 | _createClass(BaseInfo, [{ 30 | key: "getDeviceInfo", 31 | value: function getDeviceInfo() { 32 | try { 33 | var deviceInfo = _sdk.sdk.getSystemInfoSync(); 34 | 35 | var osInfo = deviceInfo.system.split(' '); 36 | var osVersion = osInfo.length > 1 && osInfo[1]; 37 | var osVersionMajor = osVersion.split('.').length && osVersion.split('.')[0]; 38 | var deviceUUid = ''; 39 | 40 | if (deviceInfo.host) { 41 | deviceUUid = deviceInfo.host.appId; 42 | } 43 | 44 | this.deviceInfo = { 45 | screenSize: "".concat(deviceInfo.screenWidth, "*").concat(deviceInfo.screenHeight, " "), 46 | platform: deviceInfo.platform, 47 | platformVersion: deviceInfo.version, 48 | osVersion: osVersion, 49 | osVersionMajor: osVersionMajor, 50 | os: osInfo.length > 1 && osInfo[0], 51 | brand: deviceInfo.brand, 52 | model: deviceInfo.model, 53 | frameworkVersion: deviceInfo.SDKVersion, 54 | pixelRatio: deviceInfo.pixelRatio, 55 | deviceUuid: deviceUUid 56 | }; 57 | } catch (e) {} 58 | } 59 | }, { 60 | key: "getClientID", 61 | value: function getClientID() { 62 | var clienetId = _sdk.sdk.getStorageSync(_enums.CLIENT_ID_TOKEN); 63 | 64 | if (!clienetId) { 65 | clienetId = (0, _utils.UUID)(); 66 | 67 | _sdk.sdk.setStorageSync(_enums.CLIENT_ID_TOKEN, clienetId); 68 | } 69 | 70 | return clienetId; 71 | } 72 | }, { 73 | key: "getNetWork", 74 | value: function getNetWork() { 75 | var _this = this; 76 | 77 | _sdk.sdk.getNetworkType({ 78 | success: function success(e) { 79 | _this.deviceInfo.network = e.networkType ? e.networkType : 'unknown'; 80 | } 81 | }); 82 | 83 | _sdk.sdk.onNetworkStatusChange(function (e) { 84 | _this.deviceInfo.network = e.networkType ? e.networkType : 'unknown'; 85 | }); 86 | } 87 | }, { 88 | key: "getSessionId", 89 | value: function getSessionId() { 90 | return this.sessionId; 91 | } 92 | }]); 93 | 94 | return BaseInfo; 95 | }(); 96 | 97 | var _default = new BaseInfo(); 98 | 99 | exports["default"] = _default; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/configuration.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.commonInit = commonInit; 7 | exports.isIntakeRequest = isIntakeRequest; 8 | exports.DEFAULT_CONFIGURATION = void 0; 9 | 10 | var _utils = require("../helper/utils"); 11 | 12 | var _enums = require("../helper/enums"); 13 | 14 | var TRIM_REGIX = /^\s+|\s+$/g; 15 | var DEFAULT_CONFIGURATION = { 16 | sampleRate: 100, 17 | flushTimeout: 30 * _enums.ONE_SECOND, 18 | maxErrorsByMinute: 3000, 19 | 20 | /** 21 | * Logs intake limit 22 | */ 23 | maxBatchSize: 50, 24 | maxMessageSize: 256 * _enums.ONE_KILO_BYTE, 25 | 26 | /** 27 | * beacon payload max queue size implementation is 64kb 28 | * ensure that we leave room for logs, rum and potential other users 29 | */ 30 | batchBytesLimit: 16 * _enums.ONE_KILO_BYTE, 31 | datakitUrl: '', 32 | 33 | /** 34 | * arbitrary value, byte precision not needed 35 | */ 36 | requestErrorResponseLengthLimit: 32 * _enums.ONE_KILO_BYTE, 37 | trackInteractions: false 38 | }; 39 | exports.DEFAULT_CONFIGURATION = DEFAULT_CONFIGURATION; 40 | 41 | function trim(str) { 42 | return str.replace(TRIM_REGIX, ''); 43 | } 44 | 45 | function getDatakitUrlUrl(url) { 46 | if (url && url.lastIndexOf('/') === url.length - 1) return trim(url) + 'v1/write/rum'; 47 | return trim(url) + '/v1/write/rum'; 48 | } 49 | 50 | function commonInit(userConfiguration, buildEnv) { 51 | var transportConfiguration = { 52 | applicationId: userConfiguration.applicationId, 53 | env: userConfiguration.env || '', 54 | version: userConfiguration.version || '', 55 | sdkVersion: buildEnv.sdkVersion, 56 | sdkName: buildEnv.sdkName, 57 | datakitUrl: getDatakitUrlUrl(userConfiguration.datakitUrl || userConfiguration.datakitOrigin), 58 | tags: userConfiguration.tags || [] 59 | }; 60 | 61 | if ('trackInteractions' in userConfiguration) { 62 | transportConfiguration.trackInteractions = !!userConfiguration.trackInteractions; 63 | } 64 | 65 | return (0, _utils.extend2Lev)(DEFAULT_CONFIGURATION, transportConfiguration); 66 | } 67 | 68 | var haveSameOrigin = function haveSameOrigin(url1, url2) { 69 | var parseUrl1 = (0, _utils.urlParse)(url1).getParse(); 70 | var parseUrl2 = (0, _utils.urlParse)(url2).getParse(); 71 | return parseUrl1.Origin === parseUrl2.Origin; 72 | }; 73 | 74 | function isIntakeRequest(url, configuration) { 75 | return haveSameOrigin(url, configuration.datakitUrl); 76 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/dataMap.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.dataMap = exports.commonTags = void 0; 7 | 8 | var _enums = require("../helper/enums"); 9 | 10 | // 需要用双引号将字符串类型的field value括起来, 这里有数组标示[string, path] 11 | var commonTags = { 12 | sdk_name: '_dd.sdk_name', 13 | sdk_version: '_dd.sdk_version', 14 | app_id: 'application.id', 15 | env: '_dd.env', 16 | version: '_dd.version', 17 | userid: 'user.user_id', 18 | session_id: 'session.id', 19 | session_type: 'session.type', 20 | is_signin: 'user.is_signin', 21 | device: 'device.brand', 22 | model: 'device.model', 23 | device_uuid: 'device.device_uuid', 24 | os: 'device.os', 25 | os_version: 'device.os_version', 26 | os_version_major: 'device.os_version_major', 27 | screen_size: 'device.screen_size', 28 | network_type: 'device.network_type', 29 | platform: 'device.platform', 30 | platform_version: 'device.platform_version', 31 | app_framework_version: 'device.framework_version', 32 | view_id: 'page.id', 33 | view_name: 'page.route', 34 | view_referer: 'page.referer' 35 | }; 36 | exports.commonTags = commonTags; 37 | var dataMap = { 38 | view: { 39 | type: _enums.RumEventType.VIEW, 40 | tags: { 41 | view_apdex_level: 'page.apdex_level', 42 | is_active: 'page.is_active' 43 | }, 44 | fields: { 45 | page_fmp: 'page.fmp', 46 | first_paint_time: 'page.fpt', 47 | loading_time: 'page.loading_time', 48 | onload_to_onshow: 'page.onload2onshow', 49 | onshow_to_onready: 'page.onshow2onready', 50 | time_spent: 'page.time_spent', 51 | view_error_count: 'page.error.count', 52 | view_resource_count: 'page.resource.count', 53 | view_long_task_count: 'page.long_task.count', 54 | view_action_count: 'page.action.count', 55 | view_setdata_count: 'page.setdata.count' 56 | } 57 | }, 58 | resource: { 59 | type: _enums.RumEventType.RESOURCE, 60 | tags: { 61 | resource_type: 'resource.type', 62 | resource_status: 'resource.status', 63 | resource_status_group: 'resource.status_group', 64 | resource_method: 'resource.method', 65 | resource_url: 'resource.url', 66 | resource_url_host: 'resource.url_host', 67 | resource_url_path: 'resource.url_path', 68 | resource_url_path_group: 'resource.url_path_group', 69 | resource_url_query: 'resource.url_query' 70 | }, 71 | fields: { 72 | resource_size: 'resource.size', 73 | resource_load: 'resource.load', 74 | resource_dns: 'resource.dns', 75 | resource_tcp: 'resource.tcp', 76 | resource_ssl: 'resource.ssl', 77 | resource_ttfb: 'resource.ttfb', 78 | resource_trans: 'resource.trans', 79 | resource_first_byte: 'resource.firstbyte', 80 | duration: 'resource.duration' 81 | } 82 | }, 83 | error: { 84 | type: _enums.RumEventType.ERROR, 85 | tags: { 86 | error_source: 'error.source', 87 | error_type: 'error.type', 88 | resource_url: 'error.resource.url', 89 | resource_url_host: 'error.resource.url_host', 90 | resource_url_path: 'error.resource.url_path', 91 | resource_url_path_group: 'error.resource.url_path_group', 92 | resource_status: 'error.resource.status', 93 | resource_status_group: 'error.resource.status_group', 94 | resource_method: 'error.resource.method' 95 | }, 96 | fields: { 97 | error_message: ['string', 'error.message'], 98 | error_stack: ['string', 'error.stack'] 99 | } 100 | }, 101 | long_task: { 102 | type: _enums.RumEventType.LONG_TASK, 103 | tags: {}, 104 | fields: { 105 | duration: 'long_task.duration' 106 | } 107 | }, 108 | action: { 109 | type: _enums.RumEventType.ACTION, 110 | tags: { 111 | action_id: 'action.id', 112 | action_name: 'action.target.name', 113 | action_type: 'action.type' 114 | }, 115 | fields: { 116 | duration: 'action.loading_time', 117 | action_error_count: 'action.error.count', 118 | action_resource_count: 'action.resource.count', 119 | action_long_task_count: 'action.long_task.count' 120 | } 121 | }, 122 | app: { 123 | alias_key: 'action', 124 | // metrc 别名, 125 | type: _enums.RumEventType.APP, 126 | tags: { 127 | action_id: 'app.id', 128 | action_name: 'app.name', 129 | action_type: 'app.type' 130 | }, 131 | fields: { 132 | duration: 'app.duration' 133 | } 134 | } 135 | }; 136 | exports.dataMap = dataMap; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/downloadProxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startDownloadProxy = startDownloadProxy; 7 | exports.resetDownloadProxy = resetDownloadProxy; 8 | 9 | var _sdk = require("./sdk"); 10 | 11 | var _utils = require("../helper/utils"); 12 | 13 | var _enums = require("../helper/enums"); 14 | 15 | var downloadProxySingleton; 16 | var beforeSendCallbacks = []; 17 | var onRequestCompleteCallbacks = []; 18 | var originalDownloadRequest; 19 | 20 | function startDownloadProxy() { 21 | if (!downloadProxySingleton) { 22 | proxyDownload(); 23 | downloadProxySingleton = { 24 | beforeSend: function beforeSend(callback) { 25 | beforeSendCallbacks.push(callback); 26 | }, 27 | onRequestComplete: function onRequestComplete(callback) { 28 | onRequestCompleteCallbacks.push(callback); 29 | } 30 | }; 31 | } 32 | 33 | return downloadProxySingleton; 34 | } 35 | 36 | function resetDownloadProxy() { 37 | if (downloadProxySingleton) { 38 | downloadProxySingleton = undefined; 39 | beforeSendCallbacks.splice(0, beforeSendCallbacks.length); 40 | onRequestCompleteCallbacks.splice(0, onRequestCompleteCallbacks.length); 41 | _sdk.sdk.downloadFile = originalDownloadRequest; 42 | } 43 | } 44 | 45 | function proxyDownload() { 46 | originalDownloadRequest = _sdk.sdk.downloadFile; 47 | 48 | _sdk.sdk.downloadFile = function () { 49 | var _this = this; 50 | 51 | var dataflux_xhr = { 52 | method: 'GET', 53 | startTime: 0, 54 | url: arguments[0].url, 55 | type: _enums.RequestType.DOWNLOAD, 56 | responseType: 'file' 57 | }; 58 | dataflux_xhr.startTime = (0, _utils.now)(); 59 | var originalSuccess = arguments[0].success; 60 | 61 | arguments[0].success = function () { 62 | reportXhr(arguments[0]); 63 | 64 | if (originalSuccess) { 65 | originalSuccess.apply(_this, arguments); 66 | } 67 | }; 68 | 69 | var originalFail = arguments[0].fail; 70 | 71 | arguments[0].fail = function () { 72 | reportXhr(arguments[0]); 73 | 74 | if (originalFail) { 75 | originalFail.apply(_this, arguments); 76 | } 77 | }; 78 | 79 | var hasBeenReported = false; 80 | 81 | var reportXhr = function reportXhr(res) { 82 | if (hasBeenReported) { 83 | return; 84 | } 85 | 86 | hasBeenReported = true; 87 | dataflux_xhr.duration = (0, _utils.now)() - dataflux_xhr.startTime; 88 | dataflux_xhr.response = JSON.stringify({ 89 | filePath: res.filePath, 90 | tempFilePath: res.tempFilePath 91 | }); 92 | dataflux_xhr.header = res.header || {}; 93 | dataflux_xhr.profile = res.profile; 94 | dataflux_xhr.status = res.statusCode || res.status || 0; 95 | onRequestCompleteCallbacks.forEach(function (callback) { 96 | callback(dataflux_xhr); 97 | }); 98 | }; 99 | 100 | beforeSendCallbacks.forEach(function (callback) { 101 | callback(dataflux_xhr); 102 | }); 103 | return originalDownloadRequest.apply(this, arguments); 104 | }; 105 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/errorCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startConsoleTracking = startConsoleTracking; 7 | exports.stopConsoleTracking = stopConsoleTracking; 8 | exports.filterErrors = filterErrors; 9 | exports.startRuntimeErrorTracking = startRuntimeErrorTracking; 10 | exports.stopRuntimeErrorTracking = stopRuntimeErrorTracking; 11 | exports.startAutomaticErrorCollection = startAutomaticErrorCollection; 12 | exports.trackNetworkError = trackNetworkError; 13 | 14 | var _utils = require("../helper/utils"); 15 | 16 | var _enums = require("../helper/enums"); 17 | 18 | var _errorTools = require("./errorTools"); 19 | 20 | var _tracekit = require("../helper/tracekit"); 21 | 22 | var _observable = require("./observable"); 23 | 24 | var _configuration = require("./configuration"); 25 | 26 | var _xhrProxy = require("./xhrProxy"); 27 | 28 | var _downloadProxy = require("./downloadProxy"); 29 | 30 | var originalConsoleError; 31 | 32 | function startConsoleTracking(errorObservable) { 33 | originalConsoleError = console.error; 34 | 35 | console.error = function () { 36 | originalConsoleError.apply(console, arguments); 37 | var args = (0, _utils.toArray)(arguments); 38 | var message = []; 39 | args.concat(['console error:']).forEach(function (para) { 40 | message.push(formatConsoleParameters(para)); 41 | }); 42 | errorObservable.notify({ 43 | message: message.join(' '), 44 | source: _errorTools.ErrorSource.CONSOLE, 45 | startTime: (0, _utils.now)() 46 | }); 47 | }; 48 | } 49 | 50 | function stopConsoleTracking() { 51 | console.error = originalConsoleError; 52 | } 53 | 54 | function formatConsoleParameters(param) { 55 | if (typeof param === 'string') { 56 | return param; 57 | } 58 | 59 | if (param instanceof Error) { 60 | return (0, _errorTools.toStackTraceString)((0, _tracekit.computeStackTrace)(param)); 61 | } 62 | 63 | return JSON.stringify(param, undefined, 2); 64 | } 65 | 66 | function filterErrors(configuration, errorObservable) { 67 | var errorCount = 0; 68 | var filteredErrorObservable = new _observable.Observable(); 69 | errorObservable.subscribe(function (error) { 70 | if (errorCount < configuration.maxErrorsByMinute) { 71 | errorCount += 1; 72 | filteredErrorObservable.notify(error); 73 | } else if (errorCount === configuration.maxErrorsByMinute) { 74 | errorCount += 1; 75 | filteredErrorObservable.notify({ 76 | message: 'Reached max number of errors by minute: ' + configuration.maxErrorsByMinute, 77 | source: _errorTools.ErrorSource.AGENT, 78 | startTime: (0, _utils.now)() 79 | }); 80 | } 81 | }); 82 | setInterval(function () { 83 | errorCount = 0; 84 | }, _enums.ONE_MINUTE); 85 | return filteredErrorObservable; 86 | } 87 | 88 | var traceKitReportHandler; 89 | 90 | function startRuntimeErrorTracking(errorObservable) { 91 | traceKitReportHandler = function traceKitReportHandler(stackTrace, _, errorObject) { 92 | var error = (0, _errorTools.formatUnknownError)(stackTrace, errorObject, 'Uncaught'); 93 | errorObservable.notify({ 94 | message: error.message, 95 | stack: error.stack, 96 | type: error.type, 97 | source: _errorTools.ErrorSource.SOURCE, 98 | startTime: (0, _utils.now)() 99 | }); 100 | }; 101 | 102 | _tracekit.report.subscribe(traceKitReportHandler); 103 | } 104 | 105 | function stopRuntimeErrorTracking() { 106 | _tracekit.report.unsubscribe(traceKitReportHandler); 107 | } 108 | 109 | var filteredErrorsObservable; 110 | 111 | function startAutomaticErrorCollection(configuration) { 112 | if (!filteredErrorsObservable) { 113 | var errorObservable = new _observable.Observable(); 114 | trackNetworkError(configuration, errorObservable); 115 | startConsoleTracking(errorObservable); 116 | startRuntimeErrorTracking(errorObservable); 117 | filteredErrorsObservable = filterErrors(configuration, errorObservable); 118 | } 119 | 120 | return filteredErrorsObservable; 121 | } 122 | 123 | function trackNetworkError(configuration, errorObservable) { 124 | (0, _xhrProxy.startXhrProxy)().onRequestComplete(function (context) { 125 | return handleCompleteRequest(context.type, context); 126 | }); 127 | (0, _downloadProxy.startDownloadProxy)().onRequestComplete(function (context) { 128 | return handleCompleteRequest(context.type, context); 129 | }); 130 | 131 | function handleCompleteRequest(type, request) { 132 | if (!(0, _configuration.isIntakeRequest)(request.url, configuration) && (isRejected(request) || isServerError(request))) { 133 | errorObservable.notify({ 134 | message: format(type) + 'error' + request.method + ' ' + request.url, 135 | resource: { 136 | method: request.method, 137 | statusCode: request.status, 138 | url: request.url 139 | }, 140 | type: _errorTools.ErrorSource.NETWORK, 141 | source: _errorTools.ErrorSource.NETWORK, 142 | stack: truncateResponse(request.response, configuration) || 'Failed to load', 143 | startTime: request.startTime 144 | }); 145 | } 146 | } 147 | 148 | return { 149 | stop: function stop() { 150 | (0, _xhrProxy.resetXhrProxy)(); 151 | (0, _downloadProxy.resetDownloadProxy)(); 152 | } 153 | }; 154 | } 155 | 156 | function isRejected(request) { 157 | return request.status === 0 && request.responseType !== 'opaque'; 158 | } 159 | 160 | function isServerError(request) { 161 | return request.status >= 500; 162 | } 163 | 164 | function truncateResponse(response, configuration) { 165 | if (response && response.length > configuration.requestErrorResponseLengthLimit) { 166 | return response.substring(0, configuration.requestErrorResponseLengthLimit) + '...'; 167 | } 168 | 169 | return response; 170 | } 171 | 172 | function format(type) { 173 | if (_enums.RequestType.XHR === type) { 174 | return 'XHR'; 175 | } 176 | 177 | return _enums.RequestType.DOWNLOAD; 178 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/errorTools.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.formatUnknownError = formatUnknownError; 7 | exports.toStackTraceString = toStackTraceString; 8 | exports.ErrorSource = void 0; 9 | var ErrorSource = { 10 | AGENT: 'agent', 11 | CONSOLE: 'console', 12 | NETWORK: 'network', 13 | SOURCE: 'source', 14 | LOGGER: 'logger' 15 | }; 16 | exports.ErrorSource = ErrorSource; 17 | 18 | function formatUnknownError(stackTrace, errorObject, nonErrorPrefix) { 19 | if (!stackTrace || stackTrace.message === undefined && !(errorObject instanceof Error)) { 20 | return { 21 | message: nonErrorPrefix + '' + JSON.stringify(errorObject), 22 | stack: 'No stack, consider using an instance of Error', 23 | type: stackTrace && stackTrace.name 24 | }; 25 | } 26 | 27 | return { 28 | message: stackTrace.message || 'Empty message', 29 | stack: toStackTraceString(stackTrace), 30 | type: stackTrace.name 31 | }; 32 | } 33 | 34 | function toStackTraceString(stack) { 35 | var result = stack.name || 'Error' + ': ' + stack.message; 36 | stack.stack.forEach(function (frame) { 37 | var func = frame.func === '?' ? '' : frame.func; 38 | var args = frame.args && frame.args.length > 0 ? '(' + frame.args.join(', ') + ')' : ''; 39 | var line = frame.line ? ':' + frame.line : ''; 40 | var column = frame.line && frame.column ? ':' + frame.column : ''; 41 | result += '\n at ' + func + args + ' @ ' + frame.url + line + column; 42 | }); 43 | return result; 44 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/lifeCycle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.LifeCycleEventType = exports.LifeCycle = void 0; 7 | 8 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 9 | 10 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 11 | 12 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 13 | 14 | var LifeCycle = /*#__PURE__*/function () { 15 | function LifeCycle() { 16 | _classCallCheck(this, LifeCycle); 17 | 18 | this.callbacks = {}; 19 | } 20 | 21 | _createClass(LifeCycle, [{ 22 | key: "notify", 23 | value: function notify(eventType, data) { 24 | var eventCallbacks = this.callbacks[eventType]; 25 | 26 | if (eventCallbacks) { 27 | eventCallbacks.forEach(function (callback) { 28 | return callback(data); 29 | }); 30 | } 31 | } 32 | }, { 33 | key: "subscribe", 34 | value: function subscribe(eventType, callback) { 35 | var _this = this; 36 | 37 | if (!this.callbacks[eventType]) { 38 | this.callbacks[eventType] = []; 39 | } 40 | 41 | this.callbacks[eventType].push(callback); 42 | return { 43 | unsubscribe: function unsubscribe() { 44 | _this.callbacks[eventType] = _this.callbacks[eventType].filter(function (other) { 45 | return callback !== other; 46 | }); 47 | } 48 | }; 49 | } 50 | }]); 51 | 52 | return LifeCycle; 53 | }(); 54 | 55 | exports.LifeCycle = LifeCycle; 56 | var LifeCycleEventType = { 57 | PERFORMANCE_ENTRY_COLLECTED: 'PERFORMANCE_ENTRY_COLLECTED', 58 | AUTO_ACTION_CREATED: 'AUTO_ACTION_CREATED', 59 | AUTO_ACTION_COMPLETED: 'AUTO_ACTION_COMPLETED', 60 | AUTO_ACTION_DISCARDED: 'AUTO_ACTION_DISCARDED', 61 | APP_HIDE: 'APP_HIDE', 62 | APP_UPDATE: 'APP_UPDATE', 63 | PAGE_SET_DATA_UPDATE: 'PAGE_SET_DATA_UPDATE', 64 | PAGE_ALIAS_ACTION: 'PAGE_ALIAS_ACTION', 65 | VIEW_CREATED: 'VIEW_CREATED', 66 | VIEW_UPDATED: 'VIEW_UPDATED', 67 | VIEW_ENDED: 'VIEW_ENDED', 68 | REQUEST_STARTED: 'REQUEST_STARTED', 69 | REQUEST_COMPLETED: 'REQUEST_COMPLETED', 70 | RAW_RUM_EVENT_COLLECTED: 'RAW_RUM_EVENT_COLLECTED', 71 | RUM_EVENT_COLLECTED: 'RUM_EVENT_COLLECTED' 72 | }; 73 | exports.LifeCycleEventType = LifeCycleEventType; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/observable.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Observable = void 0; 7 | 8 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 9 | 10 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 11 | 12 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 13 | 14 | var Observable = /*#__PURE__*/function () { 15 | function Observable() { 16 | _classCallCheck(this, Observable); 17 | 18 | this.observers = []; 19 | } 20 | 21 | _createClass(Observable, [{ 22 | key: "subscribe", 23 | value: function subscribe(f) { 24 | this.observers.push(f); 25 | } 26 | }, { 27 | key: "notify", 28 | value: function notify(data) { 29 | this.observers.forEach(function (observer) { 30 | observer(data); 31 | }); 32 | } 33 | }]); 34 | 35 | return Observable; 36 | }(); 37 | 38 | exports.Observable = Observable; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/sdk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.tracker = exports.sdk = void 0; 7 | 8 | var _utils = require("../helper/utils"); 9 | 10 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 11 | 12 | function getSDK() { 13 | var sdk = null, 14 | tracker = ''; 15 | 16 | try { 17 | if (wx && (typeof wx === "undefined" ? "undefined" : _typeof(wx)) === 'object' && typeof wx.request === 'function') { 18 | sdk = (0, _utils.deepMixObject)({}, wx); 19 | tracker = 'wx'; 20 | wx = sdk; 21 | } 22 | } catch (err) { 23 | console.warn('unsupport platform, Fail to start'); 24 | } 25 | 26 | console.log('------get SDK-------'); 27 | return { 28 | sdk: sdk, 29 | tracker: tracker 30 | }; 31 | } 32 | 33 | var instance = getSDK(); 34 | var sdk = instance.sdk; 35 | exports.sdk = sdk; 36 | var tracker = instance.tracker; 37 | exports.tracker = tracker; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/core/xhrProxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startXhrProxy = startXhrProxy; 7 | exports.resetXhrProxy = resetXhrProxy; 8 | 9 | var _sdk = require("./sdk"); 10 | 11 | var _utils = require("../helper/utils"); 12 | 13 | var _enums = require("../helper/enums"); 14 | 15 | var xhrProxySingleton; 16 | var beforeSendCallbacks = []; 17 | var onRequestCompleteCallbacks = []; 18 | var originalXhrRequest; 19 | 20 | function startXhrProxy() { 21 | if (!xhrProxySingleton) { 22 | proxyXhr(); 23 | xhrProxySingleton = { 24 | beforeSend: function beforeSend(callback) { 25 | beforeSendCallbacks.push(callback); 26 | }, 27 | onRequestComplete: function onRequestComplete(callback) { 28 | onRequestCompleteCallbacks.push(callback); 29 | } 30 | }; 31 | } 32 | 33 | return xhrProxySingleton; 34 | } 35 | 36 | function resetXhrProxy() { 37 | if (xhrProxySingleton) { 38 | xhrProxySingleton = undefined; 39 | beforeSendCallbacks.splice(0, beforeSendCallbacks.length); 40 | onRequestCompleteCallbacks.splice(0, onRequestCompleteCallbacks.length); 41 | _sdk.sdk.request = originalXhrRequest; 42 | } 43 | } 44 | 45 | function proxyXhr() { 46 | originalXhrRequest = _sdk.sdk.request; 47 | 48 | _sdk.sdk.request = function () { 49 | var _this = this; 50 | 51 | var dataflux_xhr = { 52 | method: arguments[0].method || 'GET', 53 | startTime: 0, 54 | url: arguments[0].url, 55 | type: _enums.RequestType.XHR, 56 | responseType: arguments[0].responseType || 'text' 57 | }; 58 | dataflux_xhr.startTime = (0, _utils.now)(); 59 | var originalSuccess = arguments[0].success; 60 | 61 | arguments[0].success = function () { 62 | reportXhr(arguments[0]); 63 | 64 | if (originalSuccess) { 65 | originalSuccess.apply(_this, arguments); 66 | } 67 | }; 68 | 69 | var originalFail = arguments[0].fail; 70 | 71 | arguments[0].fail = function () { 72 | reportXhr(arguments[0]); 73 | 74 | if (originalFail) { 75 | originalFail.apply(_this, arguments); 76 | } 77 | }; 78 | 79 | var hasBeenReported = false; 80 | 81 | var reportXhr = function reportXhr(res) { 82 | if (hasBeenReported) { 83 | return; 84 | } 85 | 86 | hasBeenReported = true; 87 | dataflux_xhr.duration = (0, _utils.now)() - dataflux_xhr.startTime; 88 | dataflux_xhr.response = JSON.stringify(res.data); 89 | dataflux_xhr.header = res.header || {}; 90 | dataflux_xhr.profile = res.profile; 91 | dataflux_xhr.status = res.statusCode || res.status || 0; 92 | onRequestCompleteCallbacks.forEach(function (callback) { 93 | callback(dataflux_xhr); 94 | }); 95 | }; 96 | 97 | beforeSendCallbacks.forEach(function (callback) { 98 | callback(dataflux_xhr); 99 | }); 100 | return originalXhrRequest.apply(this, arguments); 101 | }; 102 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/helper/enums.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.MpHook = exports.ActionType = exports.RequestType = exports.RumEventType = exports.CLIENT_ID_TOKEN = exports.ONE_KILO_BYTE = exports.ONE_HOUR = exports.ONE_MINUTE = exports.ONE_SECOND = void 0; 7 | 8 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 9 | 10 | var ONE_SECOND = 1000; 11 | exports.ONE_SECOND = ONE_SECOND; 12 | var ONE_MINUTE = 60 * ONE_SECOND; 13 | exports.ONE_MINUTE = ONE_MINUTE; 14 | var ONE_HOUR = 60 * ONE_MINUTE; 15 | exports.ONE_HOUR = ONE_HOUR; 16 | var ONE_KILO_BYTE = 1024; 17 | exports.ONE_KILO_BYTE = ONE_KILO_BYTE; 18 | var CLIENT_ID_TOKEN = 'datafluxRum:client:id'; 19 | exports.CLIENT_ID_TOKEN = CLIENT_ID_TOKEN; 20 | 21 | var RumEventType = _defineProperty({ 22 | ACTION: 'action', 23 | ERROR: 'error', 24 | LONG_TASK: 'long_task', 25 | VIEW: 'view', 26 | RESOURCE: 'resource', 27 | APP: 'app' 28 | }, "ACTION", 'action'); 29 | 30 | exports.RumEventType = RumEventType; 31 | var RequestType = { 32 | XHR: 'network', 33 | DOWNLOAD: 'resource' 34 | }; 35 | exports.RequestType = RequestType; 36 | var ActionType = { 37 | tap: 'tap', 38 | longpress: 'longpress', 39 | longtap: 'longtap' 40 | }; 41 | exports.ActionType = ActionType; 42 | var MpHook = { 43 | data: 1, 44 | onLoad: 1, 45 | onShow: 1, 46 | onReady: 1, 47 | onPullDownRefresh: 1, 48 | onReachBottom: 1, 49 | onShareAppMessage: 1, 50 | onPageScroll: 1, 51 | onResize: 1, 52 | onTabItemTap: 1, 53 | onHide: 1, 54 | onUnload: 1 55 | }; 56 | exports.MpHook = MpHook; -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "datafluxRum", { 7 | enumerable: true, 8 | get: function get() { 9 | return _rum.datafluxRum; 10 | } 11 | }); 12 | 13 | var _rum = require("./boot/rum.entry"); -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/app/appCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startAppCollection = startAppCollection; 7 | 8 | var _index = require("./index"); 9 | 10 | var _lifeCycle = require("../../core/lifeCycle"); 11 | 12 | var _enums = require("../../helper/enums"); 13 | 14 | var _utils = require("../../helper/utils"); 15 | 16 | function startAppCollection(lifeCycle, configuration) { 17 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.APP_UPDATE, function (appinfo) { 18 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processAppUpdate(appinfo)); 19 | }); 20 | return (0, _index.rewriteApp)(configuration, lifeCycle); 21 | } 22 | 23 | function processAppUpdate(appinfo) { 24 | var appEvent = { 25 | date: appinfo.startTime, 26 | type: _enums.RumEventType.APP, 27 | app: { 28 | type: appinfo.type, 29 | name: appinfo.name, 30 | id: appinfo.id, 31 | duration: (0, _utils.msToNs)(appinfo.duration) 32 | } 33 | }; 34 | console.log(appEvent, 'appEvent===='); 35 | return { 36 | rawRumEvent: appEvent, 37 | startTime: appinfo.startTime 38 | }; 39 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/app/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.rewriteApp = rewriteApp; 7 | exports.startupTypes = exports.THROTTLE_VIEW_UPDATE_PERIOD = void 0; 8 | 9 | var _utils = require("../../helper/utils"); 10 | 11 | var _lifeCycle = require("../../core/lifeCycle"); 12 | 13 | // 劫持原小程序App方法 14 | var THROTTLE_VIEW_UPDATE_PERIOD = 3000; 15 | exports.THROTTLE_VIEW_UPDATE_PERIOD = THROTTLE_VIEW_UPDATE_PERIOD; 16 | var startupTypes = { 17 | COLD: 'cold', 18 | HOT: 'hot' 19 | }; 20 | exports.startupTypes = startupTypes; 21 | 22 | function rewriteApp(configuration, lifeCycle) { 23 | var originApp = App; 24 | var appInfo = { 25 | isStartUp: false // 是否启动 26 | 27 | }; 28 | var startTime; 29 | 30 | App = function App(app) { 31 | startTime = (0, _utils.now)() // 合并方法,插入记录脚本 32 | ; 33 | ['onLaunch', 'onShow', 'onHide'].forEach(function (methodName) { 34 | var userDefinedMethod = app[methodName]; // 暂存用户定义的方法 35 | 36 | app[methodName] = function (options) { 37 | console.log(methodName, 'methodName app'); 38 | 39 | if (methodName === 'onLaunch') { 40 | appInfo.isStartUp = true; 41 | appInfo.isHide = false; 42 | appInfo.startupType = startupTypes.COLD; 43 | } else if (methodName === 'onShow') { 44 | if (appInfo.isStartUp && appInfo.isHide) { 45 | // 判断是热启动 46 | appInfo.startupType = startupTypes.HOT; // appUpdate() 47 | } 48 | } else if (methodName === 'onHide') { 49 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.APP_HIDE); 50 | appInfo.isHide = true; 51 | } 52 | 53 | return userDefinedMethod && userDefinedMethod.call(this, options); 54 | }; 55 | }); 56 | return originApp(app); 57 | }; 58 | 59 | startPerformanceObservable(lifeCycle); 60 | } 61 | 62 | function startPerformanceObservable(lifeCycle) { 63 | var subscribe = lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, function (entitys) { 64 | // 过滤掉其他页面监听,只保留首次启动 65 | var codeDownloadDuration; 66 | var launchEntity = entitys.find(function (entity) { 67 | return entity.entryType === 'navigation' && entity.navigationType === 'appLaunch'; 68 | }); 69 | 70 | if (typeof launchEntity !== 'undefined') { 71 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.APP_UPDATE, { 72 | startTime: launchEntity.startTime, 73 | name: '启动', 74 | type: 'launch', 75 | id: (0, _utils.UUID)(), 76 | duration: launchEntity.duration 77 | }); 78 | } 79 | 80 | var scriptentity = entitys.find(function (entity) { 81 | return entity.entryType === 'script' && entity.name === 'evaluateScript'; 82 | }); 83 | 84 | if (typeof scriptentity !== 'undefined') { 85 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.APP_UPDATE, { 86 | startTime: scriptentity.startTime, 87 | name: '脚本注入', 88 | type: 'script_insert', 89 | id: (0, _utils.UUID)(), 90 | duration: scriptentity.duration 91 | }); 92 | } 93 | 94 | var firstEntity = entitys.find(function (entity) { 95 | return entity.entryType === 'render' && entity.name === 'firstRender'; 96 | }); 97 | 98 | if (firstEntity && scriptentity && launchEntity) { 99 | if (!(0, _utils.areInOrder)(firstEntity.duration, launchEntity.duration) || !(0, _utils.areInOrder)(scriptentity.duration, launchEntity.duration)) { 100 | return; 101 | } 102 | 103 | codeDownloadDuration = launchEntity.duration - firstEntity.duration - scriptentity.duration; // 资源下载耗时 104 | 105 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.APP_UPDATE, { 106 | startTime: launchEntity.startTime, 107 | name: '小程序包下载', 108 | type: 'package_download', 109 | id: (0, _utils.UUID)(), 110 | duration: codeDownloadDuration 111 | }); // 资源下载时间暂时定为:首次启动时间-脚本加载时间-初次渲染时间 112 | } 113 | }); 114 | return { 115 | stop: subscribe.unsubscribe 116 | }; 117 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/assembly.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startRumAssembly = startRumAssembly; 7 | 8 | var _utils = require("../helper/utils"); 9 | 10 | var _lifeCycle = require("../core/lifeCycle"); 11 | 12 | var _enums = require("../helper/enums"); 13 | 14 | var _baseInfo = _interopRequireDefault(require("../core/baseInfo")); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 17 | 18 | function isTracked(configuration) { 19 | return (0, _utils.performDraw)(configuration.sampleRate); 20 | } 21 | 22 | var SessionType = { 23 | SYNTHETICS: 'synthetics', 24 | USER: 'user' 25 | }; 26 | 27 | function startRumAssembly(applicationId, configuration, lifeCycle, parentContexts) { 28 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, function (data) { 29 | var startTime = data.startTime; 30 | var rawRumEvent = data.rawRumEvent; 31 | var viewContext = parentContexts.findView(startTime); 32 | var deviceContext = { 33 | device: _baseInfo["default"].deviceInfo 34 | }; 35 | 36 | if (isTracked(configuration) && (viewContext || rawRumEvent.type === _enums.RumEventType.APP)) { 37 | var actionContext = parentContexts.findAction(startTime); 38 | var rumContext = { 39 | _dd: { 40 | sdkName: configuration.sdkName, 41 | sdkVersion: configuration.sdkVersion, 42 | env: configuration.env, 43 | version: configuration.version 44 | }, 45 | tags: configuration.tags, 46 | application: { 47 | id: applicationId 48 | }, 49 | device: {}, 50 | date: new Date().getTime(), 51 | session: { 52 | id: _baseInfo["default"].getSessionId(), 53 | type: SessionType.USER 54 | }, 55 | user: { 56 | user_id: configuration.user_id || _baseInfo["default"].getClientID(), 57 | is_signin: configuration.user_id ? 'T' : 'F' 58 | } 59 | }; 60 | var rumEvent = (0, _utils.extend2Lev)(rumContext, deviceContext, viewContext, actionContext, rawRumEvent); 61 | var serverRumEvent = (0, _utils.withSnakeCaseKeys)(rumEvent); // if ( 62 | // serverRumEvent.type === 'view' || 63 | // serverRumEvent.type === 'action' 64 | // ) { 65 | // console.log(serverRumEvent, 'serverRumEvent') 66 | // } 67 | 68 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.RUM_EVENT_COLLECTED, serverRumEvent); 69 | } 70 | }); 71 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/error/errorCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startErrorCollection = startErrorCollection; 7 | exports.doStartErrorCollection = doStartErrorCollection; 8 | 9 | var _errorCollection = require("../../core/errorCollection"); 10 | 11 | var _enums = require("../../helper/enums"); 12 | 13 | var _lifeCycle = require("../../core/lifeCycle"); 14 | 15 | var _utils = require("../../helper/utils"); 16 | 17 | function startErrorCollection(lifeCycle, configuration) { 18 | return doStartErrorCollection(lifeCycle, configuration, (0, _errorCollection.startAutomaticErrorCollection)(configuration)); 19 | } 20 | 21 | function doStartErrorCollection(lifeCycle, configuration, observable) { 22 | observable.subscribe(function (error) { 23 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processError(error)); 24 | }); 25 | } 26 | 27 | function processError(error) { 28 | var resource = error.resource; 29 | 30 | if (resource) { 31 | var urlObj = (0, _utils.urlParse)(error.resource.url).getParse(); 32 | resource = { 33 | method: error.resource.method, 34 | status: error.resource.statusCode, 35 | statusGroup: (0, _utils.getStatusGroup)(error.resource.statusCode), 36 | url: error.resource.url, 37 | urlHost: urlObj.Host, 38 | urlPath: urlObj.Path, 39 | urlPathGroup: (0, _utils.replaceNumberCharByPath)(urlObj.Path) 40 | }; 41 | } 42 | 43 | var rawRumEvent = { 44 | date: error.startTime, 45 | error: { 46 | message: error.message, 47 | resource: resource, 48 | source: error.source, 49 | stack: error.stack, 50 | type: error.type, 51 | starttime: error.startTime 52 | }, 53 | type: _enums.RumEventType.ERROR 54 | }; 55 | return { 56 | rawRumEvent: rawRumEvent, 57 | startTime: error.startTime 58 | }; 59 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/page/viewCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startViewCollection = startViewCollection; 7 | 8 | var _index = require("./index"); 9 | 10 | var _enums = require("../../helper/enums"); 11 | 12 | var _utils = require("../../helper/utils"); 13 | 14 | var _lifeCycle = require("../../core/lifeCycle"); 15 | 16 | function startViewCollection(lifeCycle, configuration) { 17 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.VIEW_UPDATED, function (view) { 18 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processViewUpdate(view)); 19 | }); 20 | return (0, _index.rewritePage)(configuration, lifeCycle); 21 | } 22 | 23 | function processViewUpdate(view) { 24 | var apdexLevel; 25 | 26 | if (view.fmp) { 27 | apdexLevel = parseInt(Number(view.fmp) / 1000); 28 | apdexLevel = apdexLevel > 9 ? 9 : apdexLevel; 29 | } 30 | 31 | var viewEvent = { 32 | _dd: { 33 | documentVersion: view.documentVersion 34 | }, 35 | date: view.startTime, 36 | type: _enums.RumEventType.VIEW, 37 | page: { 38 | action: { 39 | count: view.eventCounts.userActionCount 40 | }, 41 | error: { 42 | count: view.eventCounts.errorCount 43 | }, 44 | setdata: { 45 | count: view.setdataCount 46 | }, 47 | setdata_duration: (0, _utils.msToNs)(view.setdataDuration), 48 | loadingTime: (0, _utils.msToNs)(view.loadingTime), 49 | stayTime: (0, _utils.msToNs)(view.stayTime), 50 | onload2onshow: (0, _utils.msToNs)(view.onload2onshowTime), 51 | onshow2onready: (0, _utils.msToNs)(view.onshow2onready), 52 | fpt: (0, _utils.msToNs)(view.fpt), 53 | fmp: (0, _utils.msToNs)(view.fmp), 54 | isActive: view.isActive, 55 | apdexLevel: apdexLevel, 56 | // longTask: { 57 | // count: view.eventCounts.longTaskCount 58 | // }, 59 | resource: { 60 | count: view.eventCounts.resourceCount 61 | }, 62 | timeSpent: (0, _utils.msToNs)(view.duration) 63 | } 64 | }; 65 | return { 66 | rawRumEvent: viewEvent, 67 | startTime: view.startTime 68 | }; 69 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/parentContexts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startParentContexts = startParentContexts; 7 | exports.CLEAR_OLD_CONTEXTS_INTERVAL = exports.VIEW_CONTEXT_TIME_OUT_DELAY = void 0; 8 | 9 | var _enums = require("../helper/enums"); 10 | 11 | var _utils = require("../helper/utils"); 12 | 13 | var _lifeCycle = require("../core/lifeCycle"); 14 | 15 | var VIEW_CONTEXT_TIME_OUT_DELAY = 4 * _enums.ONE_HOUR; 16 | exports.VIEW_CONTEXT_TIME_OUT_DELAY = VIEW_CONTEXT_TIME_OUT_DELAY; 17 | var CLEAR_OLD_CONTEXTS_INTERVAL = _enums.ONE_MINUTE; 18 | exports.CLEAR_OLD_CONTEXTS_INTERVAL = CLEAR_OLD_CONTEXTS_INTERVAL; 19 | 20 | function startParentContexts(lifeCycle) { 21 | var currentView; 22 | var currentAction; 23 | var previousViews = []; 24 | var previousActions = []; 25 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.VIEW_CREATED, function (currentContext) { 26 | currentView = currentContext; 27 | }); 28 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.VIEW_UPDATED, function (currentContext) { 29 | // A view can be updated after its end. We have to ensure that the view being updated is the 30 | // most recently created. 31 | if (currentView && currentView.id === currentContext.id) { 32 | currentView = currentContext; 33 | } 34 | }); 35 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.VIEW_ENDED, function (data) { 36 | if (currentView) { 37 | previousViews.unshift({ 38 | endTime: data.endClocks, 39 | context: buildCurrentViewContext(), 40 | startTime: currentView.startTime 41 | }); 42 | currentView = undefined; 43 | } 44 | }); 45 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.AUTO_ACTION_CREATED, function (currentContext) { 46 | currentAction = currentContext; 47 | }); 48 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.AUTO_ACTION_COMPLETED, function (action) { 49 | if (currentAction) { 50 | previousActions.unshift({ 51 | context: buildCurrentActionContext(), 52 | endTime: currentAction.startClocks + action.duration, 53 | startTime: currentAction.startClocks 54 | }); 55 | } 56 | 57 | currentAction = undefined; 58 | }); 59 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.AUTO_ACTION_DISCARDED, function () { 60 | currentAction = undefined; 61 | }); 62 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.SESSION_RENEWED, function () { 63 | previousViews = []; 64 | previousActions = []; 65 | currentView = undefined; 66 | currentAction = undefined; 67 | }); 68 | var clearOldContextsInterval = setInterval(function () { 69 | clearOldContexts(previousViews, VIEW_CONTEXT_TIME_OUT_DELAY); 70 | }, CLEAR_OLD_CONTEXTS_INTERVAL); 71 | 72 | function clearOldContexts(previousContexts, timeOutDelay) { 73 | var oldTimeThreshold = (0, _utils.now)() - timeOutDelay; 74 | 75 | while (previousContexts.length > 0 && previousContexts[previousContexts.length - 1].startTime < oldTimeThreshold) { 76 | previousContexts.pop(); 77 | } 78 | } 79 | 80 | function buildCurrentActionContext() { 81 | return { 82 | userAction: { 83 | id: currentAction.id 84 | } 85 | }; 86 | } 87 | 88 | function buildCurrentViewContext() { 89 | return { 90 | page: { 91 | id: currentView.id, 92 | referer: previousViews.length && previousViews[previousViews.length - 1].context.page.route || undefined, 93 | route: currentView.route 94 | } 95 | }; 96 | } 97 | 98 | function findContext(buildContext, previousContexts, currentContext, startTime) { 99 | if (startTime === undefined) { 100 | return currentContext ? buildContext() : undefined; 101 | } 102 | 103 | if (currentContext && startTime >= currentContext.startTime) { 104 | return buildContext(); 105 | } 106 | 107 | var flag = undefined; 108 | (0, _utils.each)(previousContexts, function (previousContext) { 109 | if (startTime > previousContext.endTime) { 110 | return false; 111 | } 112 | 113 | if (startTime >= previousContext.startTime) { 114 | flag = previousContext.context; 115 | return false; 116 | } 117 | }); 118 | return flag; 119 | } 120 | 121 | var parentContexts = { 122 | findView: function findView(startTime) { 123 | return findContext(buildCurrentViewContext, previousViews, currentView, startTime); 124 | }, 125 | findAction: function findAction(startTime) { 126 | return findContext(buildCurrentActionContext, previousActions, currentAction, startTime); 127 | }, 128 | stop: function stop() { 129 | clearInterval(clearOldContextsInterval); 130 | } 131 | }; 132 | return parentContexts; 133 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/performanceCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startPagePerformanceObservable = startPagePerformanceObservable; 7 | 8 | var _lifeCycle = require("../core/lifeCycle"); 9 | 10 | var _sdk = require("../core/sdk"); 11 | 12 | function startPagePerformanceObservable(lifeCycle, configuration) { 13 | if (!!_sdk.sdk.getPerformance) { 14 | var performance = _sdk.sdk.getPerformance(); 15 | 16 | var observer = performance.createObserver(function (entryList) { 17 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, entryList.getEntries()); 18 | }); 19 | observer.observe({ 20 | entryTypes: ['render', 'script', 'navigation'] 21 | }); 22 | } 23 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/requestCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startRequestCollection = startRequestCollection; 7 | exports.trackXhr = trackXhr; 8 | exports.trackDownload = trackDownload; 9 | 10 | var _xhrProxy = require("../core/xhrProxy"); 11 | 12 | var _downloadProxy = require("../core/downloadProxy"); 13 | 14 | var _lifeCycle = require("../core/lifeCycle"); 15 | 16 | var _utils = require("../helper/utils"); 17 | 18 | var _resourceUtils = require("../rumEventsCollection/resource/resourceUtils"); 19 | 20 | var nextRequestIndex = 1; 21 | 22 | function startRequestCollection(lifeCycle, configuration) { 23 | trackXhr(lifeCycle, configuration); 24 | trackDownload(lifeCycle, configuration); 25 | } 26 | 27 | function parseHeader(header) { 28 | // 大小写兼容 29 | if (!(0, _utils.isObject)(header)) return header; 30 | var res = {}; 31 | Object.keys(header).forEach(function (key) { 32 | res[key.toLowerCase()] = header[key]; 33 | }); 34 | return res; 35 | } 36 | 37 | function getHeaderString(header) { 38 | if (!(0, _utils.isObject)(header)) return header; 39 | var headerStr = ''; 40 | Object.keys(header).forEach(function (key) { 41 | headerStr += key + ':' + header[key] + ';'; 42 | }); 43 | return headerStr; 44 | } 45 | 46 | function trackXhr(lifeCycle, configuration) { 47 | var xhrProxy = (0, _xhrProxy.startXhrProxy)(); 48 | xhrProxy.beforeSend(function (context) { 49 | if ((0, _resourceUtils.isAllowedRequestUrl)(configuration, context.url)) { 50 | context.requestIndex = getNextRequestIndex(); 51 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.REQUEST_STARTED, { 52 | requestIndex: context.requestIndex 53 | }); 54 | } 55 | }); 56 | xhrProxy.onRequestComplete(function (context) { 57 | if ((0, _resourceUtils.isAllowedRequestUrl)(configuration, context.url)) { 58 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.REQUEST_COMPLETED, { 59 | duration: context.duration, 60 | method: context.method, 61 | requestIndex: context.requestIndex, 62 | performance: context.profile, 63 | response: context.response, 64 | startTime: context.startTime, 65 | status: context.status, 66 | type: context.type, 67 | url: context.url 68 | }); 69 | } 70 | }); 71 | return xhrProxy; 72 | } 73 | 74 | function trackDownload(lifeCycle, configuration) { 75 | var dwonloadProxy = (0, _downloadProxy.startDownloadProxy)(); 76 | dwonloadProxy.beforeSend(function (context) { 77 | if ((0, _resourceUtils.isAllowedRequestUrl)(configuration, context.url)) { 78 | context.requestIndex = getNextRequestIndex(); 79 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.REQUEST_STARTED, { 80 | requestIndex: context.requestIndex 81 | }); 82 | } 83 | }); 84 | dwonloadProxy.onRequestComplete(function (context) { 85 | if ((0, _resourceUtils.isAllowedRequestUrl)(configuration, context.url)) { 86 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.REQUEST_COMPLETED, { 87 | duration: context.duration, 88 | method: context.method, 89 | requestIndex: context.requestIndex, 90 | performance: context.profile, 91 | response: context.response, 92 | startTime: context.startTime, 93 | status: context.status, 94 | type: context.type, 95 | url: context.url 96 | }); 97 | } 98 | }); 99 | return dwonloadProxy; 100 | } 101 | 102 | function getNextRequestIndex() { 103 | var result = nextRequestIndex; 104 | nextRequestIndex += 1; 105 | return result; 106 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/resource/resourceCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startResourceCollection = startResourceCollection; 7 | 8 | var _resourceUtils = require("./resourceUtils"); 9 | 10 | var _lifeCycle = require("../../core/lifeCycle"); 11 | 12 | var _utils = require("../../helper/utils"); 13 | 14 | var _enums = require("../../helper/enums"); 15 | 16 | function startResourceCollection(lifeCycle, configuration) { 17 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.REQUEST_COMPLETED, function (request) { 18 | lifeCycle.notify(_lifeCycle.LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processRequest(request)); 19 | }); 20 | } 21 | 22 | function processRequest(request) { 23 | var type = request.type; 24 | var timing = request.performance; 25 | var correspondingTimingOverrides = timing ? computePerformanceEntryMetrics(timing) : undefined; 26 | var urlObj = (0, _utils.urlParse)(request.url).getParse(); 27 | var startTime = request.startTime; 28 | var resourceEvent = (0, _utils.extend2Lev)({ 29 | date: startTime, 30 | resource: { 31 | type: type, 32 | duration: (0, _utils.msToNs)(request.duration), 33 | method: request.method, 34 | status: request.status, 35 | statusGroup: (0, _utils.getStatusGroup)(request.status), 36 | url: request.url, 37 | urlHost: urlObj.Host, 38 | urlPath: urlObj.Path, 39 | urlPathGroup: (0, _utils.replaceNumberCharByPath)(urlObj.Path), 40 | urlQuery: (0, _utils.jsonStringify)((0, _utils.getQueryParamsFromUrl)(request.url)) 41 | }, 42 | type: _enums.RumEventType.RESOURCE 43 | }, correspondingTimingOverrides); 44 | return { 45 | startTime: startTime, 46 | rawRumEvent: resourceEvent 47 | }; 48 | } 49 | 50 | function computePerformanceEntryMetrics(timing) { 51 | return { 52 | resource: (0, _utils.extend2Lev)({}, { 53 | load: (0, _resourceUtils.computePerformanceResourceDuration)(timing), 54 | size: (0, _resourceUtils.computeSize)(timing) 55 | }, (0, _resourceUtils.computePerformanceResourceDetails)(timing)) 56 | }; 57 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/trackEventCounts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.trackEventCounts = trackEventCounts; 7 | 8 | var _utils = require("../helper/utils"); 9 | 10 | var _enums = require("../helper/enums"); 11 | 12 | var _lifeCycle = require("../core/lifeCycle"); 13 | 14 | function trackEventCounts(lifeCycle, callback) { 15 | if (typeof callback === 'undefined') { 16 | callback = _utils.noop; 17 | } 18 | 19 | var eventCounts = { 20 | errorCount: 0, 21 | resourceCount: 0, 22 | longTaskCount: 0, 23 | userActionCount: 0 24 | }; 25 | var subscription = lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, function (data) { 26 | var rawRumEvent = data.rawRumEvent; 27 | 28 | switch (rawRumEvent.type) { 29 | case _enums.RumEventType.ERROR: 30 | eventCounts.errorCount += 1; 31 | callback(eventCounts); 32 | break; 33 | 34 | case _enums.RumEventType.RESOURCE: 35 | eventCounts.resourceCount += 1; 36 | callback(eventCounts); 37 | break; 38 | 39 | case _enums.RumEventType.ACTION: 40 | eventCounts.userActionCount += 1; 41 | callback(eventCounts); 42 | break; 43 | } 44 | }); 45 | return { 46 | stop: function stop() { 47 | subscription.unsubscribe(); 48 | }, 49 | eventCounts: eventCounts 50 | }; 51 | } -------------------------------------------------------------------------------- /demo/miniprogram_npm/@cloudcare/rum-miniapp/rumEventsCollection/transport/batch.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.startRumBatch = startRumBatch; 7 | 8 | var _lifeCycle = require("../../core/lifeCycle"); 9 | 10 | var _transport = require("../../core/transport"); 11 | 12 | var _enums = require("../../helper/enums"); 13 | 14 | function startRumBatch(configuration, lifeCycle) { 15 | var batch = makeRumBatch(configuration, lifeCycle); 16 | lifeCycle.subscribe(_lifeCycle.LifeCycleEventType.RUM_EVENT_COLLECTED, function (serverRumEvent) { 17 | if (serverRumEvent.type === _enums.RumEventType.VIEW) { 18 | batch.upsert(serverRumEvent, serverRumEvent.page.id); 19 | } else { 20 | batch.add(serverRumEvent); 21 | } 22 | }); 23 | return { 24 | stop: function stop() { 25 | batch.stop(); 26 | } 27 | }; 28 | } 29 | 30 | function makeRumBatch(configuration, lifeCycle) { 31 | var primaryBatch = createRumBatch(configuration.datakitUrl, lifeCycle); 32 | 33 | function createRumBatch(endpointUrl, lifeCycle) { 34 | return new _transport.Batch(new _transport.HttpRequest(endpointUrl, configuration.batchBytesLimit), configuration.maxBatchSize, configuration.batchBytesLimit, configuration.maxMessageSize, configuration.flushTimeout, lifeCycle); 35 | } 36 | 37 | var stopped = false; 38 | return { 39 | add: function add(message) { 40 | if (stopped) { 41 | return; 42 | } 43 | 44 | primaryBatch.add(message); 45 | }, 46 | stop: function stop() { 47 | stopped = true; 48 | }, 49 | upsert: function upsert(message, key) { 50 | if (stopped) { 51 | return; 52 | } 53 | 54 | primaryBatch.upsert(message, key); 55 | } 56 | }; 57 | } -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@cloudcare/rum-miniapp": "^2.0.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demo/pages/index/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | const app = getApp() 4 | Page({ 5 | data: { 6 | motto: 'Hello World', 7 | userInfo: {}, 8 | hasUserInfo: false, 9 | canIUse: wx.canIUse('button.open-type.getUserInfo'), 10 | }, 11 | bindSetData: function () { 12 | this.setData({motto: 'hahhah'}) 13 | }, 14 | 15 | onAddToFavorites(res) { 16 | // webview 页面返回 webViewUrl 17 | console.log('webViewUrl: ', res.webViewUrl) 18 | return { 19 | title: '自定义标题', 20 | imageUrl: 'http://demo.png', 21 | query: 'name=xxx&age=xxx', 22 | } 23 | }, 24 | onShareAppMessage() { 25 | const promise = new Promise(resolve => { 26 | setTimeout(() => { 27 | resolve({ 28 | title: '自定义转发标题' 29 | }) 30 | }, 2000) 31 | }) 32 | console.log('分享app') 33 | return { 34 | title: '自定义转发标题', 35 | path: '/page/user?id=123', 36 | promise 37 | } 38 | }, 39 | onShareTimeline() { 40 | console.log(111111,'分享朋友圈') 41 | return { 42 | title: '自定义标题', 43 | imageUrl: 'http://demo.png', 44 | query: 'name=xxx&age=xxx', 45 | } 46 | }, 47 | onTabItemTap(item) { 48 | console.log(item.index) 49 | console.log(item.pagePath) 50 | console.log(item.text) 51 | }, 52 | //事件处理函数 53 | bindViewTap: function () { 54 | wx.request({ 55 | url: "http://testing-ft2x-api.cloudcare.cn/api/v1/tag/list", 56 | method: "get", 57 | header: { 58 | "content-type": "application/json", 59 | "X-FT-Auth-Token": 60 | "front.eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY250X3V1aWQiOiJhY250XzM1OTNkNmQwNzFjYzExZWI5NTZmMWVmM2RkZTUyNTlmIiwid3NfdXVpZCI6Indrc3BfMmRjNDMxZDY2OTM3MTFlYjhmZjk3YWVlZTA0YjU0YWYiLCJ0b2tlbl91dWlkIjoiMjRhZWJiNTlkZDVlNDVlNGIxNDdjYzIyZTY2MDJlYjMiLCJ0aW1lIjoxNjQ4NDM0OTk1LjE2MTg4NTUsInJhbmdzdHIiOiJQT1k0RVNGWiIsImxvZ2luX3R5cGUiOiJ3ZWIifQ.loI4aNgFnXwcBNfVcMDNJbH9jmgLew3YmDR2l_Tk_T8", 61 | }, 62 | }) 63 | wx.downloadFile({ 64 | url: 'https://www.xxxxx.com/sdtf', 65 | }) 66 | }, 67 | onError: function () { 68 | console.log(arguments, 'arguments') 69 | }, 70 | onLoad: function () { 71 | console.log(getCurrentPages(), 'getCurrentPages') 72 | console.error('xxxxxxx') 73 | const xxx = sdfs 74 | 75 | if (app.globalData.userInfo) { 76 | this.setData({ 77 | userInfo: app.globalData.userInfo, 78 | hasUserInfo: true, 79 | }) 80 | } else if (this.data.canIUse) { 81 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 82 | // 所以此处加入 callback 以防止这种情况 83 | app.userInfoReadyCallback = (res) => { 84 | this.setData({ 85 | userInfo: res.userInfo, 86 | hasUserInfo: true, 87 | }) 88 | } 89 | } else { 90 | // 在没有 open-type=getUserInfo 版本的兼容处理 91 | wx.getUserInfo({ 92 | success: (res) => { 93 | app.globalData.userInfo = res.userInfo 94 | this.setData({ 95 | userInfo: res.userInfo, 96 | hasUserInfo: true, 97 | }) 98 | }, 99 | }) 100 | } 101 | }, 102 | getUserInfo: function (e) { 103 | console.log(e) 104 | app.globalData.userInfo = e.detail.userInfo 105 | this.setData({ 106 | userInfo: e.detail.userInfo, 107 | hasUserInfo: true, 108 | }) 109 | }, 110 | }) 111 | -------------------------------------------------------------------------------- /demo/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "test": "/components/test/test" 4 | } 5 | } -------------------------------------------------------------------------------- /demo/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{userInfo.nickName}} 8 | 9 | 10 | 11 | 12 | 13 | {{motto}} 14 | rum 15 | redirect rum 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/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 | } -------------------------------------------------------------------------------- /demo/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 | }) 16 | -------------------------------------------------------------------------------- /demo/pages/logs/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "查看启动日志", 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /demo/pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{index + 1}}. {{log}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /demo/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 | -------------------------------------------------------------------------------- /demo/pages/rum.js: -------------------------------------------------------------------------------- 1 | // pages/rum.js 2 | Page({ 3 | 4 | /** 5 | * 页面的初始数据 6 | */ 7 | data: { 8 | text: '' 9 | }, 10 | 11 | /** 12 | * 生命周期函数--监听页面加载 13 | */ 14 | onLoad: function (options) { 15 | var t = 'xxxx' 16 | setTimeout(() => { 17 | t = 'xxxxxss' 18 | console.log(t,'t') 19 | this.setData({ 20 | text: 'xxxx' 21 | }) 22 | }, 1000) 23 | }, 24 | 25 | /** 26 | * 生命周期函数--监听页面初次渲染完成 27 | */ 28 | onReady: function () { 29 | 30 | }, 31 | bindSetData: function() { 32 | // this.setData({ 33 | // text: '111111' 34 | // }) 35 | }, 36 | /** 37 | * 生命周期函数--监听页面显示 38 | */ 39 | onShow: function () { 40 | 41 | }, 42 | 43 | /** 44 | * 生命周期函数--监听页面隐藏 45 | */ 46 | onHide: function () { 47 | 48 | }, 49 | 50 | /** 51 | * 生命周期函数--监听页面卸载 52 | */ 53 | onUnload: function () { 54 | 55 | }, 56 | 57 | /** 58 | * 页面相关事件处理函数--监听用户下拉动作 59 | */ 60 | onPullDownRefresh: function () { 61 | 62 | }, 63 | 64 | /** 65 | * 页面上拉触底事件的处理函数 66 | */ 67 | onReachBottom: function () { 68 | 69 | }, 70 | 71 | /** 72 | * 用户点击右上角分享 73 | */ 74 | onShareAppMessage: function () { 75 | 76 | } 77 | }) -------------------------------------------------------------------------------- /demo/pages/rum.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /demo/pages/rum.wxml: -------------------------------------------------------------------------------- 1 | 2 | {{text}} 3 | 4 | -------------------------------------------------------------------------------- /demo/pages/rum.wxss: -------------------------------------------------------------------------------- 1 | /* pages/rum.wxss */ -------------------------------------------------------------------------------- /demo/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": false, 8 | "es6": true, 9 | "enhance": false, 10 | "postcss": true, 11 | "preloadBackgroundData": false, 12 | "minified": true, 13 | "newFeature": false, 14 | "coverView": true, 15 | "nodeModules": false, 16 | "autoAudits": false, 17 | "showShadowRootInWxmlPanel": true, 18 | "scopeDataCheck": false, 19 | "uglifyFileName": false, 20 | "checkInvalidKey": true, 21 | "checkSiteMap": true, 22 | "uploadWithSourceMap": true, 23 | "compileHotReLoad": false, 24 | "useMultiFrameRuntime": true, 25 | "useApiHook": true, 26 | "useApiHostProcess": true, 27 | "babelSetting": { 28 | "ignore": [], 29 | "disablePlugins": [], 30 | "outputPath": "" 31 | }, 32 | "useIsolateContext": false, 33 | "userConfirmedBundleSwitch": false, 34 | "packNpmManually": false, 35 | "packNpmRelationList": [], 36 | "minifyWXSS": true, 37 | "disableUseStrict": false, 38 | "minifyWXML": true, 39 | "showES6CompileOption": false, 40 | "useCompilerPlugins": false, 41 | "ignoreUploadUnusedFiles": true 42 | }, 43 | "compileType": "miniprogram", 44 | "libVersion": "2.17.0", 45 | "appid": "wx4bffe864f9649b91", 46 | "projectname": "demo", 47 | "debugOptions": { 48 | "hidedInDevtools": [] 49 | }, 50 | "scripts": {}, 51 | "isGameTourist": false, 52 | "simulatorType": "wechat", 53 | "simulatorPluginLibVersion": {}, 54 | "condition": { 55 | "search": { 56 | "list": [] 57 | }, 58 | "conversation": { 59 | "list": [] 60 | }, 61 | "game": { 62 | "list": [] 63 | }, 64 | "plugin": { 65 | "list": [] 66 | }, 67 | "gamePlugin": { 68 | "list": [] 69 | }, 70 | "miniprogram": { 71 | "list": [] 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /demo/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /demo/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 | -------------------------------------------------------------------------------- /demo2/.hbuilderx/launch.json: -------------------------------------------------------------------------------- 1 | { // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ 2 | // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 3 | "version": "0.0", 4 | "configurations": [{ 5 | "type": "uniCloud", 6 | "default": { 7 | "launchtype": "local" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /demo2/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /demo2/demo2/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /demo2/demo2/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | Vue.config.productionTip = false 5 | 6 | App.mpType = 'app' 7 | 8 | const app = new Vue({ 9 | ...App 10 | }) 11 | app.$mount() 12 | -------------------------------------------------------------------------------- /demo2/demo2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "demo2", 3 | "appid" : "", 4 | "description" : "", 5 | "versionName" : "1.0.0", 6 | "versionCode" : "100", 7 | "transformPx" : false, 8 | /* 5+App特有相关 */ 9 | "app-plus" : { 10 | "usingComponents" : true, 11 | "nvueStyleCompiler" : "uni-app", 12 | "compilerVersion" : 3, 13 | "splashscreen" : { 14 | "alwaysShowBeforeRender" : true, 15 | "waiting" : true, 16 | "autoclose" : true, 17 | "delay" : 0 18 | }, 19 | /* 模块配置 */ 20 | "modules" : {}, 21 | /* 应用发布信息 */ 22 | "distribute" : { 23 | /* android打包配置 */ 24 | "android" : { 25 | "permissions" : [ 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "" 41 | ] 42 | }, 43 | /* ios打包配置 */ 44 | "ios" : {}, 45 | /* SDK配置 */ 46 | "sdkConfigs" : {} 47 | } 48 | }, 49 | /* 快应用特有相关 */ 50 | "quickapp" : {}, 51 | /* 小程序特有相关 */ 52 | "mp-weixin" : { 53 | "appid" : "", 54 | "setting" : { 55 | "urlCheck" : false 56 | }, 57 | "usingComponents" : true 58 | }, 59 | "mp-alipay" : { 60 | "usingComponents" : true 61 | }, 62 | "mp-baidu" : { 63 | "usingComponents" : true 64 | }, 65 | "mp-toutiao" : { 66 | "usingComponents" : true 67 | }, 68 | "uniStatistics": { 69 | "enable": false 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /demo2/demo2/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "navigationBarTitleText": "uni-app" 7 | } 8 | } 9 | ], 10 | "globalStyle": { 11 | "navigationBarTextStyle": "black", 12 | "navigationBarTitleText": "uni-app", 13 | "navigationBarBackgroundColor": "#F8F8F8", 14 | "backgroundColor": "#F8F8F8" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo2/demo2/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | 26 | 53 | -------------------------------------------------------------------------------- /demo2/demo2/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuanceCloud/datakit-miniprogram/5c24eb3f4980ff1cab5a2cb6550e2c16cbea204e/demo2/demo2/static/logo.png -------------------------------------------------------------------------------- /demo2/demo2/uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color:#333;//基本色 25 | $uni-text-color-inverse:#fff;//反色 26 | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable:#c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color:#ffffff; 32 | $uni-bg-color-grey:#f8f8f8; 33 | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 34 | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color:#c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm:24rpx; 43 | $uni-font-size-base:28rpx; 44 | $uni-font-size-lg:32rpx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm:40rpx; 48 | $uni-img-size-base:52rpx; 49 | $uni-img-size-lg:80rpx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4rpx; 53 | $uni-border-radius-base: 6rpx; 54 | $uni-border-radius-lg: 12rpx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20rpx; 60 | $uni-spacing-row-lg: 30rpx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8rpx; 64 | $uni-spacing-col-base: 16rpx; 65 | $uni-spacing-col-lg: 24rpx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2C405A; // 文章标题颜色 72 | $uni-font-size-title:40rpx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle:36rpx; 75 | $uni-color-paragraph: #3F536E; // 文章段落颜色 76 | $uni-font-size-paragraph:30rpx; -------------------------------------------------------------------------------- /demo2/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | Vue.config.productionTip = false 5 | 6 | App.mpType = 'app' 7 | 8 | const { datafluxRum } = require('./miniprogram/dataflux-rum-uniapp') 9 | // // 初始化 Sentry 10 | datafluxRum.init(Vue, { 11 | datakitOrigin: 'http://172.16.2.201:31845', 12 | applicationId: 'appid_6cb4c98eba9143c88c83e544407b1c74', 13 | env: 'prod', 14 | version: '1.0.0', 15 | trackInteractions: true, 16 | allowedTracingOrigins: ['http://testing-ft2x-api.cloudcare.cn'], 17 | traceType: 'zipkin' 18 | }) 19 | datafluxRum.setUser({ 20 | id: '1234', 21 | name: 'John Doe', 22 | email: 'john@doe.com', 23 | }) 24 | datafluxRum && datafluxRum.addRumGlobalContext('isvip', 'xxxx'); 25 | datafluxRum.addRumGlobalContext('activity', { 26 | hasPaid: true, 27 | amount: 23.42 28 | }); 29 | const app = new Vue({ 30 | ...App, 31 | }) 32 | app.$mount() 33 | -------------------------------------------------------------------------------- /demo2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "rrr", 3 | "appid" : "", 4 | "description" : "", 5 | "versionName" : "1.0.0", 6 | "versionCode" : "100", 7 | "transformPx" : false, 8 | /* 5+App特有相关 */ 9 | "app-plus" : { 10 | "usingComponents" : true, 11 | "nvueStyleCompiler" : "uni-app", 12 | "compilerVersion" : 3, 13 | "splashscreen" : { 14 | "alwaysShowBeforeRender" : true, 15 | "waiting" : true, 16 | "autoclose" : true, 17 | "delay" : 0 18 | }, 19 | /* 模块配置 */ 20 | "modules" : {}, 21 | /* 应用发布信息 */ 22 | "distribute" : { 23 | /* android打包配置 */ 24 | "android" : { 25 | "permissions" : [ 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "" 41 | ] 42 | }, 43 | /* ios打包配置 */ 44 | "ios" : {}, 45 | /* SDK配置 */ 46 | "sdkConfigs" : {} 47 | } 48 | }, 49 | /* 快应用特有相关 */ 50 | "quickapp" : {}, 51 | /* 小程序特有相关 */ 52 | "mp-weixin" : { 53 | "appid" : "", 54 | "setting" : { 55 | "urlCheck" : false, 56 | "es6" : false 57 | }, 58 | "usingComponents" : true 59 | }, 60 | "mp-alipay" : { 61 | "usingComponents" : true 62 | }, 63 | "mp-baidu" : { 64 | "usingComponents" : true 65 | }, 66 | "mp-toutiao" : { 67 | "usingComponents" : true 68 | }, 69 | "uniStatistics" : { 70 | "enable" : false 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /demo2/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "navigationBarTitleText": "uni-app" 7 | } 8 | }, 9 | { 10 | "path": "pages/home/home", 11 | "style": { 12 | "navigationBarTitleText": "uni-app" 13 | } 14 | } 15 | ], 16 | "globalStyle": { 17 | "navigationBarTextStyle": "black", 18 | "navigationBarTitleText": "uni-app", 19 | "navigationBarBackgroundColor": "#F8F8F8", 20 | "backgroundColor": "#F8F8F8" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo2/pages/home/home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /demo2/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 117 | 118 | 145 | -------------------------------------------------------------------------------- /demo2/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuanceCloud/datakit-miniprogram/5c24eb3f4980ff1cab5a2cb6550e2c16cbea204e/demo2/static/logo.png -------------------------------------------------------------------------------- /demo2/test-sdk/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /demo2/test-sdk/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | Vue.config.productionTip = false 5 | 6 | App.mpType = 'app' 7 | 8 | const app = new Vue({ 9 | ...App 10 | }) 11 | app.$mount() 12 | -------------------------------------------------------------------------------- /demo2/test-sdk/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "test-sdk", 3 | "appid" : "", 4 | "description" : "", 5 | "versionName" : "1.0.0", 6 | "versionCode" : "100", 7 | "transformPx" : false, 8 | /* 5+App特有相关 */ 9 | "app-plus" : { 10 | "usingComponents" : true, 11 | "nvueStyleCompiler" : "uni-app", 12 | "compilerVersion" : 3, 13 | "splashscreen" : { 14 | "alwaysShowBeforeRender" : true, 15 | "waiting" : true, 16 | "autoclose" : true, 17 | "delay" : 0 18 | }, 19 | /* 模块配置 */ 20 | "modules" : {}, 21 | /* 应用发布信息 */ 22 | "distribute" : { 23 | /* android打包配置 */ 24 | "android" : { 25 | "permissions" : [ 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "" 41 | ] 42 | }, 43 | /* ios打包配置 */ 44 | "ios" : {}, 45 | /* SDK配置 */ 46 | "sdkConfigs" : {} 47 | } 48 | }, 49 | /* 快应用特有相关 */ 50 | "quickapp" : {}, 51 | /* 小程序特有相关 */ 52 | "mp-weixin" : { 53 | "appid" : "", 54 | "setting" : { 55 | "urlCheck" : false 56 | }, 57 | "usingComponents" : true 58 | }, 59 | "mp-alipay" : { 60 | "usingComponents" : true 61 | }, 62 | "mp-baidu" : { 63 | "usingComponents" : true 64 | }, 65 | "mp-toutiao" : { 66 | "usingComponents" : true 67 | }, 68 | "uniStatistics": { 69 | "enable": false 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /demo2/test-sdk/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "navigationBarTitleText": "uni-app" 7 | } 8 | } 9 | ], 10 | "globalStyle": { 11 | "navigationBarTextStyle": "black", 12 | "navigationBarTitleText": "uni-app", 13 | "navigationBarBackgroundColor": "#F8F8F8", 14 | "backgroundColor": "#F8F8F8" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo2/test-sdk/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | 26 | 53 | -------------------------------------------------------------------------------- /demo2/test-sdk/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuanceCloud/datakit-miniprogram/5c24eb3f4980ff1cab5a2cb6550e2c16cbea204e/demo2/test-sdk/static/logo.png -------------------------------------------------------------------------------- /demo2/test-sdk/uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color:#333;//基本色 25 | $uni-text-color-inverse:#fff;//反色 26 | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable:#c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color:#ffffff; 32 | $uni-bg-color-grey:#f8f8f8; 33 | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 34 | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color:#c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm:24rpx; 43 | $uni-font-size-base:28rpx; 44 | $uni-font-size-lg:32rpx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm:40rpx; 48 | $uni-img-size-base:52rpx; 49 | $uni-img-size-lg:80rpx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4rpx; 53 | $uni-border-radius-base: 6rpx; 54 | $uni-border-radius-lg: 12rpx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20rpx; 60 | $uni-spacing-row-lg: 30rpx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8rpx; 64 | $uni-spacing-col-base: 16rpx; 65 | $uni-spacing-col-lg: 24rpx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2C405A; // 文章标题颜色 72 | $uni-font-size-title:40rpx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle:36rpx; 75 | $uni-color-paragraph: #3F536E; // 文章段落颜色 76 | $uni-font-size-paragraph:30rpx; -------------------------------------------------------------------------------- /demo2/uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color:#333;//基本色 25 | $uni-text-color-inverse:#fff;//反色 26 | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable:#c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color:#ffffff; 32 | $uni-bg-color-grey:#f8f8f8; 33 | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 34 | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color:#c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm:24rpx; 43 | $uni-font-size-base:28rpx; 44 | $uni-font-size-lg:32rpx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm:40rpx; 48 | $uni-img-size-base:52rpx; 49 | $uni-img-size-lg:80rpx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4rpx; 53 | $uni-border-radius-base: 6rpx; 54 | $uni-border-radius-lg: 12rpx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20rpx; 60 | $uni-spacing-row-lg: 30rpx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8rpx; 64 | $uni-spacing-col-base: 16rpx; 65 | $uni-spacing-col-lg: 24rpx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2C405A; // 文章标题颜色 72 | $uni-font-size-title:40rpx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle:36rpx; 75 | $uni-color-paragraph: #3F536E; // 文章段落颜色 76 | $uni-font-size-paragraph:30rpx; -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/.automator/mp-alipay/.automator.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuanceCloud/datakit-miniprogram/5c24eb3f4980ff1cab5a2cb6550e2c16cbea204e/demo2/unpackage/dist/dev/.automator/mp-alipay/.automator.json -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/.automator/mp-weixin/.automator.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuanceCloud/datakit-miniprogram/5c24eb3f4980ff1cab5a2cb6550e2c16cbea204e/demo2/unpackage/dist/dev/.automator/mp-weixin/.automator.json -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/.sourcemap/mp-weixin/common/runtime.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[null],"names":[],"mappings":";QAAA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;QACA,QAAQ,oBAAoB;QAC5B;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA,iBAAiB,4BAA4B;QAC7C;QACA;QACA,kBAAkB,2BAA2B;QAC7C;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;;QAEA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA,0CAA0C,gCAAgC;QAC1E;QACA;;QAEA;QACA;QACA;QACA,wDAAwD,kBAAkB;QAC1E;QACA,iDAAiD,cAAc;QAC/D;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,yCAAyC,iCAAiC;QAC1E,gHAAgH,mBAAmB,EAAE;QACrI;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;QAEA;QACA;QACA;QACA;QACA,gBAAgB,uBAAuB;QACvC;;;QAGA;QACA","file":"common/runtime.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t\"common/runtime\": 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \tvar jsonpArray = global[\"webpackJsonp\"] = global[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/.sourcemap/mp-weixin/pages/home/home.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["uni-app:///main.js","webpack:////Users/arnolddeng/Desktop/www/dataflux-rum-sdk-wechat/demo2/pages/home/home.vue?4be5","webpack:////Users/arnolddeng/Desktop/www/dataflux-rum-sdk-wechat/demo2/pages/home/home.vue?4a4f","webpack:////Users/arnolddeng/Desktop/www/dataflux-rum-sdk-wechat/demo2/pages/home/home.vue?b7f0","webpack:////Users/arnolddeng/Desktop/www/dataflux-rum-sdk-wechat/demo2/pages/home/home.vue?ef02","uni-app:///pages/home/home.vue"],"names":["createPage","Page"],"mappings":";;;;;;;;;;kDAAA;AACA;AACA,yF;AACAA,UAAU,CAACC,aAAD,CAAV,C;;;;;;;;;;;;;ACHA;AAAA;AAAA;AAAA;AAAA;AAAiH;AACjH;AACwD;AACL;;;AAGnD;AACmN;AACnN,gBAAgB,iNAAU;AAC1B,EAAE,0EAAM;AACR,EAAE,+EAAM;AACR,EAAE,wFAAe;AACjB;AACA;AACA;AACA;AACA;AACA,EAAE,mFAAU;AACZ;AACA;;AAEA;AACe,gF;;;;;;;;;;;;ACtBf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;;;;;;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;ACRA;AAAA;AAAA;AAAA;AAAi1B,CAAgB,kyBAAG,EAAC,C;;;;;;;;;;;;;;;;;;;ACOr2B;AACA,QADA,oBACA;;AAEA,GAHA;AAIA;AACA;AACA;AACA,KAHA,EAJA,E","file":"pages/home/home.js","sourcesContent":["import 'uni-pages';\nimport Vue from 'vue'\nimport Page from './pages/home/home.vue'\ncreatePage(Page)","import { render, staticRenderFns, recyclableRender, components } from \"./home.vue?vue&type=template&id=92bb8f34&\"\nvar renderjs\nimport script from \"./home.vue?vue&type=script&lang=js&\"\nexport * from \"./home.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null,\n false,\n components,\n renderjs\n)\n\ncomponent.options.__file = \"pages/home/home.vue\"\nexport default component.exports","export * from \"-!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader/index.js??ref--16-0!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/webpack-uni-mp-loader/lib/template.js!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-app-loader/page-meta.js!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/index.js??vue-loader-options!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/webpack-uni-mp-loader/lib/style.js!./home.vue?vue&type=template&id=92bb8f34&\"","var components\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n}\nvar recyclableRender = false\nvar staticRenderFns = []\nrender._withStripped = true\n\nexport { render, staticRenderFns, recyclableRender, components }","import mod from \"-!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/babel-loader/lib/index.js!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader/index.js??ref--12-1!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/webpack-uni-mp-loader/lib/script.js!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/index.js??vue-loader-options!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/webpack-uni-mp-loader/lib/style.js!./home.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/babel-loader/lib/index.js!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader/index.js??ref--12-1!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/webpack-uni-mp-loader/lib/script.js!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/index.js??vue-loader-options!../../../../../../../../Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/@dcloudio/webpack-uni-mp-loader/lib/style.js!./home.vue?vue&type=script&lang=js&\"","\n\n\n\n\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/app.js: -------------------------------------------------------------------------------- 1 | 2 | require('./common/runtime.js') 3 | require('./common/vendor.js') 4 | require('./common/main.js') -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/home/home" 5 | ], 6 | "subPackages": [], 7 | "window": { 8 | "navigationBarTextStyle": "black", 9 | "navigationBarTitleText": "uni-app", 10 | "navigationBarBackgroundColor": "#F8F8F8", 11 | "backgroundColor": "#F8F8F8" 12 | }, 13 | "usingComponents": {} 14 | } -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/app.wxss: -------------------------------------------------------------------------------- 1 | @import './common/main.wxss'; 2 | 3 | [data-custom-hidden="true"],[bind-data-custom-hidden="true"]{display: none !important;} -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/common/main.wxss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | /*每个页面公共css */ 17 | 18 | -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/pages/home/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "uni-app", 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/pages/home/home.wxml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "uni-app", 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | 2 | .content { 3 | display: -webkit-box; 4 | display: -webkit-flex; 5 | display: flex; 6 | -webkit-box-orient: vertical; 7 | -webkit-box-direction: normal; 8 | -webkit-flex-direction: column; 9 | flex-direction: column; 10 | -webkit-box-align: center; 11 | -webkit-align-items: center; 12 | align-items: center; 13 | -webkit-box-pack: center; 14 | -webkit-justify-content: center; 15 | justify-content: center; 16 | } 17 | .logo { 18 | height: 200rpx; 19 | width: 200rpx; 20 | margin-top: 200rpx; 21 | margin-left: auto; 22 | margin-right: auto; 23 | margin-bottom: 50rpx; 24 | } 25 | .text-area { 26 | display: -webkit-box; 27 | display: -webkit-flex; 28 | display: flex; 29 | -webkit-box-pack: center; 30 | -webkit-justify-content: center; 31 | justify-content: center; 32 | } 33 | .title { 34 | font-size: 36rpx; 35 | color: #8f8f94; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": false, 8 | "es6": false 9 | }, 10 | "compileType": "miniprogram", 11 | "libVersion": "", 12 | "appid": "touristappid", 13 | "projectname": "rrr", 14 | "condition": { 15 | "search": { 16 | "current": -1, 17 | "list": [] 18 | }, 19 | "conversation": { 20 | "current": -1, 21 | "list": [] 22 | }, 23 | "game": { 24 | "current": -1, 25 | "list": [] 26 | }, 27 | "miniprogram": { 28 | "current": -1, 29 | "list": [] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /demo2/unpackage/dist/dev/mp-weixin/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuanceCloud/datakit-miniprogram/5c24eb3f4980ff1cab5a2cb6550e2c16cbea204e/demo2/unpackage/dist/dev/mp-weixin/static/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudcare/rum-miniapp", 3 | "version": "2.1.0", 4 | "main": "cjs/index.js", 5 | "module": "esm/index.js", 6 | "miniprogram": "cjs", 7 | "types": "cjs/index.d.ts", 8 | "devDependencies": { 9 | "@babel/core": "^7.12.10", 10 | "@babel/preset-env": "^7.12.10", 11 | "ali-oss": "^6.12.0", 12 | "npm-run-all": "^4.1.5", 13 | "replace-in-file": "^6.1.0", 14 | "webpack": "^5.37.0", 15 | "webpack-cli": "^4.7.0", 16 | "webpack-dev-server": "^3.11.2" 17 | }, 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1", 20 | "build": "run-p build:cjs build:esm build:wx", 21 | "build:watch": "webpack --watch --config ./webpack.config.js --mode=development", 22 | "build:wx": "rm -rf demo/miniprogram && webpack --config webpack.config.js --mode=production && npm run replace-build-env demo/miniprogram", 23 | "build:cjs": "rm -rf cjs && babel --config-file ./babel.cjs.json --out-dir cjs ./src && npm run replace-build-env cjs", 24 | "build:esm": "rm -rf esm && babel --config-file ./babel.esm.json --out-dir esm ./src && npm run replace-build-env esm", 25 | "publish:npm": "npm run build && node ./scripts/publish-oss.js && npm publish --access=public", 26 | "publish:oss:test": "npm run build && node ./scripts/publish-oss.js test", 27 | "replace-build-env": "node scripts/replace-build-env.js" 28 | }, 29 | "keywords": [ 30 | "dataflux", 31 | "rum", 32 | "sdk", 33 | "小程序", 34 | "miniapp" 35 | ], 36 | "repository": { 37 | "url": "https://github.com/DataFlux-cn/datakit-miniprogram", 38 | "type": "git" 39 | }, 40 | "author": "dataflux", 41 | "license": "MIT", 42 | "description": "DataFlux RUM 小程序 端数据指标监控", 43 | "dependencies": {} 44 | } 45 | -------------------------------------------------------------------------------- /scripts/build-env.js: -------------------------------------------------------------------------------- 1 | const execSync = require('child_process').execSync 2 | const packageJSON = require('../package.json') 3 | 4 | let sdkVersion = packageJSON.version 5 | 6 | module.exports = { 7 | SDK_VERSION: sdkVersion, 8 | } 9 | -------------------------------------------------------------------------------- /scripts/replace-build-env.js: -------------------------------------------------------------------------------- 1 | const replace = require('replace-in-file') 2 | const buildEnv = require('./build-env') 3 | 4 | /** 5 | * Replace BuildEnv in build files 6 | * Usage: 7 | * TARGET_DATACENTER=xxx BUILD_MODE=zzz node replace-build-env.js /path/to/build/directory 8 | */ 9 | 10 | const buildDirectory = process.argv[2] 11 | console.log(process.argv, '=====') 12 | console.log(`Replace BuildEnv in '${buildDirectory}' with:`) 13 | console.log(JSON.stringify(buildEnv, null, 2)) 14 | 15 | try { 16 | const results = replace.sync({ 17 | files: `${buildDirectory}/**/*.js`, 18 | from: Object.keys(buildEnv).map((entry) => `<<< ${entry} >>>`), 19 | to: Object.values(buildEnv), 20 | }) 21 | 22 | console.log( 23 | 'Changed files:', 24 | results.filter((entry) => entry.hasChanged).map((entry) => entry.file), 25 | ) 26 | process.exit(0) 27 | } catch (error) { 28 | console.error('Error occurred:', error) 29 | process.exit(1) 30 | } 31 | -------------------------------------------------------------------------------- /src/boot/buildEnv.js: -------------------------------------------------------------------------------- 1 | export const buildEnv = { 2 | sdkVersion: '<<< SDK_VERSION >>>', 3 | sdkName: 'df_miniapp_rum_sdk', 4 | } 5 | -------------------------------------------------------------------------------- /src/boot/rum.entry.js: -------------------------------------------------------------------------------- 1 | import { isPercentage, extend2Lev, createContextManager } from '../helper/utils' 2 | import { startRum } from './rum' 3 | 4 | export const makeRum = function (startRumImpl) { 5 | var isAlreadyInitialized = false 6 | var globalContextManager = createContextManager() 7 | var user = {} 8 | function clonedCommonContext() { 9 | return extend2Lev( 10 | {}, 11 | { 12 | context: globalContextManager.get(), 13 | user: user 14 | } 15 | ) 16 | } 17 | var rumGlobal = { 18 | init: function (userConfiguration) { 19 | if (typeof userConfiguration === 'undefined') { 20 | userConfiguration = {} 21 | } 22 | if (!canInitRum(userConfiguration)) { 23 | return 24 | } 25 | startRumImpl(userConfiguration, function () { 26 | return { 27 | user: user, 28 | context: globalContextManager.get() 29 | } 30 | }) 31 | isAlreadyInitialized = true 32 | }, 33 | addRumGlobalContext: globalContextManager.add, 34 | removeRumGlobalContext: globalContextManager.remove, 35 | getRumGlobalContext: globalContextManager.get, 36 | setRumGlobalContext: globalContextManager.set, 37 | setUser: function (newUser) { 38 | var sanitizedUser = sanitizeUser(newUser) 39 | if (sanitizedUser) { 40 | user = sanitizedUser 41 | } else { 42 | console.error('Unsupported user:', newUser) 43 | } 44 | }, 45 | removeUser: function () { 46 | user = {} 47 | } 48 | } 49 | return rumGlobal 50 | 51 | function canInitRum(userConfiguration) { 52 | if (isAlreadyInitialized) { 53 | console.error('DATAFLUX_RUM is already initialized.') 54 | return false 55 | } 56 | 57 | if (!userConfiguration.applicationId) { 58 | console.error( 59 | 'Application ID is not configured, no RUM data will be collected.', 60 | ) 61 | return false 62 | } 63 | if (!userConfiguration.datakitOrigin) { 64 | console.error( 65 | 'datakitOrigin is not configured, no RUM data will be collected.', 66 | ) 67 | return false 68 | } 69 | if ( 70 | userConfiguration.sampleRate !== undefined && 71 | !isPercentage(userConfiguration.sampleRate) 72 | ) { 73 | console.error('Sample Rate should be a number between 0 and 100') 74 | return false 75 | } 76 | return true 77 | } 78 | function sanitizeUser(newUser) { 79 | if (typeof newUser !== 'object' || !newUser) { 80 | return 81 | } 82 | var result = extend2Lev({}, newUser) 83 | if ('id' in result) { 84 | result.id = String(result.id) 85 | } 86 | if ('name' in result) { 87 | result.name = String(result.name) 88 | } 89 | if ('email' in result) { 90 | result.email = String(result.email) 91 | } 92 | return result 93 | } 94 | } 95 | export const datafluxRum = makeRum(startRum) 96 | -------------------------------------------------------------------------------- /src/boot/rum.js: -------------------------------------------------------------------------------- 1 | import { buildEnv } from './buildEnv' 2 | import { LifeCycle } from '../core/lifeCycle' 3 | import { commonInit } from '../core/configuration' 4 | import { startErrorCollection } from '../rumEventsCollection/error/errorCollection' 5 | import { startRumAssembly } from '../rumEventsCollection/assembly' 6 | import { startParentContexts } from '../rumEventsCollection/parentContexts' 7 | import { startRumBatch } from '../rumEventsCollection/transport/batch' 8 | import { startViewCollection } from '../rumEventsCollection/page/viewCollection' 9 | import { startRequestCollection } from '../rumEventsCollection/requestCollection' 10 | import { startResourceCollection } from '../rumEventsCollection/resource/resourceCollection' 11 | import { startAppCollection } from '../rumEventsCollection/app/appCollection' 12 | import { startPagePerformanceObservable } from '../rumEventsCollection/performanceCollection' 13 | import { startSetDataColloction } from '../rumEventsCollection/setDataCollection' 14 | import { startActionCollection } from '../rumEventsCollection/action/actionCollection' 15 | 16 | import { sdk } from '../core/sdk' 17 | export const startRum = function (userConfiguration, getCommonContext) { 18 | const configuration = commonInit(userConfiguration, buildEnv) 19 | const lifeCycle = new LifeCycle() 20 | var parentContexts = startParentContexts(lifeCycle) 21 | var batch = startRumBatch(configuration, lifeCycle) 22 | startRumAssembly( 23 | userConfiguration.applicationId, 24 | configuration, 25 | lifeCycle, 26 | parentContexts, 27 | getCommonContext 28 | ) 29 | startAppCollection(lifeCycle, configuration) 30 | startResourceCollection(lifeCycle, configuration) 31 | startViewCollection(lifeCycle, configuration) 32 | startErrorCollection(lifeCycle, configuration) 33 | startRequestCollection(lifeCycle, configuration) 34 | startPagePerformanceObservable(lifeCycle, configuration) 35 | startSetDataColloction(lifeCycle) 36 | startActionCollection(lifeCycle, configuration) 37 | } 38 | -------------------------------------------------------------------------------- /src/core/baseInfo.js: -------------------------------------------------------------------------------- 1 | import { sdk } from '../core/sdk' 2 | import { UUID } from '../helper/utils' 3 | import { CLIENT_ID_TOKEN } from '../helper/enums' 4 | class BaseInfo { 5 | constructor() { 6 | this.sessionId = UUID() 7 | this.getDeviceInfo() 8 | this.getNetWork() 9 | } 10 | getDeviceInfo() { 11 | try { 12 | const deviceInfo = sdk.getSystemInfoSync() 13 | var osInfo = deviceInfo.system.split(' ') 14 | var osVersion = osInfo.length > 1 && osInfo[1] 15 | var osVersionMajor = 16 | osVersion.split('.').length && osVersion.split('.')[0] 17 | var deviceUUid = '' 18 | if (deviceInfo.host) { 19 | deviceUUid = deviceInfo.host.appId 20 | } 21 | this.deviceInfo = { 22 | screenSize: `${deviceInfo.screenWidth}*${deviceInfo.screenHeight} `, 23 | platform: deviceInfo.platform, 24 | platformVersion: deviceInfo.version, 25 | osVersion: osVersion, 26 | osVersionMajor: osVersionMajor, 27 | os: osInfo.length > 1 && osInfo[0], 28 | brand: deviceInfo.brand, 29 | model: deviceInfo.model, 30 | frameworkVersion: deviceInfo.SDKVersion, 31 | pixelRatio: deviceInfo.pixelRatio, 32 | deviceUuid: deviceUUid, 33 | } 34 | } catch (e) {} 35 | } 36 | getClientID() { 37 | var clienetId = sdk.getStorageSync(CLIENT_ID_TOKEN) 38 | if (!clienetId) { 39 | clienetId = UUID() 40 | sdk.setStorageSync(CLIENT_ID_TOKEN, clienetId) 41 | } 42 | return clienetId 43 | } 44 | getNetWork() { 45 | sdk.getNetworkType({ 46 | success: (e) => { 47 | this.deviceInfo.network = e.networkType ? e.networkType : 'unknown' 48 | }, 49 | }) 50 | sdk.onNetworkStatusChange((e) => { 51 | this.deviceInfo.network = e.networkType ? e.networkType : 'unknown' 52 | }) 53 | } 54 | getSessionId() { 55 | return this.sessionId 56 | } 57 | } 58 | 59 | export default new BaseInfo() 60 | -------------------------------------------------------------------------------- /src/core/configuration.js: -------------------------------------------------------------------------------- 1 | import { extend2Lev, urlParse, values } from '../helper/utils' 2 | import { ONE_KILO_BYTE, ONE_SECOND, TraceType } from '../helper/enums' 3 | var TRIM_REGIX = /^\s+|\s+$/g 4 | export var DEFAULT_CONFIGURATION = { 5 | sampleRate: 100, 6 | flushTimeout: 30 * ONE_SECOND, 7 | maxErrorsByMinute: 3000, 8 | /** 9 | * Logs intake limit 10 | */ 11 | maxBatchSize: 50, 12 | maxMessageSize: 256 * ONE_KILO_BYTE, 13 | 14 | /** 15 | * beacon payload max queue size implementation is 64kb 16 | * ensure that we leave room for logs, rum and potential other users 17 | */ 18 | batchBytesLimit: 16 * ONE_KILO_BYTE, 19 | datakitUrl: '', 20 | /** 21 | * arbitrary value, byte precision not needed 22 | */ 23 | requestErrorResponseLengthLimit: 32 * ONE_KILO_BYTE, 24 | trackInteractions: false, 25 | traceType: TraceType.DDTRACE, 26 | traceId128Bit: false, 27 | allowedTracingOrigins:[], // 新增 28 | } 29 | function trim(str) { 30 | return str.replace(TRIM_REGIX, '') 31 | } 32 | function getDatakitUrlUrl(url) { 33 | if (url && url.lastIndexOf('/') === url.length - 1) 34 | return trim(url) + 'v1/write/rum' 35 | return trim(url) + '/v1/write/rum' 36 | } 37 | export function commonInit(userConfiguration, buildEnv) { 38 | var transportConfiguration = { 39 | applicationId: userConfiguration.applicationId, 40 | env: userConfiguration.env || '', 41 | version: userConfiguration.version || '', 42 | sdkVersion: buildEnv.sdkVersion, 43 | sdkName: buildEnv.sdkName, 44 | datakitUrl: getDatakitUrlUrl( 45 | userConfiguration.datakitUrl || userConfiguration.datakitOrigin, 46 | ), 47 | tags: userConfiguration.tags || [], 48 | } 49 | if ('trackInteractions' in userConfiguration) { 50 | transportConfiguration.trackInteractions = !!userConfiguration.trackInteractions 51 | } 52 | if ('allowedTracingOrigins' in userConfiguration) { 53 | transportConfiguration.allowedTracingOrigins = userConfiguration.allowedTracingOrigins 54 | } 55 | if ('traceId128Bit' in userConfiguration) { 56 | transportConfiguration.traceId128Bit = !!userConfiguration.traceId128Bit 57 | } 58 | if ('traceType' in userConfiguration && hasTraceType(userConfiguration.traceType)) { 59 | transportConfiguration.traceType = userConfiguration.traceType 60 | } 61 | return extend2Lev(DEFAULT_CONFIGURATION, transportConfiguration) 62 | } 63 | function hasTraceType(traceType) { 64 | if (traceType && values(TraceType).indexOf(traceType) > -1) return true 65 | return false 66 | } 67 | const haveSameOrigin = function (url1, url2) { 68 | const parseUrl1 = urlParse(url1).getParse() 69 | const parseUrl2 = urlParse(url2).getParse() 70 | return parseUrl1.Origin === parseUrl2.Origin 71 | } 72 | export function isIntakeRequest(url, configuration) { 73 | return haveSameOrigin(url, configuration.datakitUrl) 74 | } 75 | -------------------------------------------------------------------------------- /src/core/dataMap.js: -------------------------------------------------------------------------------- 1 | import { RumEventType } from '../helper/enums' 2 | // 需要用双引号将字符串类型的field value括起来, 这里有数组标示[string, path] 3 | export var commonTags = { 4 | sdk_name: '_dd.sdk_name', 5 | sdk_version: '_dd.sdk_version', 6 | app_id: 'application.id', 7 | env: '_dd.env', 8 | version: '_dd.version', 9 | userid: 'user.id', 10 | user_email: 'user.email', 11 | user_name: 'user.name', 12 | session_id: 'session.id', 13 | session_type: 'session.type', 14 | is_signin: 'user.is_signin', 15 | device: 'device.brand', 16 | model: 'device.model', 17 | device_uuid: 'device.device_uuid', 18 | os: 'device.os', 19 | app: 'device.app', 20 | os_version: 'device.os_version', 21 | os_version_major: 'device.os_version_major', 22 | screen_size: 'device.screen_size', 23 | network_type: 'device.network_type', 24 | platform: 'device.platform', 25 | platform_version: 'device.platform_version', 26 | app_framework_version: 'device.framework_version', 27 | view_id: 'page.id', 28 | view_name: 'page.route', 29 | view_referer: 'page.referer', 30 | } 31 | export var dataMap = { 32 | view: { 33 | type: RumEventType.VIEW, 34 | tags: { 35 | view_apdex_level: 'page.apdex_level', 36 | is_active: 'page.is_active', 37 | }, 38 | fields: { 39 | page_fmp: 'page.fmp', 40 | first_paint_time: 'page.fpt', 41 | loading_time: 'page.loading_time', 42 | onload_to_onshow: 'page.onload2onshow', 43 | onshow_to_onready: 'page.onshow2onready', 44 | time_spent: 'page.time_spent', 45 | view_error_count: 'page.error.count', 46 | view_resource_count: 'page.resource.count', 47 | view_long_task_count: 'page.long_task.count', 48 | view_action_count: 'page.action.count', 49 | view_setdata_count: 'page.setdata.count', 50 | }, 51 | }, 52 | resource: { 53 | type: RumEventType.RESOURCE, 54 | tags: { 55 | trace_id: '_dd.trace_id', 56 | span_id: '_dd.span_id', 57 | resource_type: 'resource.type', 58 | resource_status: 'resource.status', 59 | resource_status_group: 'resource.status_group', 60 | resource_method: 'resource.method', 61 | resource_url: 'resource.url', 62 | resource_url_host: 'resource.url_host', 63 | resource_url_path: 'resource.url_path', 64 | resource_url_path_group: 'resource.url_path_group', 65 | resource_url_query: 'resource.url_query', 66 | }, 67 | fields: { 68 | resource_size: 'resource.size', 69 | resource_load: 'resource.load', 70 | resource_dns: 'resource.dns', 71 | resource_tcp: 'resource.tcp', 72 | resource_ssl: 'resource.ssl', 73 | resource_ttfb: 'resource.ttfb', 74 | resource_trans: 'resource.trans', 75 | resource_first_byte: 'resource.firstbyte', 76 | duration: 'resource.duration', 77 | }, 78 | }, 79 | error: { 80 | type: RumEventType.ERROR, 81 | tags: { 82 | error_source: 'error.source', 83 | error_type: 'error.type', 84 | resource_url: 'error.resource.url', 85 | resource_url_host: 'error.resource.url_host', 86 | resource_url_path: 'error.resource.url_path', 87 | resource_url_path_group: 'error.resource.url_path_group', 88 | resource_status: 'error.resource.status', 89 | resource_status_group: 'error.resource.status_group', 90 | resource_method: 'error.resource.method', 91 | }, 92 | fields: { 93 | error_message: ['string', 'error.message'], 94 | error_stack: ['string', 'error.stack'], 95 | }, 96 | }, 97 | long_task: { 98 | type: RumEventType.LONG_TASK, 99 | tags: {}, 100 | fields: { 101 | duration: 'long_task.duration', 102 | }, 103 | }, 104 | action: { 105 | type: RumEventType.ACTION, 106 | tags: { 107 | action_id: 'action.id', 108 | action_name: 'action.target.name', 109 | action_type: 'action.type', 110 | }, 111 | fields: { 112 | duration: 'action.loading_time', 113 | action_error_count: 'action.error.count', 114 | action_resource_count: 'action.resource.count', 115 | action_long_task_count: 'action.long_task.count', 116 | }, 117 | }, 118 | app: { 119 | alias_key: 'action', // metrc 别名, 120 | type: RumEventType.APP, 121 | tags: { 122 | action_id: 'app.id', 123 | action_name: 'app.name', 124 | action_type: 'app.type', 125 | }, 126 | fields: { 127 | duration: 'app.duration', 128 | }, 129 | }, 130 | } 131 | -------------------------------------------------------------------------------- /src/core/downloadProxy.js: -------------------------------------------------------------------------------- 1 | import { sdk } from './sdk' 2 | import { now } from '../helper/utils' 3 | import { RequestType } from '../helper/enums' 4 | var downloadProxySingleton 5 | var beforeSendCallbacks = [] 6 | var onRequestCompleteCallbacks = [] 7 | var originalDownloadRequest 8 | export function startDownloadProxy() { 9 | if (!downloadProxySingleton) { 10 | proxyDownload() 11 | downloadProxySingleton = { 12 | beforeSend: function (callback) { 13 | beforeSendCallbacks.push(callback) 14 | }, 15 | onRequestComplete: function (callback) { 16 | onRequestCompleteCallbacks.push(callback) 17 | }, 18 | } 19 | } 20 | return downloadProxySingleton 21 | } 22 | 23 | export function resetDownloadProxy() { 24 | if (downloadProxySingleton) { 25 | downloadProxySingleton = undefined 26 | beforeSendCallbacks.splice(0, beforeSendCallbacks.length) 27 | onRequestCompleteCallbacks.splice(0, onRequestCompleteCallbacks.length) 28 | sdk.downloadFile = originalDownloadRequest 29 | } 30 | } 31 | 32 | function proxyDownload() { 33 | originalDownloadRequest = sdk.downloadFile 34 | sdk.downloadFile = function () { 35 | var _this = this 36 | var dataflux_xhr = { 37 | method: 'GET', 38 | startTime: 0, 39 | url: arguments[0].url, 40 | type: RequestType.DOWNLOAD, 41 | responseType: 'file', 42 | } 43 | dataflux_xhr.startTime = now() 44 | 45 | var originalSuccess = arguments[0].success 46 | 47 | arguments[0].success = function () { 48 | reportXhr(arguments[0]) 49 | 50 | if (originalSuccess) { 51 | originalSuccess.apply(_this, arguments) 52 | } 53 | } 54 | var originalFail = arguments[0].fail 55 | arguments[0].fail = function () { 56 | reportXhr(arguments[0]) 57 | if (originalFail) { 58 | originalFail.apply(_this, arguments) 59 | } 60 | } 61 | var hasBeenReported = false 62 | var reportXhr = function (res) { 63 | if (hasBeenReported) { 64 | return 65 | } 66 | hasBeenReported = true 67 | dataflux_xhr.duration = now() - dataflux_xhr.startTime 68 | dataflux_xhr.response = JSON.stringify({ 69 | filePath: res.filePath, 70 | tempFilePath: res.tempFilePath, 71 | }) 72 | dataflux_xhr.header = res.header || {} 73 | dataflux_xhr.profile = res.profile 74 | dataflux_xhr.status = res.statusCode || res.status || 0 75 | onRequestCompleteCallbacks.forEach(function (callback) { 76 | callback(dataflux_xhr) 77 | }) 78 | } 79 | beforeSendCallbacks.forEach(function (callback) { 80 | callback(dataflux_xhr) 81 | }) 82 | return originalDownloadRequest.apply(this, arguments) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/core/errorCollection.js: -------------------------------------------------------------------------------- 1 | import { toArray, now } from '../helper/utils' 2 | import { ONE_MINUTE, RequestType } from '../helper/enums' 3 | import { 4 | ErrorSource, 5 | formatUnknownError, 6 | toStackTraceString, 7 | } from './errorTools' 8 | import { computeStackTrace, report } from '../helper/tracekit' 9 | import { Observable } from './observable' 10 | import { isIntakeRequest } from './configuration' 11 | import { resetXhrProxy, startXhrProxy } from './xhrProxy' 12 | import { resetDownloadProxy, startDownloadProxy } from './downloadProxy' 13 | var originalConsoleError 14 | 15 | export function startConsoleTracking(errorObservable) { 16 | originalConsoleError = console.error 17 | console.error = function () { 18 | originalConsoleError.apply(console, arguments) 19 | var args = toArray(arguments) 20 | var message = [] 21 | args.concat(['console error:']).forEach(function (para) { 22 | message.push(formatConsoleParameters(para)) 23 | }) 24 | 25 | errorObservable.notify({ 26 | message: message.join(' '), 27 | source: ErrorSource.CONSOLE, 28 | startTime: now(), 29 | }) 30 | } 31 | } 32 | 33 | export function stopConsoleTracking() { 34 | console.error = originalConsoleError 35 | } 36 | 37 | function formatConsoleParameters(param) { 38 | if (typeof param === 'string') { 39 | return param 40 | } 41 | if (param instanceof Error) { 42 | return toStackTraceString(computeStackTrace(param)) 43 | } 44 | return JSON.stringify(param, undefined, 2) 45 | } 46 | export function filterErrors(configuration, errorObservable) { 47 | var errorCount = 0 48 | var filteredErrorObservable = new Observable() 49 | errorObservable.subscribe(function (error) { 50 | if (errorCount < configuration.maxErrorsByMinute) { 51 | errorCount += 1 52 | filteredErrorObservable.notify(error) 53 | } else if (errorCount === configuration.maxErrorsByMinute) { 54 | errorCount += 1 55 | filteredErrorObservable.notify({ 56 | message: 57 | 'Reached max number of errors by minute: ' + 58 | configuration.maxErrorsByMinute, 59 | source: ErrorSource.AGENT, 60 | startTime: now(), 61 | }) 62 | } 63 | }) 64 | setInterval(function () { 65 | errorCount = 0 66 | }, ONE_MINUTE) 67 | return filteredErrorObservable 68 | } 69 | var traceKitReportHandler 70 | 71 | export function startRuntimeErrorTracking(errorObservable) { 72 | traceKitReportHandler = function (stackTrace, _, errorObject) { 73 | var error = formatUnknownError(stackTrace, errorObject, 'Uncaught') 74 | errorObservable.notify({ 75 | message: error.message, 76 | stack: error.stack, 77 | type: error.type, 78 | source: ErrorSource.SOURCE, 79 | startTime: now(), 80 | }) 81 | } 82 | report.subscribe(traceKitReportHandler) 83 | } 84 | 85 | export function stopRuntimeErrorTracking() { 86 | report.unsubscribe(traceKitReportHandler) 87 | } 88 | var filteredErrorsObservable 89 | 90 | export function startAutomaticErrorCollection(configuration) { 91 | if (!filteredErrorsObservable) { 92 | var errorObservable = new Observable() 93 | trackNetworkError(configuration, errorObservable) 94 | startConsoleTracking(errorObservable) 95 | startRuntimeErrorTracking(errorObservable) 96 | filteredErrorsObservable = filterErrors(configuration, errorObservable) 97 | } 98 | return filteredErrorsObservable 99 | } 100 | 101 | export function trackNetworkError(configuration, errorObservable) { 102 | startXhrProxy().onRequestComplete(function (context) { 103 | return handleCompleteRequest(context.type, context) 104 | }) 105 | startDownloadProxy().onRequestComplete(function (context) { 106 | return handleCompleteRequest(context.type, context) 107 | }) 108 | 109 | function handleCompleteRequest(type, request) { 110 | if ( 111 | !isIntakeRequest(request.url, configuration) && 112 | (isRejected(request) || isServerError(request)) 113 | ) { 114 | errorObservable.notify({ 115 | message: format(type) + 'error' + request.method + ' ' + request.url, 116 | resource: { 117 | method: request.method, 118 | statusCode: request.status, 119 | url: request.url, 120 | }, 121 | type: ErrorSource.NETWORK, 122 | source: ErrorSource.NETWORK, 123 | stack: 124 | truncateResponse(request.response, configuration) || 'Failed to load', 125 | startTime: request.startTime, 126 | }) 127 | } 128 | } 129 | 130 | return { 131 | stop: function () { 132 | resetXhrProxy() 133 | resetDownloadProxy() 134 | }, 135 | } 136 | } 137 | function isRejected(request) { 138 | return request.status === 0 && request.responseType !== 'opaque' 139 | } 140 | 141 | function isServerError(request) { 142 | return request.status >= 500 143 | } 144 | 145 | function truncateResponse(response, configuration) { 146 | if ( 147 | response && 148 | response.length > configuration.requestErrorResponseLengthLimit 149 | ) { 150 | return ( 151 | response.substring(0, configuration.requestErrorResponseLengthLimit) + 152 | '...' 153 | ) 154 | } 155 | return response 156 | } 157 | 158 | function format(type) { 159 | if (RequestType.XHR === type) { 160 | return 'XHR' 161 | } 162 | return RequestType.DOWNLOAD 163 | } 164 | -------------------------------------------------------------------------------- /src/core/errorTools.js: -------------------------------------------------------------------------------- 1 | export var ErrorSource = { 2 | AGENT: 'agent', 3 | CONSOLE: 'console', 4 | NETWORK: 'network', 5 | SOURCE: 'source', 6 | LOGGER: 'logger', 7 | } 8 | export function formatUnknownError(stackTrace, errorObject, nonErrorPrefix) { 9 | if ( 10 | !stackTrace || 11 | (stackTrace.message === undefined && !(errorObject instanceof Error)) 12 | ) { 13 | return { 14 | message: nonErrorPrefix + '' + JSON.stringify(errorObject), 15 | stack: 'No stack, consider using an instance of Error', 16 | type: stackTrace && stackTrace.name, 17 | } 18 | } 19 | return { 20 | message: stackTrace.message || 'Empty message', 21 | stack: toStackTraceString(stackTrace), 22 | type: stackTrace.name, 23 | } 24 | } 25 | 26 | export function toStackTraceString(stack) { 27 | var result = stack.name || 'Error' + ': ' + stack.message 28 | stack.stack.forEach(function (frame) { 29 | var func = frame.func === '?' ? '' : frame.func 30 | var args = 31 | frame.args && frame.args.length > 0 32 | ? '(' + frame.args.join(', ') + ')' 33 | : '' 34 | var line = frame.line ? ':' + frame.line : '' 35 | var column = frame.line && frame.column ? ':' + frame.column : '' 36 | result += '\n at ' + func + args + ' @ ' + frame.url + line + column 37 | }) 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /src/core/lifeCycle.js: -------------------------------------------------------------------------------- 1 | export class LifeCycle { 2 | constructor() { 3 | this.callbacks = {} 4 | } 5 | notify(eventType, data) { 6 | const eventCallbacks = this.callbacks[eventType] 7 | if (eventCallbacks) { 8 | eventCallbacks.forEach((callback) => callback(data)) 9 | } 10 | } 11 | subscribe(eventType, callback) { 12 | if (!this.callbacks[eventType]) { 13 | this.callbacks[eventType] = [] 14 | } 15 | this.callbacks[eventType].push(callback) 16 | return { 17 | unsubscribe: () => { 18 | this.callbacks[eventType] = this.callbacks[eventType].filter( 19 | (other) => callback !== other, 20 | ) 21 | }, 22 | } 23 | } 24 | } 25 | 26 | export var LifeCycleEventType = { 27 | PERFORMANCE_ENTRY_COLLECTED: 'PERFORMANCE_ENTRY_COLLECTED', 28 | AUTO_ACTION_CREATED: 'AUTO_ACTION_CREATED', 29 | AUTO_ACTION_COMPLETED: 'AUTO_ACTION_COMPLETED', 30 | AUTO_ACTION_DISCARDED: 'AUTO_ACTION_DISCARDED', 31 | APP_HIDE: 'APP_HIDE', 32 | APP_UPDATE: 'APP_UPDATE', 33 | PAGE_SET_DATA_UPDATE: 'PAGE_SET_DATA_UPDATE', 34 | PAGE_ALIAS_ACTION: 'PAGE_ALIAS_ACTION', 35 | VIEW_CREATED: 'VIEW_CREATED', 36 | VIEW_UPDATED: 'VIEW_UPDATED', 37 | VIEW_ENDED: 'VIEW_ENDED', 38 | REQUEST_STARTED: 'REQUEST_STARTED', 39 | REQUEST_COMPLETED: 'REQUEST_COMPLETED', 40 | RAW_RUM_EVENT_COLLECTED: 'RAW_RUM_EVENT_COLLECTED', 41 | RUM_EVENT_COLLECTED: 'RUM_EVENT_COLLECTED', 42 | } 43 | -------------------------------------------------------------------------------- /src/core/observable.js: -------------------------------------------------------------------------------- 1 | export class Observable { 2 | constructor() { 3 | this.observers = [] 4 | } 5 | subscribe(f) { 6 | this.observers.push(f) 7 | } 8 | notify(data) { 9 | this.observers.forEach(function (observer) { 10 | observer(data) 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/core/sdk.js: -------------------------------------------------------------------------------- 1 | import { deepMixObject } from '../helper/utils' 2 | 3 | function getSDK() { 4 | var sdk = null, 5 | tracker = '' 6 | try { 7 | if (wx && typeof wx === 'object' && typeof wx.request === 'function') { 8 | sdk = deepMixObject({}, wx) 9 | tracker = 'wx' 10 | wx = sdk 11 | } 12 | } catch (err) { 13 | console.warn('unsupport platform, Fail to start') 14 | } 15 | console.log('------get SDK-------') 16 | return { sdk, tracker } 17 | } 18 | const instance = getSDK() 19 | 20 | export const sdk = instance.sdk 21 | export const tracker = instance.tracker 22 | -------------------------------------------------------------------------------- /src/core/xhrProxy.js: -------------------------------------------------------------------------------- 1 | import { sdk } from './sdk' 2 | import { now } from '../helper/utils' 3 | import { RequestType } from '../helper/enums' 4 | var xhrProxySingleton 5 | var beforeSendCallbacks = [] 6 | var onRequestCompleteCallbacks = [] 7 | var originalXhrRequest 8 | export function startXhrProxy() { 9 | if (!xhrProxySingleton) { 10 | proxyXhr() 11 | xhrProxySingleton = { 12 | beforeSend: function (callback) { 13 | beforeSendCallbacks.push(callback) 14 | }, 15 | onRequestComplete: function (callback) { 16 | onRequestCompleteCallbacks.push(callback) 17 | }, 18 | } 19 | } 20 | return xhrProxySingleton 21 | } 22 | 23 | export function resetXhrProxy() { 24 | if (xhrProxySingleton) { 25 | xhrProxySingleton = undefined 26 | beforeSendCallbacks.splice(0, beforeSendCallbacks.length) 27 | onRequestCompleteCallbacks.splice(0, onRequestCompleteCallbacks.length) 28 | sdk.request = originalXhrRequest 29 | } 30 | } 31 | 32 | function proxyXhr() { 33 | originalXhrRequest = sdk.request 34 | sdk.request = function () { 35 | var _this = this 36 | var dataflux_xhr = { 37 | method: arguments[0].method || 'GET', 38 | startTime: 0, 39 | url: arguments[0].url, 40 | type: RequestType.XHR, 41 | responseType: arguments[0].responseType || 'text', 42 | option: arguments[0] 43 | } 44 | dataflux_xhr.startTime = now() 45 | 46 | var originalSuccess = arguments[0].success 47 | 48 | arguments[0].success = function () { 49 | reportXhr(arguments[0]) 50 | 51 | if (originalSuccess) { 52 | originalSuccess.apply(_this, arguments) 53 | } 54 | } 55 | var originalFail = arguments[0].fail 56 | arguments[0].fail = function () { 57 | reportXhr(arguments[0]) 58 | if (originalFail) { 59 | originalFail.apply(_this, arguments) 60 | } 61 | } 62 | var hasBeenReported = false 63 | var reportXhr = function (res) { 64 | if (hasBeenReported) { 65 | return 66 | } 67 | hasBeenReported = true 68 | dataflux_xhr.duration = now() - dataflux_xhr.startTime 69 | dataflux_xhr.response = JSON.stringify(res.data) 70 | dataflux_xhr.header = res.header || {} 71 | dataflux_xhr.profile = res.profile 72 | dataflux_xhr.status = res.statusCode || res.status || 0 73 | onRequestCompleteCallbacks.forEach(function (callback) { 74 | callback(dataflux_xhr) 75 | }) 76 | } 77 | beforeSendCallbacks.forEach(function (callback) { 78 | callback(dataflux_xhr) 79 | }) 80 | return originalXhrRequest.call(this, dataflux_xhr.option) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/helper/enums.js: -------------------------------------------------------------------------------- 1 | export const ONE_SECOND = 1000 2 | export const ONE_MINUTE = 60 * ONE_SECOND 3 | export const ONE_HOUR = 60 * ONE_MINUTE 4 | export const ONE_KILO_BYTE = 1024 5 | export const CLIENT_ID_TOKEN = 'datafluxRum:client:id' 6 | export const RumEventType = { 7 | ACTION: 'action', 8 | ERROR: 'error', 9 | LONG_TASK: 'long_task', 10 | VIEW: 'view', 11 | RESOURCE: 'resource', 12 | APP: 'app', 13 | ACTION: 'action', 14 | } 15 | 16 | export var RequestType = { 17 | XHR: 'network', 18 | DOWNLOAD: 'resource', 19 | } 20 | 21 | export var ActionType = { 22 | tap: 'tap', 23 | longpress: 'longpress', 24 | longtap: 'longtap', 25 | } 26 | export var MpHook = { 27 | data: 1, 28 | onLoad: 1, 29 | onShow: 1, 30 | onReady: 1, 31 | onPullDownRefresh: 1, 32 | onReachBottom: 1, 33 | onShareAppMessage: 1, 34 | onPageScroll: 1, 35 | onResize: 1, 36 | onTabItemTap: 1, 37 | onHide: 1, 38 | onUnload: 1, 39 | } 40 | export var TraceType = { 41 | DDTRACE: 'ddtrace', 42 | ZIPKIN_MULTI_HEADER: 'zipkin', 43 | ZIPKIN_SINGLE_HEADER: 'zipkin_single_header', 44 | W3C_TRACEPARENT: 'w3c_traceparent', 45 | SKYWALKING_V3: 'skywalking_v3', 46 | JAEGER: 'jaeger', 47 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { datafluxRum } from './boot/rum.entry' 2 | -------------------------------------------------------------------------------- /src/rumEventsCollection/action/actionCollection.js: -------------------------------------------------------------------------------- 1 | import { msToNs, extend2Lev } from '../../helper/utils' 2 | import { LifeCycleEventType } from '../../core/lifeCycle' 3 | import { RumEventType } from '../../helper/enums' 4 | import { trackActions } from './trackActions' 5 | 6 | export function startActionCollection(lifeCycle, configuration) { 7 | lifeCycle.subscribe( 8 | LifeCycleEventType.AUTO_ACTION_COMPLETED, 9 | function (action) { 10 | lifeCycle.notify( 11 | LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, 12 | processAction(action), 13 | ) 14 | }, 15 | ) 16 | if (configuration.trackInteractions) { 17 | trackActions(lifeCycle) 18 | } 19 | } 20 | 21 | function processAction(action) { 22 | var autoActionProperties = { 23 | action: { 24 | error: { 25 | count: action.counts.errorCount, 26 | }, 27 | id: action.id, 28 | loadingTime: msToNs(action.duration), 29 | long_task: { 30 | count: action.counts.longTaskCount, 31 | }, 32 | resource: { 33 | count: action.counts.resourceCount, 34 | }, 35 | }, 36 | } 37 | var actionEvent = extend2Lev( 38 | { 39 | action: { 40 | target: { 41 | name: action.name, 42 | }, 43 | type: action.type, 44 | }, 45 | date: action.startClocks, 46 | type: RumEventType.ACTION, 47 | }, 48 | autoActionProperties, 49 | ) 50 | return { 51 | rawRumEvent: actionEvent, 52 | startTime: action.startClocks, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/rumEventsCollection/action/trackActions.js: -------------------------------------------------------------------------------- 1 | import { elapsed, now, UUID, getMethods, isObject } from '../../helper/utils' 2 | import { LifeCycleEventType } from '../../core/lifeCycle' 3 | import { MinaTouch } from '../../core/miniaTouch' 4 | import { trackEventCounts } from '../trackEventCounts' 5 | import { waitIdlePageActivity } from '../trackPageActiveites' 6 | import { ActionType } from '../../helper/enums' 7 | export function trackActions(lifeCycle) { 8 | var action = startActionManagement(lifeCycle) 9 | 10 | // New views trigger the discard of the current pending Action 11 | lifeCycle.subscribe(LifeCycleEventType.VIEW_CREATED, function () { 12 | action.discardCurrent() 13 | }) 14 | var originPage = Page 15 | Page = function (page) { 16 | const methods = getMethods(page) 17 | methods.forEach((methodName) => { 18 | clickProxy( 19 | page, 20 | methodName, 21 | function (_action) { 22 | action.create(_action.type, _action.name) 23 | }, 24 | lifeCycle, 25 | ) 26 | }) 27 | return originPage(page) 28 | } 29 | var originComponent = Component 30 | Component = function (component) { 31 | const methods = getMethods(component) 32 | methods.forEach((methodName) => { 33 | clickProxy(component, methodName, function (_action) { 34 | action.create(_action.type, _action.name) 35 | }) 36 | }) 37 | return originComponent(component) 38 | } 39 | return { 40 | stop: function () { 41 | action.discardCurrent() 42 | // stopListener() 43 | }, 44 | } 45 | } 46 | function clickProxy(page, methodName, callback, lifeCycle) { 47 | var oirginMethod = page[methodName] 48 | 49 | page[methodName] = function () { 50 | const result = oirginMethod.apply(this, arguments) 51 | var action = {} 52 | if (isObject(arguments[0])) { 53 | var currentTarget = arguments[0].currentTarget || {} 54 | var dataset = currentTarget.dataset || {} 55 | var actionType = arguments[0].type 56 | if (actionType && ActionType[actionType]) { 57 | action.type = actionType 58 | action.name = dataset.name || dataset.content || dataset.type 59 | callback(action) 60 | lifeCycle.notify(LifeCycleEventType.PAGE_ALIAS_ACTION, true) 61 | } else if (methodName === 'onAddToFavorites') { 62 | action.type = 'click' 63 | action.name = 64 | '收藏 ' + 65 | '标题: ' + 66 | result.title + 67 | (result.query ? ' query: ' + result.query : '') 68 | callback(action) 69 | lifeCycle.notify(LifeCycleEventType.PAGE_ALIAS_ACTION, true) 70 | } else if (methodName === 'onShareAppMessage') { 71 | action.type = 'click' 72 | action.name = 73 | '转发 ' + 74 | '标题: ' + 75 | result.title + 76 | (result.path ? ' path: ' + result.path : '') 77 | callback(action) 78 | lifeCycle.notify(LifeCycleEventType.PAGE_ALIAS_ACTION, true) 79 | } else if (methodName === 'onShareTimeline') { 80 | action.type = 'click' 81 | action.name = 82 | '分享到朋友圈 ' + 83 | '标题: ' + 84 | result.title + 85 | (result.query ? ' query: ' + result.query : '') 86 | callback(action) 87 | lifeCycle.notify(LifeCycleEventType.PAGE_ALIAS_ACTION, true) 88 | } else if (methodName === 'onTabItemTap') { 89 | var item = arguments.length && arguments[0] 90 | action.type = 'click' 91 | action.name = 92 | 'tab ' + 93 | '名称: ' + 94 | item.text + 95 | (item.pagePath ? ' 跳转到: ' + item.pagePath : '') 96 | callback(action) 97 | lifeCycle.notify(LifeCycleEventType.PAGE_ALIAS_ACTION, true) 98 | } 99 | } 100 | return result 101 | } 102 | } 103 | function startActionManagement(lifeCycle) { 104 | var currentAction 105 | var currentIdlePageActivitySubscription 106 | 107 | return { 108 | create: function (type, name) { 109 | if (currentAction) { 110 | // Ignore any new action if another one is already occurring. 111 | return 112 | } 113 | var pendingAutoAction = new PendingAutoAction(lifeCycle, type, name) 114 | 115 | currentAction = pendingAutoAction 116 | currentIdlePageActivitySubscription = waitIdlePageActivity( 117 | lifeCycle, 118 | function (params) { 119 | if (params.hadActivity) { 120 | pendingAutoAction.complete(params.endTime) 121 | } else { 122 | pendingAutoAction.discard() 123 | } 124 | currentAction = undefined 125 | }, 126 | ) 127 | }, 128 | discardCurrent: function () { 129 | if (currentAction) { 130 | currentIdlePageActivitySubscription.stop() 131 | currentAction.discard() 132 | currentAction = undefined 133 | } 134 | }, 135 | } 136 | } 137 | var PendingAutoAction = function (lifeCycle, type, name) { 138 | this.id = UUID() 139 | this.startClocks = now() 140 | this.name = name 141 | this.type = type 142 | this.lifeCycle = lifeCycle 143 | this.eventCountsSubscription = trackEventCounts(lifeCycle) 144 | this.lifeCycle.notify(LifeCycleEventType.AUTO_ACTION_CREATED, { 145 | id: this.id, 146 | startClocks: this.startClocks, 147 | }) 148 | } 149 | PendingAutoAction.prototype = { 150 | complete: function (endTime) { 151 | var eventCounts = this.eventCountsSubscription.eventCounts 152 | this.lifeCycle.notify(LifeCycleEventType.AUTO_ACTION_COMPLETED, { 153 | counts: { 154 | errorCount: eventCounts.errorCount, 155 | longTaskCount: eventCounts.longTaskCount, 156 | resourceCount: eventCounts.resourceCount, 157 | }, 158 | duration: elapsed(this.startClocks, endTime), 159 | id: this.id, 160 | name: this.name, 161 | startClocks: this.startClocks, 162 | type: this.type, 163 | }) 164 | this.eventCountsSubscription.stop() 165 | }, 166 | discard: function () { 167 | this.lifeCycle.notify(LifeCycleEventType.AUTO_ACTION_DISCARDED) 168 | this.eventCountsSubscription.stop() 169 | }, 170 | } 171 | -------------------------------------------------------------------------------- /src/rumEventsCollection/app/appCollection.js: -------------------------------------------------------------------------------- 1 | import { rewriteApp } from './index' 2 | import { LifeCycleEventType } from '../../core/lifeCycle' 3 | import { RumEventType } from '../../helper/enums' 4 | import { msToNs } from '../../helper/utils' 5 | export function startAppCollection(lifeCycle, configuration) { 6 | lifeCycle.subscribe(LifeCycleEventType.APP_UPDATE, function (appinfo) { 7 | lifeCycle.notify( 8 | LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, 9 | processAppUpdate(appinfo), 10 | ) 11 | }) 12 | 13 | return rewriteApp(configuration, lifeCycle) 14 | } 15 | 16 | function processAppUpdate(appinfo) { 17 | var appEvent = { 18 | date: appinfo.startTime, 19 | type: RumEventType.APP, 20 | app: { 21 | type: appinfo.type, 22 | name: appinfo.name, 23 | id: appinfo.id, 24 | duration: msToNs(appinfo.duration), 25 | }, 26 | } 27 | console.log(appEvent, 'appEvent====') 28 | return { 29 | rawRumEvent: appEvent, 30 | startTime: appinfo.startTime, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/rumEventsCollection/app/index.js: -------------------------------------------------------------------------------- 1 | import { now, areInOrder, UUID } from '../../helper/utils' 2 | import { LifeCycleEventType } from '../../core/lifeCycle' 3 | 4 | // 劫持原小程序App方法 5 | export var THROTTLE_VIEW_UPDATE_PERIOD = 3000 6 | export const startupTypes = { 7 | COLD: 'cold', 8 | HOT: 'hot', 9 | } 10 | export function rewriteApp(configuration, lifeCycle) { 11 | const originApp = App 12 | var appInfo = { 13 | isStartUp: false, // 是否启动 14 | } 15 | var startTime 16 | App = function (app) { 17 | startTime = now() 18 | // 合并方法,插入记录脚本 19 | ;['onLaunch', 'onShow', 'onHide'].forEach((methodName) => { 20 | const userDefinedMethod = app[methodName] // 暂存用户定义的方法 21 | app[methodName] = function (options) { 22 | console.log(methodName, 'methodName app') 23 | if (methodName === 'onLaunch') { 24 | appInfo.isStartUp = true 25 | appInfo.isHide = false 26 | appInfo.startupType = startupTypes.COLD 27 | } else if (methodName === 'onShow') { 28 | if (appInfo.isStartUp && appInfo.isHide) { 29 | // 判断是热启动 30 | appInfo.startupType = startupTypes.HOT 31 | // appUpdate() 32 | } 33 | } else if (methodName === 'onHide') { 34 | lifeCycle.notify(LifeCycleEventType.APP_HIDE) 35 | appInfo.isHide = true 36 | } 37 | return userDefinedMethod && userDefinedMethod.call(this, options) 38 | } 39 | }) 40 | return originApp(app) 41 | } 42 | 43 | startPerformanceObservable(lifeCycle) 44 | } 45 | 46 | function startPerformanceObservable(lifeCycle) { 47 | var subscribe = lifeCycle.subscribe( 48 | LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, 49 | function (entitys) { 50 | // 过滤掉其他页面监听,只保留首次启动 51 | var codeDownloadDuration 52 | const launchEntity = entitys.find( 53 | (entity) => 54 | entity.entryType === 'navigation' && 55 | entity.navigationType === 'appLaunch', 56 | ) 57 | if (typeof launchEntity !== 'undefined') { 58 | lifeCycle.notify(LifeCycleEventType.APP_UPDATE, { 59 | startTime: launchEntity.startTime, 60 | name: '启动', 61 | type: 'launch', 62 | id: UUID(), 63 | duration: launchEntity.duration, 64 | }) 65 | } 66 | const scriptentity = entitys.find( 67 | (entity) => 68 | entity.entryType === 'script' && entity.name === 'evaluateScript', 69 | ) 70 | if (typeof scriptentity !== 'undefined') { 71 | lifeCycle.notify(LifeCycleEventType.APP_UPDATE, { 72 | startTime: scriptentity.startTime, 73 | name: '脚本注入', 74 | type: 'script_insert', 75 | id: UUID(), 76 | duration: scriptentity.duration, 77 | }) 78 | } 79 | const firstEntity = entitys.find( 80 | (entity) => 81 | entity.entryType === 'render' && entity.name === 'firstRender', 82 | ) 83 | if (firstEntity && scriptentity && launchEntity) { 84 | if ( 85 | !areInOrder(firstEntity.duration, launchEntity.duration) || 86 | !areInOrder(scriptentity.duration, launchEntity.duration) 87 | ) { 88 | return 89 | } 90 | codeDownloadDuration = 91 | launchEntity.duration - firstEntity.duration - scriptentity.duration 92 | // 资源下载耗时 93 | lifeCycle.notify(LifeCycleEventType.APP_UPDATE, { 94 | startTime: launchEntity.startTime, 95 | name: '小程序包下载', 96 | type: 'package_download', 97 | id: UUID(), 98 | duration: codeDownloadDuration, 99 | }) 100 | // 资源下载时间暂时定为:首次启动时间-脚本加载时间-初次渲染时间 101 | } 102 | }, 103 | ) 104 | return { 105 | stop: subscribe.unsubscribe, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/rumEventsCollection/assembly.js: -------------------------------------------------------------------------------- 1 | import { extend2Lev, withSnakeCaseKeys, performDraw, isEmptyObject } from '../helper/utils' 2 | import { LifeCycleEventType } from '../core/lifeCycle' 3 | import { RumEventType } from '../helper/enums' 4 | import baseInfo from '../core/baseInfo' 5 | function isTracked(configuration) { 6 | return performDraw(configuration.sampleRate) 7 | } 8 | var SessionType = { 9 | SYNTHETICS: 'synthetics', 10 | USER: 'user', 11 | } 12 | export function startRumAssembly( 13 | applicationId, 14 | configuration, 15 | lifeCycle, 16 | parentContexts, 17 | getCommonContext 18 | ) { 19 | lifeCycle.subscribe( 20 | LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, 21 | function (data) { 22 | var startTime = data.startTime 23 | var rawRumEvent = data.rawRumEvent 24 | var viewContext = parentContexts.findView(startTime) 25 | var savedCommonContext = data.savedGlobalContext 26 | var customerContext = data.customerContext 27 | var deviceContext = { 28 | device: baseInfo.deviceInfo, 29 | } 30 | if ( 31 | isTracked(configuration) && 32 | (viewContext || rawRumEvent.type === RumEventType.APP) 33 | ) { 34 | var actionContext = parentContexts.findAction(startTime) 35 | var commonContext = savedCommonContext || getCommonContext() 36 | var rumContext = { 37 | _dd: { 38 | sdkName: configuration.sdkName, 39 | sdkVersion: configuration.sdkVersion, 40 | env: configuration.env, 41 | version: configuration.version, 42 | }, 43 | tags: configuration.tags, 44 | application: { 45 | id: applicationId, 46 | }, 47 | device: {}, 48 | date: new Date().getTime(), 49 | session: { 50 | id: baseInfo.getSessionId(), 51 | type: SessionType.USER, 52 | }, 53 | user: { 54 | id: configuration.user_id || baseInfo.getClientID(), 55 | is_signin: configuration.user_id ? 'T' : 'F', 56 | }, 57 | } 58 | 59 | var rumEvent = extend2Lev( 60 | rumContext, 61 | deviceContext, 62 | viewContext, 63 | actionContext, 64 | rawRumEvent, 65 | ) 66 | 67 | var serverRumEvent = withSnakeCaseKeys(rumEvent) 68 | var context = extend2Lev(commonContext.context, customerContext) 69 | if (!isEmptyObject(context)) { 70 | serverRumEvent.tags = context 71 | } 72 | if (!isEmptyObject(commonContext.user)) { 73 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion 74 | serverRumEvent.user = extend2Lev( 75 | { 76 | id: baseInfo.getClientID(), 77 | is_signin: 'T' 78 | }, 79 | commonContext.user 80 | ) 81 | } 82 | lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, serverRumEvent) 83 | } 84 | }, 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /src/rumEventsCollection/error/errorCollection.js: -------------------------------------------------------------------------------- 1 | import { startAutomaticErrorCollection } from '../../core/errorCollection' 2 | import { RumEventType } from '../../helper/enums' 3 | import { LifeCycleEventType } from '../../core/lifeCycle' 4 | import { 5 | urlParse, 6 | replaceNumberCharByPath, 7 | getStatusGroup, 8 | } from '../../helper/utils' 9 | export function startErrorCollection(lifeCycle, configuration) { 10 | return doStartErrorCollection( 11 | lifeCycle, 12 | configuration, 13 | startAutomaticErrorCollection(configuration), 14 | ) 15 | } 16 | 17 | export function doStartErrorCollection(lifeCycle, configuration, observable) { 18 | observable.subscribe(function (error) { 19 | lifeCycle.notify( 20 | LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, 21 | processError(error), 22 | ) 23 | }) 24 | } 25 | 26 | function processError(error) { 27 | var resource = error.resource 28 | if (resource) { 29 | var urlObj = urlParse(error.resource.url).getParse() 30 | resource = { 31 | method: error.resource.method, 32 | status: error.resource.statusCode, 33 | statusGroup: getStatusGroup(error.resource.statusCode), 34 | url: error.resource.url, 35 | urlHost: urlObj.Host, 36 | urlPath: urlObj.Path, 37 | urlPathGroup: replaceNumberCharByPath(urlObj.Path), 38 | } 39 | } 40 | var rawRumEvent = { 41 | date: error.startTime, 42 | error: { 43 | message: error.message, 44 | resource: resource, 45 | source: error.source, 46 | stack: error.stack, 47 | type: error.type, 48 | starttime: error.startTime, 49 | }, 50 | type: RumEventType.ERROR, 51 | } 52 | return { 53 | rawRumEvent: rawRumEvent, 54 | startTime: error.startTime, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/rumEventsCollection/page/index.js: -------------------------------------------------------------------------------- 1 | import { extend, now, throttle, UUID, isNumber, getActivePage } from '../../helper/utils' 2 | import { trackEventCounts } from '../trackEventCounts' 3 | import { LifeCycleEventType } from '../../core/lifeCycle' 4 | // 劫持原小程序App方法 5 | export var THROTTLE_VIEW_UPDATE_PERIOD = 3000 6 | 7 | export function rewritePage(configuration, lifeCycle) { 8 | const originPage = Page 9 | console.log(originPage, 'originPage=====') 10 | Page = function (page) { 11 | // 合并方法,插入记录脚本 12 | var currentView, 13 | startTime = now() 14 | ;['onReady', 'onShow', 'onLoad', 'onUnload', 'onHide'].forEach( 15 | (methodName) => { 16 | const userDefinedMethod = page[methodName] 17 | page[methodName] = function () { 18 | console.log(methodName, 'methodName page') 19 | if (methodName === 'onShow' || methodName === 'onLoad') { 20 | if (typeof currentView === 'undefined') { 21 | const activePage = getActivePage() 22 | currentView = newView( 23 | lifeCycle, 24 | activePage && activePage.route, 25 | startTime, 26 | ) 27 | } 28 | } 29 | 30 | currentView && currentView.setLoadEventEnd(methodName) 31 | 32 | if ( 33 | (methodName === 'onUnload' || 34 | methodName === 'onHide' || 35 | methodName === 'onShow') && 36 | currentView 37 | ) { 38 | currentView.triggerUpdate() 39 | if (methodName === 'onUnload' || methodName === 'onHide') { 40 | currentView.end() 41 | } 42 | } 43 | return userDefinedMethod && userDefinedMethod.apply(this, arguments) 44 | } 45 | }, 46 | ) 47 | return originPage(page) 48 | } 49 | } 50 | function newView(lifeCycle, route, startTime) { 51 | if (typeof startTime === 'undefined') { 52 | startTime = now() 53 | } 54 | var id = UUID() 55 | var isActive = true 56 | var eventCounts = { 57 | errorCount: 0, 58 | resourceCount: 0, 59 | userActionCount: 0, 60 | } 61 | var setdataCount = 0 62 | 63 | var documentVersion = 0 64 | var setdataDuration = 0 65 | var loadingDuration = 0 66 | var loadingTime 67 | var showTime 68 | var onload2onshowTime 69 | var onshow2onready 70 | var stayTime 71 | var fpt, fmp 72 | lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, { 73 | id, 74 | startTime, 75 | route, 76 | }) 77 | var scheduleViewUpdate = throttle( 78 | triggerViewUpdate, 79 | THROTTLE_VIEW_UPDATE_PERIOD, 80 | { 81 | leading: false, 82 | }, 83 | ) 84 | var cancelScheduleViewUpdate = scheduleViewUpdate.cancel 85 | var _trackEventCounts = trackEventCounts( 86 | lifeCycle, 87 | function (newEventCounts) { 88 | eventCounts = newEventCounts 89 | scheduleViewUpdate() 90 | }, 91 | ) 92 | var stopEventCountsTracking = _trackEventCounts.stop 93 | var _trackFptTime = trackFptTime(lifeCycle, function (duration) { 94 | fpt = duration 95 | scheduleViewUpdate() 96 | }) 97 | var stopFptTracking = _trackFptTime.stop 98 | var _trackSetDataTime = trackSetDataTime(lifeCycle, function (duration) { 99 | if (isNumber(duration)) { 100 | setdataDuration += duration 101 | setdataCount++ 102 | scheduleViewUpdate() 103 | } 104 | }) 105 | var stopSetDataTracking = _trackSetDataTime.stop 106 | var _trackLoadingTime = trackLoadingTime(lifeCycle, function (duration) { 107 | if (isNumber(duration)) { 108 | loadingDuration = duration 109 | scheduleViewUpdate() 110 | } 111 | }) 112 | var stopLoadingTimeTracking = _trackLoadingTime.stop 113 | 114 | var setLoadEventEnd = function (type) { 115 | if (type === 'onLoad') { 116 | loadingTime = now() 117 | } else if (type === 'onShow') { 118 | showTime = now() 119 | if ( 120 | typeof onload2onshowTime === 'undefined' && 121 | typeof loadingTime !== 'undefined' 122 | ) { 123 | onload2onshowTime = showTime - loadingTime 124 | } 125 | } else if (type === 'onReady') { 126 | if ( 127 | typeof onshow2onready === 'undefined' && 128 | typeof showTime !== 'undefined' 129 | ) { 130 | onshow2onready = now() - showTime 131 | } 132 | if (typeof fmp === 'undefined') { 133 | fmp = now() - startTime // 从开发者角度看,小程序首屏渲染完成的标志是首页 Page.onReady 事件触发。 134 | } 135 | } else if (type === 'onHide' || type === 'onUnload') { 136 | if (typeof showTime !== 'undefined') { 137 | stayTime = now() - showTime 138 | } 139 | isActive = false 140 | } 141 | triggerViewUpdate() 142 | } 143 | function triggerViewUpdate() { 144 | documentVersion += 1 145 | lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, { 146 | documentVersion: documentVersion, 147 | eventCounts: eventCounts, 148 | id: id, 149 | loadingTime: loadingDuration, 150 | stayTime, 151 | onload2onshowTime, 152 | onshow2onready, 153 | setdataDuration, 154 | setdataCount, 155 | fmp, 156 | fpt, 157 | startTime: startTime, 158 | route: route, 159 | duration: now() - startTime, 160 | isActive: isActive, 161 | }) 162 | } 163 | return { 164 | scheduleUpdate: scheduleViewUpdate, 165 | setLoadEventEnd, 166 | triggerUpdate: function () { 167 | cancelScheduleViewUpdate() 168 | triggerViewUpdate() 169 | }, 170 | end: function () { 171 | stopEventCountsTracking() 172 | stopFptTracking() 173 | cancelScheduleViewUpdate() 174 | stopSetDataTracking() 175 | stopLoadingTimeTracking() 176 | lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks: now() }) 177 | }, 178 | } 179 | } 180 | function trackFptTime(lifeCycle, callback) { 181 | var subscribe = lifeCycle.subscribe( 182 | LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, 183 | function (entitys) { 184 | const firstRenderEntity = entitys.find( 185 | (entity) => 186 | entity.entryType === 'render' && entity.name === 'firstRender', 187 | ) 188 | 189 | if (typeof firstRenderEntity !== 'undefined') { 190 | callback(firstRenderEntity.duration) 191 | } 192 | }, 193 | ) 194 | return { 195 | stop: subscribe.unsubscribe, 196 | } 197 | } 198 | function trackLoadingTime(lifeCycle, callback) { 199 | var subscribe = lifeCycle.subscribe( 200 | LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, 201 | function (entitys) { 202 | const navigationEnity = entitys.find( 203 | (entity) => entity.entryType === 'navigation', 204 | ) 205 | if (typeof navigationEnity !== 'undefined') { 206 | callback(navigationEnity.duration) 207 | } 208 | }, 209 | ) 210 | return { 211 | stop: subscribe.unsubscribe, 212 | } 213 | } 214 | function trackSetDataTime(lifeCycle, callback) { 215 | var subscribe = lifeCycle.subscribe( 216 | LifeCycleEventType.PAGE_SET_DATA_UPDATE, 217 | function (data) { 218 | if (!data) return 219 | callback(data.updateEndTimestamp - data.pendingStartTimestamp) 220 | }, 221 | ) 222 | return { 223 | stop: subscribe.unsubscribe, 224 | } 225 | } 226 | // function getActivePage() { 227 | // const curPages = getCurrentPages() 228 | // if (curPages.length) { 229 | // return curPages[curPages.length - 1] 230 | // } 231 | // return {} 232 | // } 233 | -------------------------------------------------------------------------------- /src/rumEventsCollection/page/viewCollection.js: -------------------------------------------------------------------------------- 1 | import { rewritePage } from './index' 2 | import { RumEventType } from '../../helper/enums' 3 | import { msToNs } from '../../helper/utils' 4 | import { LifeCycleEventType } from '../../core/lifeCycle' 5 | export function startViewCollection(lifeCycle, configuration) { 6 | lifeCycle.subscribe(LifeCycleEventType.VIEW_UPDATED, function (view) { 7 | lifeCycle.notify( 8 | LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, 9 | processViewUpdate(view), 10 | ) 11 | }) 12 | 13 | return rewritePage(configuration, lifeCycle) 14 | } 15 | function processViewUpdate(view) { 16 | var apdexLevel 17 | if (view.fmp) { 18 | apdexLevel = parseInt(Number(view.fmp) / 1000) 19 | apdexLevel = apdexLevel > 9 ? 9 : apdexLevel 20 | } 21 | var viewEvent = { 22 | _dd: { 23 | documentVersion: view.documentVersion, 24 | }, 25 | date: view.startTime, 26 | type: RumEventType.VIEW, 27 | page: { 28 | action: { 29 | count: view.eventCounts.userActionCount, 30 | }, 31 | error: { 32 | count: view.eventCounts.errorCount, 33 | }, 34 | setdata: { 35 | count: view.setdataCount, 36 | }, 37 | setdata_duration: msToNs(view.setdataDuration), 38 | loadingTime: msToNs(view.loadingTime), 39 | stayTime: msToNs(view.stayTime), 40 | onload2onshow: msToNs(view.onload2onshowTime), 41 | onshow2onready: msToNs(view.onshow2onready), 42 | fpt: msToNs(view.fpt), 43 | fmp: msToNs(view.fmp), 44 | isActive: view.isActive, 45 | apdexLevel, 46 | // longTask: { 47 | // count: view.eventCounts.longTaskCount 48 | // }, 49 | resource: { 50 | count: view.eventCounts.resourceCount, 51 | }, 52 | timeSpent: msToNs(view.duration), 53 | }, 54 | } 55 | return { 56 | rawRumEvent: viewEvent, 57 | startTime: view.startTime, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/rumEventsCollection/parentContexts.js: -------------------------------------------------------------------------------- 1 | import { ONE_MINUTE, ONE_HOUR } from '../helper/enums' 2 | import { each, now } from '../helper/utils' 3 | import { LifeCycleEventType } from '../core/lifeCycle' 4 | export var VIEW_CONTEXT_TIME_OUT_DELAY = 4 * ONE_HOUR 5 | export var CLEAR_OLD_CONTEXTS_INTERVAL = ONE_MINUTE 6 | 7 | export function startParentContexts(lifeCycle) { 8 | var currentView 9 | var currentAction 10 | var previousViews = [] 11 | var previousActions = [] 12 | lifeCycle.subscribe( 13 | LifeCycleEventType.VIEW_CREATED, 14 | function (currentContext) { 15 | currentView = currentContext 16 | }, 17 | ) 18 | 19 | lifeCycle.subscribe( 20 | LifeCycleEventType.VIEW_UPDATED, 21 | function (currentContext) { 22 | // A view can be updated after its end. We have to ensure that the view being updated is the 23 | // most recently created. 24 | if (currentView && currentView.id === currentContext.id) { 25 | currentView = currentContext 26 | } 27 | }, 28 | ) 29 | lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, function (data) { 30 | if (currentView) { 31 | previousViews.unshift({ 32 | endTime: data.endClocks, 33 | context: buildCurrentViewContext(), 34 | startTime: currentView.startTime, 35 | }) 36 | currentView = undefined 37 | } 38 | }) 39 | lifeCycle.subscribe( 40 | LifeCycleEventType.AUTO_ACTION_CREATED, 41 | function (currentContext) { 42 | currentAction = currentContext 43 | }, 44 | ) 45 | 46 | lifeCycle.subscribe( 47 | LifeCycleEventType.AUTO_ACTION_COMPLETED, 48 | function (action) { 49 | if (currentAction) { 50 | previousActions.unshift({ 51 | context: buildCurrentActionContext(), 52 | endTime: currentAction.startClocks + action.duration, 53 | startTime: currentAction.startClocks, 54 | }) 55 | } 56 | currentAction = undefined 57 | }, 58 | ) 59 | 60 | lifeCycle.subscribe(LifeCycleEventType.AUTO_ACTION_DISCARDED, function () { 61 | currentAction = undefined 62 | }) 63 | lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, function () { 64 | previousViews = [] 65 | previousActions = [] 66 | currentView = undefined 67 | currentAction = undefined 68 | }) 69 | var clearOldContextsInterval = setInterval(function () { 70 | clearOldContexts(previousViews, VIEW_CONTEXT_TIME_OUT_DELAY) 71 | }, CLEAR_OLD_CONTEXTS_INTERVAL) 72 | 73 | function clearOldContexts(previousContexts, timeOutDelay) { 74 | var oldTimeThreshold = now() - timeOutDelay 75 | while ( 76 | previousContexts.length > 0 && 77 | previousContexts[previousContexts.length - 1].startTime < oldTimeThreshold 78 | ) { 79 | previousContexts.pop() 80 | } 81 | } 82 | function buildCurrentActionContext() { 83 | return { userAction: { id: currentAction.id } } 84 | } 85 | function buildCurrentViewContext() { 86 | return { 87 | page: { 88 | id: currentView.id, 89 | referer: 90 | (previousViews.length && 91 | previousViews[previousViews.length - 1].context.page.route) || 92 | undefined, 93 | route: currentView.route, 94 | }, 95 | } 96 | } 97 | 98 | function findContext( 99 | buildContext, 100 | previousContexts, 101 | currentContext, 102 | startTime, 103 | ) { 104 | if (startTime === undefined) { 105 | return currentContext ? buildContext() : undefined 106 | } 107 | if (currentContext && startTime >= currentContext.startTime) { 108 | return buildContext() 109 | } 110 | var flag = undefined 111 | each(previousContexts, function (previousContext) { 112 | if (startTime > previousContext.endTime) { 113 | return false 114 | } 115 | if (startTime >= previousContext.startTime) { 116 | flag = previousContext.context 117 | return false 118 | } 119 | }) 120 | 121 | return flag 122 | } 123 | 124 | var parentContexts = { 125 | findView: function (startTime) { 126 | return findContext( 127 | buildCurrentViewContext, 128 | previousViews, 129 | currentView, 130 | startTime, 131 | ) 132 | }, 133 | findAction: function (startTime) { 134 | return findContext( 135 | buildCurrentActionContext, 136 | previousActions, 137 | currentAction, 138 | startTime, 139 | ) 140 | }, 141 | 142 | stop: function () { 143 | clearInterval(clearOldContextsInterval) 144 | }, 145 | } 146 | return parentContexts 147 | } 148 | -------------------------------------------------------------------------------- /src/rumEventsCollection/performanceCollection.js: -------------------------------------------------------------------------------- 1 | import { LifeCycleEventType } from '../core/lifeCycle' 2 | import { sdk } from '../core/sdk' 3 | export function startPagePerformanceObservable(lifeCycle, configuration) { 4 | if (!!sdk.getPerformance) { 5 | const performance = sdk.getPerformance() 6 | const observer = performance.createObserver((entryList) => { 7 | lifeCycle.notify( 8 | LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, 9 | entryList.getEntries(), 10 | ) 11 | }) 12 | observer.observe({ entryTypes: ['render', 'script', 'navigation'] }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/rumEventsCollection/requestCollection.js: -------------------------------------------------------------------------------- 1 | import { startXhrProxy } from '../core/xhrProxy' 2 | import { startDownloadProxy } from '../core/downloadProxy' 3 | import { LifeCycleEventType } from '../core/lifeCycle' 4 | import { isObject } from '../helper/utils' 5 | import { isAllowedRequestUrl } from '../rumEventsCollection/resource/resourceUtils' 6 | import {startTracer} from '../rumEventsCollection/tracing/tracer' 7 | var nextRequestIndex = 1 8 | 9 | export function startRequestCollection(lifeCycle, configuration) { 10 | var tracer = startTracer(configuration) 11 | trackXhr(lifeCycle, configuration,tracer) 12 | trackDownload(lifeCycle, configuration) 13 | } 14 | function parseHeader(header) { 15 | // 大小写兼容 16 | if (!isObject(header)) return header 17 | var res = {} 18 | Object.keys(header).forEach(function (key) { 19 | res[key.toLowerCase()] = header[key] 20 | }) 21 | return res 22 | } 23 | function getHeaderString(header) { 24 | if (!isObject(header)) return header 25 | var headerStr = '' 26 | Object.keys(header).forEach(function (key) { 27 | headerStr += key + ':' + header[key] + ';' 28 | }) 29 | return headerStr 30 | } 31 | export function trackXhr(lifeCycle, configuration,tracer) { 32 | var xhrProxy = startXhrProxy() 33 | xhrProxy.beforeSend(function (context) { 34 | if (isAllowedRequestUrl(configuration, context.url)) { 35 | tracer.traceXhr(context) 36 | context.requestIndex = getNextRequestIndex() 37 | lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, { 38 | requestIndex: context.requestIndex, 39 | }) 40 | } 41 | }) 42 | xhrProxy.onRequestComplete(function (context) { 43 | if (isAllowedRequestUrl(configuration, context.url)) { 44 | tracer.clearTracingIfCancelled(context) 45 | lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, { 46 | duration: context.duration, 47 | method: context.method, 48 | requestIndex: context.requestIndex, 49 | performance: context.profile, 50 | response: context.response, 51 | startTime: context.startTime, 52 | status: context.status, 53 | traceId: context.traceId, 54 | spanId: context.spanId, 55 | type: context.type, 56 | url: context.url, 57 | }) 58 | } 59 | }) 60 | return xhrProxy 61 | } 62 | export function trackDownload(lifeCycle, configuration) { 63 | var dwonloadProxy = startDownloadProxy() 64 | dwonloadProxy.beforeSend(function (context) { 65 | if (isAllowedRequestUrl(configuration, context.url)) { 66 | context.requestIndex = getNextRequestIndex() 67 | lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, { 68 | requestIndex: context.requestIndex, 69 | }) 70 | } 71 | }) 72 | dwonloadProxy.onRequestComplete(function (context) { 73 | if (isAllowedRequestUrl(configuration, context.url)) { 74 | lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, { 75 | duration: context.duration, 76 | method: context.method, 77 | requestIndex: context.requestIndex, 78 | performance: context.profile, 79 | response: context.response, 80 | startTime: context.startTime, 81 | status: context.status, 82 | type: context.type, 83 | url: context.url, 84 | }) 85 | } 86 | }) 87 | return dwonloadProxy 88 | } 89 | function getNextRequestIndex() { 90 | var result = nextRequestIndex 91 | nextRequestIndex += 1 92 | return result 93 | } 94 | -------------------------------------------------------------------------------- /src/rumEventsCollection/resource/resourceCollection.js: -------------------------------------------------------------------------------- 1 | import { 2 | computePerformanceResourceDuration, 3 | computePerformanceResourceDetails, 4 | computeSize, 5 | } from './resourceUtils' 6 | import { LifeCycleEventType } from '../../core/lifeCycle' 7 | import { 8 | msToNs, 9 | extend2Lev, 10 | urlParse, 11 | getQueryParamsFromUrl, 12 | replaceNumberCharByPath, 13 | jsonStringify, 14 | getStatusGroup, 15 | UUID 16 | } from '../../helper/utils' 17 | import { RumEventType } from '../../helper/enums' 18 | export function startResourceCollection(lifeCycle, configuration) { 19 | lifeCycle.subscribe(LifeCycleEventType.REQUEST_COMPLETED, function (request) { 20 | lifeCycle.notify( 21 | LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, 22 | processRequest(request), 23 | ) 24 | }) 25 | } 26 | 27 | function processRequest(request) { 28 | var type = request.type 29 | var timing = request.performance 30 | var correspondingTimingOverrides = timing 31 | ? computePerformanceEntryMetrics(timing) 32 | : undefined 33 | var tracingInfo = computeRequestTracingInfo(request) 34 | var urlObj = urlParse(request.url).getParse() 35 | var startTime = request.startTime 36 | var resourceEvent = extend2Lev( 37 | { 38 | date: startTime, 39 | resource: { 40 | type: type, 41 | duration: msToNs(request.duration), 42 | method: request.method, 43 | status: request.status, 44 | statusGroup: getStatusGroup(request.status), 45 | url: request.url, 46 | urlHost: urlObj.Host, 47 | urlPath: urlObj.Path, 48 | urlPathGroup: replaceNumberCharByPath(urlObj.Path), 49 | urlQuery: jsonStringify(getQueryParamsFromUrl(request.url)), 50 | }, 51 | type: RumEventType.RESOURCE, 52 | }, 53 | tracingInfo, 54 | correspondingTimingOverrides, 55 | ) 56 | return { startTime: startTime, rawRumEvent: resourceEvent } 57 | } 58 | function computeRequestTracingInfo(request) { 59 | var hasBeenTraced = request.traceId && request.spanId 60 | if (!hasBeenTraced) { 61 | return undefined 62 | } 63 | return { 64 | _dd: { 65 | spanId: request.spanId, 66 | traceId: request.traceId 67 | }, 68 | resource: { id: UUID() } 69 | } 70 | } 71 | function computePerformanceEntryMetrics(timing) { 72 | return { 73 | resource: extend2Lev( 74 | {}, 75 | { 76 | load: computePerformanceResourceDuration(timing), 77 | size: computeSize(timing), 78 | }, 79 | computePerformanceResourceDetails(timing), 80 | ), 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/rumEventsCollection/setDataCollection.js: -------------------------------------------------------------------------------- 1 | import { LifeCycleEventType } from '../core/lifeCycle' 2 | 3 | export function startSetDataColloction(lifeCycle) { 4 | const originPage = Page 5 | const originComponent = Component 6 | Page = function (page) { 7 | const originPageOnLoad = page['onLoad'] 8 | page['onLoad'] = function () { 9 | this.setUpdatePerformanceListener && 10 | this.setUpdatePerformanceListener({ withDataPaths: true }, (res) => { 11 | lifeCycle.notify(LifeCycleEventType.PAGE_SET_DATA_UPDATE, res) 12 | }) 13 | return originPageOnLoad && originPageOnLoad.apply(this, arguments) 14 | } 15 | return originPage(page) 16 | } 17 | Component = function (component) { 18 | let originComponentAttached 19 | if (component.lifetimes) { 20 | originComponentAttached = component.lifetimes['attached'] 21 | } else { 22 | // 兼容老版本 23 | originComponentAttached = component['attached'] 24 | } 25 | component['attached'] = function () { 26 | this.setUpdatePerformanceListener && 27 | this.setUpdatePerformanceListener({ withDataPaths: true }, (res) => { 28 | lifeCycle.notify(LifeCycleEventType.PAGE_SET_DATA_UPDATE, res) 29 | }) 30 | return originComponentAttached && originComponentAttached.apply(this, arguments) 31 | } 32 | return originComponent(component) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/rumEventsCollection/tracing/ddtraceTracer.js: -------------------------------------------------------------------------------- 1 | // === Generate a random 64-bit number in fixed-length hex format 2 | function randomTraceId() { 3 | const digits = '0123456789abcdef'; 4 | let n = ''; 5 | for (let i = 0; i < 19; i += 1) { 6 | const rand = Math.floor(Math.random() * 10); 7 | n += digits[rand]; 8 | } 9 | return n; 10 | } 11 | /** 12 | * 13 | * @param {*} configuration 配置信息 14 | */ 15 | export function DDtraceTracer(configuration) { 16 | this._spanId = randomTraceId() 17 | this._traceId = randomTraceId() 18 | } 19 | DDtraceTracer.prototype = { 20 | isTracingSupported: function() { 21 | return true 22 | }, 23 | getSpanId:function() { 24 | return this._spanId 25 | }, 26 | getTraceId: function() { 27 | return this._traceId 28 | }, 29 | makeTracingHeaders: function() { 30 | return { 31 | 'x-datadog-origin': 'rum', 32 | // 'x-datadog-parent-id': spanId.toDecimalString(), 33 | 'x-datadog-sampled': '1', 34 | 'x-datadog-sampling-priority': '1', 35 | 'x-datadog-trace-id': this.getTraceId() 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/rumEventsCollection/tracing/jaegerTracer.js: -------------------------------------------------------------------------------- 1 | // === Generate a random 64-bit number in fixed-length hex format 2 | function randomTraceId() { 3 | const digits = '0123456789abcdef'; 4 | let n = ''; 5 | for (let i = 0; i < 16; i += 1) { 6 | const rand = Math.floor(Math.random() * 16); 7 | n += digits[rand]; 8 | } 9 | return n; 10 | } 11 | 12 | /** 13 | * 14 | * @param {*} configuration 配置信息 15 | */ 16 | export function JaegerTracer(configuration) { 17 | const rootSpanId = randomTraceId(); 18 | // this._traceId = randomTraceId() + rootSpanId // 默认用128bit,兼容其他配置 19 | if (configuration.traceId128Bit) { 20 | // 128bit生成traceid 21 | this._traceId = randomTraceId() + rootSpanId 22 | } else { 23 | this._traceId = rootSpanId 24 | } 25 | this._spanId = rootSpanId 26 | } 27 | JaegerTracer.prototype = { 28 | isTracingSupported: function() { 29 | return true 30 | }, 31 | getSpanId:function() { 32 | return this._spanId 33 | }, 34 | getTraceId: function() { 35 | return this._traceId 36 | }, 37 | getUberTraceId: function() { 38 | //{trace-id}:{span-id}:{parent-span-id}:{flags} 39 | return this._traceId + ':' + this._spanId + ':' + '0' + ':' + '1' 40 | }, 41 | makeTracingHeaders: function() { 42 | return { 43 | 'uber-trace-id': this.getUberTraceId(), 44 | } 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /src/rumEventsCollection/tracing/skywalkingTracer.js: -------------------------------------------------------------------------------- 1 | import {base64Encode, urlParse , getActivePage} from '../../helper/utils' 2 | // start SkyWalking 3 | function uuid() { 4 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { 5 | /* tslint:disable */ 6 | const r = (Math.random() * 16) | 0; 7 | /* tslint:disable */ 8 | const v = c === 'x' ? r : (r & 0x3) | 0x8; 9 | 10 | return v.toString(16); 11 | }); 12 | } 13 | 14 | /** 15 | * 16 | * @param {*} configuration 配置信息 17 | * @param {*} requestUrl 请求的url 18 | */ 19 | export function SkyWalkingTracer(configuration, requestUrl) { 20 | this._spanId = uuid() 21 | this._traceId = uuid() 22 | this._applicationId = configuration.applicationId 23 | this._env = configuration.env 24 | this._version = configuration.version 25 | this._urlParse = urlParse(requestUrl).getParse() 26 | } 27 | SkyWalkingTracer.prototype = { 28 | isTracingSupported: function() { 29 | if (this._env && this._version && this._urlParse) return true 30 | return false 31 | }, 32 | getSpanId:function() { 33 | return this._spanId 34 | }, 35 | getTraceId: function() { 36 | return this._traceId 37 | }, 38 | getSkyWalkingSw8:function() { 39 | try { 40 | var traceIdStr = String(base64Encode(this._traceId)); 41 | var segmentId = String(base64Encode(this._spanId)); 42 | var service = String(base64Encode(this._applicationId + '_rum_' + this.env)); 43 | var instance = String(base64Encode(this._version)); 44 | var activePage = getActivePage() 45 | var endpointPage = '' 46 | if (activePage && activePage.route) { 47 | endpointPage = activePage.route 48 | } 49 | var endpoint = String(base64Encode(endpointPage)); 50 | var peer = String(base64Encode(this._urlParse.Host)); 51 | var index = '0' 52 | // var values = `${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`; 53 | return '1-' + traceIdStr + '-'+ segmentId + '-' +index + '-'+ service + '-'+ instance + '-'+ endpoint + '-'+ peer 54 | } catch(err) { 55 | return '' 56 | } 57 | }, 58 | makeTracingHeaders: function() { 59 | return { 60 | 'sw8': this.getSkyWalkingSw8(), 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/rumEventsCollection/tracing/tracer.js: -------------------------------------------------------------------------------- 1 | 2 | import {each, extend, getOrigin} from '../../helper/utils' 3 | import {TraceType} from '../../helper/enums' 4 | import { DDtraceTracer} from './ddtraceTracer' 5 | import { SkyWalkingTracer} from './skywalkingTracer' 6 | import { JaegerTracer} from './jaegerTracer' 7 | import { ZipkinSingleTracer} from './zipkinSingleTracer' 8 | import { ZipkinMultiTracer} from './zipkinMultiTracer' 9 | import { W3cTraceParentTracer} from './w3cTraceParentTracer' 10 | 11 | export function clearTracingIfCancelled(context) { 12 | if (context.status === 0) { 13 | context.traceId = undefined 14 | context.spanId = undefined 15 | } 16 | } 17 | 18 | export function startTracer(configuration) { 19 | return { 20 | clearTracingIfCancelled: clearTracingIfCancelled, 21 | traceXhr: function (context) { 22 | return injectHeadersIfTracingAllowed( 23 | configuration, 24 | context, 25 | function (tracingHeaders) { 26 | context.option = extend({}, context.option) 27 | var header = {} 28 | if (context.option.header) { 29 | each(context.option.header, function (value, key) { 30 | header[key] = value 31 | }) 32 | } 33 | context.option.header = extend(header, tracingHeaders) 34 | } 35 | ) 36 | } 37 | } 38 | } 39 | function isAllowedUrl(configuration, requestUrl) { 40 | var requestOrigin = getOrigin(requestUrl) 41 | var flag = false 42 | each(configuration.allowedTracingOrigins, function (allowedOrigin) { 43 | if ( 44 | requestOrigin === allowedOrigin || 45 | (allowedOrigin instanceof RegExp && allowedOrigin.test(requestOrigin)) 46 | ) { 47 | flag = true 48 | return false 49 | } 50 | }) 51 | return flag 52 | } 53 | 54 | export function injectHeadersIfTracingAllowed(configuration, context, inject) { 55 | if (!isAllowedUrl(configuration, context.url) || !configuration.traceType) { 56 | return 57 | } 58 | var tracer; 59 | switch(configuration.traceType) { 60 | case TraceType.DDTRACE: 61 | tracer = new DDtraceTracer(); 62 | break; 63 | case TraceType.SKYWALKING_V3: 64 | tracer = new SkyWalkingTracer(configuration, context.url); 65 | break; 66 | case TraceType.ZIPKIN_MULTI_HEADER: 67 | tracer = new ZipkinMultiTracer(configuration); 68 | break; 69 | case TraceType.JAEGER: 70 | tracer = new JaegerTracer(configuration); 71 | break; 72 | case TraceType.W3C_TRACEPARENT: 73 | tracer = new W3cTraceParentTracer(configuration); 74 | break; 75 | case TraceType.ZIPKIN_SINGLE_HEADER: 76 | tracer = new ZipkinSingleTracer(configuration); 77 | break; 78 | default: 79 | break; 80 | } 81 | if (!tracer || !tracer.isTracingSupported()) { 82 | return 83 | } 84 | 85 | context.traceId = tracer.getTraceId() 86 | context.spanId = tracer.getSpanId() 87 | inject(tracer.makeTracingHeaders()) 88 | } 89 | -------------------------------------------------------------------------------- /src/rumEventsCollection/tracing/w3cTraceParentTracer.js: -------------------------------------------------------------------------------- 1 | // === Generate a random 64-bit number in fixed-length hex format 2 | function randomTraceId() { 3 | const digits = '0123456789abcdef'; 4 | let n = ''; 5 | for (let i = 0; i < 16; i += 1) { 6 | const rand = Math.floor(Math.random() * 16); 7 | n += digits[rand]; 8 | } 9 | return n; 10 | } 11 | 12 | /** 13 | * 14 | * @param {*} configuration 配置信息 15 | */ 16 | export function W3cTraceParentTracer(configuration) { 17 | const rootSpanId = randomTraceId(); 18 | this._traceId = randomTraceId() + rootSpanId 19 | this._spanId = rootSpanId 20 | } 21 | W3cTraceParentTracer.prototype = { 22 | isTracingSupported: function() { 23 | return true 24 | }, 25 | getSpanId:function() { 26 | return this._spanId 27 | }, 28 | getTraceId: function() { 29 | return this._traceId 30 | }, 31 | getTraceParent: function() { 32 | // '{version}-{traceId}-{spanId}-{sampleDecision}' 33 | return '00-' + this._traceId + '-' + this._spanId + '-01' 34 | }, 35 | makeTracingHeaders: function() { 36 | return { 37 | 'traceparent': this.getTraceParent() 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/rumEventsCollection/tracing/zipkinMultiTracer.js: -------------------------------------------------------------------------------- 1 | // === Generate a random 64-bit number in fixed-length hex format 2 | function randomTraceId() { 3 | const digits = '0123456789abcdef'; 4 | let n = ''; 5 | for (let i = 0; i < 16; i += 1) { 6 | const rand = Math.floor(Math.random() * 16); 7 | n += digits[rand]; 8 | } 9 | return n; 10 | } 11 | 12 | /** 13 | * 14 | * @param {*} configuration 配置信息 15 | */ 16 | export function ZipkinMultiTracer(configuration) { 17 | const rootSpanId = randomTraceId(); 18 | if (configuration.traceId128Bit) { 19 | // 128bit生成traceid 20 | this._traceId = randomTraceId() + rootSpanId 21 | } else { 22 | this._traceId = rootSpanId 23 | } 24 | this._spanId = rootSpanId 25 | } 26 | ZipkinMultiTracer.prototype = { 27 | isTracingSupported: function() { 28 | return true 29 | }, 30 | getSpanId:function() { 31 | return this._spanId 32 | }, 33 | getTraceId: function() { 34 | return this._traceId 35 | }, 36 | 37 | makeTracingHeaders: function() { 38 | return { 39 | 'X-B3-TraceId': this.getTraceId(), 40 | 'X-B3-SpanId': this.getSpanId(), 41 | // 'X-B3-ParentSpanId': '', 42 | 'X-B3-Sampled': '1', 43 | // 'X-B3-Flags': '0' 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/rumEventsCollection/tracing/zipkinSingleTracer.js: -------------------------------------------------------------------------------- 1 | // === Generate a random 64-bit number in fixed-length hex format 2 | function randomTraceId() { 3 | const digits = '0123456789abcdef'; 4 | let n = ''; 5 | for (let i = 0; i < 16; i += 1) { 6 | const rand = Math.floor(Math.random() * 16); 7 | n += digits[rand]; 8 | } 9 | return n; 10 | } 11 | 12 | /** 13 | * 14 | * @param {*} configuration 配置信息 15 | */ 16 | export function ZipkinSingleTracer(configuration) { 17 | const rootSpanId = randomTraceId(); 18 | this._traceId = randomTraceId() + rootSpanId 19 | this._spanId = rootSpanId 20 | } 21 | ZipkinSingleTracer.prototype = { 22 | isTracingSupported: function() { 23 | return true 24 | }, 25 | getSpanId:function() { 26 | return this._spanId 27 | }, 28 | getTraceId: function() { 29 | return this._traceId 30 | }, 31 | getB3Str: function() { 32 | //{TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} 33 | return this._traceId + '-' + this._spanId + '-1' 34 | }, 35 | makeTracingHeaders: function() { 36 | return { 37 | 'b3': this.getB3Str() 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/rumEventsCollection/trackEventCounts.js: -------------------------------------------------------------------------------- 1 | import { noop } from '../helper/utils' 2 | import { RumEventType } from '../helper/enums' 3 | import { LifeCycleEventType } from '../core/lifeCycle' 4 | 5 | export function trackEventCounts(lifeCycle, callback) { 6 | if (typeof callback === 'undefined') { 7 | callback = noop 8 | } 9 | var eventCounts = { 10 | errorCount: 0, 11 | resourceCount: 0, 12 | longTaskCount: 0, 13 | userActionCount: 0, 14 | } 15 | 16 | var subscription = lifeCycle.subscribe( 17 | LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, 18 | function (data) { 19 | var rawRumEvent = data.rawRumEvent 20 | switch (rawRumEvent.type) { 21 | case RumEventType.ERROR: 22 | eventCounts.errorCount += 1 23 | callback(eventCounts) 24 | break 25 | case RumEventType.RESOURCE: 26 | eventCounts.resourceCount += 1 27 | callback(eventCounts) 28 | break 29 | case RumEventType.ACTION: 30 | eventCounts.userActionCount += 1 31 | callback(eventCounts) 32 | break 33 | } 34 | }, 35 | ) 36 | 37 | return { 38 | stop: function () { 39 | subscription.unsubscribe() 40 | }, 41 | eventCounts: eventCounts, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/rumEventsCollection/trackPageActiveites.js: -------------------------------------------------------------------------------- 1 | import { each, now } from '../helper/utils' 2 | import { LifeCycleEventType } from '../core/lifeCycle' 3 | import { Observable } from '../core/observable' 4 | // Delay to wait for a page activity to validate the tracking process 5 | export var PAGE_ACTIVITY_VALIDATION_DELAY = 100 6 | // Delay to wait after a page activity to end the tracking process 7 | export var PAGE_ACTIVITY_END_DELAY = 100 8 | // Maximum duration of the tracking process 9 | export var PAGE_ACTIVITY_MAX_DURATION = 10000 10 | 11 | export function waitIdlePageActivity(lifeCycle, completionCallback) { 12 | var _trackPageActivities = trackPageActivities(lifeCycle) 13 | var pageActivitiesObservable = _trackPageActivities.observable 14 | var stopPageActivitiesTracking = _trackPageActivities.stop 15 | var _waitPageActivitiesCompletion = waitPageActivitiesCompletion( 16 | pageActivitiesObservable, 17 | stopPageActivitiesTracking, 18 | completionCallback, 19 | ) 20 | 21 | var stopWaitPageActivitiesCompletion = _waitPageActivitiesCompletion.stop 22 | function stop() { 23 | stopWaitPageActivitiesCompletion() 24 | stopPageActivitiesTracking() 25 | } 26 | 27 | return { stop: stop } 28 | } 29 | 30 | // Automatic action collection lifecycle overview: 31 | // (Start new trackPageActivities) 32 | // .-------------------'--------------------. 33 | // v v 34 | // [Wait for a page activity ] [Wait for a maximum duration] 35 | // [timeout: VALIDATION_DELAY] [ timeout: MAX_DURATION ] 36 | // / \ | 37 | // v v | 38 | // [No page activity] [Page activity] | 39 | // | |,----------------------. | 40 | // v v | | 41 | // (Discard) [Wait for a page activity] | | 42 | // [ timeout: END_DELAY ] | | 43 | // / \ | | 44 | // v v | | 45 | // [No page activity] [Page activity] | | 46 | // | | | | 47 | // | '------------' | 48 | // '-----------. ,--------------------' 49 | // v 50 | // (End) 51 | // 52 | // Note: because MAX_DURATION > VALIDATION_DELAY, we are sure that if the process is still alive 53 | // after MAX_DURATION, it has been validated. 54 | export function trackPageActivities(lifeCycle) { 55 | var observable = new Observable() 56 | var subscriptions = [] 57 | var firstRequestIndex 58 | var pendingRequestsCount = 0 59 | 60 | subscriptions.push( 61 | lifeCycle.subscribe(LifeCycleEventType.PAGE_SET_DATA_UPDATE, function () { 62 | notifyPageActivity() 63 | }), 64 | lifeCycle.subscribe(LifeCycleEventType.PAGE_ALIAS_ACTION, function () { 65 | notifyPageActivity() 66 | }), 67 | ) 68 | 69 | subscriptions.push( 70 | lifeCycle.subscribe( 71 | LifeCycleEventType.REQUEST_STARTED, 72 | function (startEvent) { 73 | if (firstRequestIndex === undefined) { 74 | firstRequestIndex = startEvent.requestIndex 75 | } 76 | 77 | pendingRequestsCount += 1 78 | notifyPageActivity() 79 | }, 80 | ), 81 | ) 82 | 83 | subscriptions.push( 84 | lifeCycle.subscribe( 85 | LifeCycleEventType.REQUEST_COMPLETED, 86 | function (request) { 87 | // If the request started before the tracking start, ignore it 88 | if ( 89 | firstRequestIndex === undefined || 90 | request.requestIndex < firstRequestIndex 91 | ) { 92 | return 93 | } 94 | pendingRequestsCount -= 1 95 | notifyPageActivity() 96 | }, 97 | ), 98 | ) 99 | 100 | function notifyPageActivity() { 101 | observable.notify({ isBusy: pendingRequestsCount > 0 }) 102 | } 103 | 104 | return { 105 | observable: observable, 106 | stop: function () { 107 | each(subscriptions, function (sub) { 108 | sub.unsubscribe() 109 | }) 110 | }, 111 | } 112 | } 113 | 114 | export function waitPageActivitiesCompletion( 115 | pageActivitiesObservable, 116 | stopPageActivitiesTracking, 117 | completionCallback, 118 | ) { 119 | var idleTimeoutId 120 | var hasCompleted = false 121 | 122 | var validationTimeoutId = setTimeout(function () { 123 | complete({ hadActivity: false }) 124 | }, PAGE_ACTIVITY_VALIDATION_DELAY) 125 | var maxDurationTimeoutId = setTimeout(function () { 126 | complete({ hadActivity: true, endTime: now() }) 127 | }, PAGE_ACTIVITY_MAX_DURATION) 128 | pageActivitiesObservable.subscribe(function (data) { 129 | var isBusy = data.isBusy 130 | clearTimeout(validationTimeoutId) 131 | clearTimeout(idleTimeoutId) 132 | var lastChangeTime = now() 133 | if (!isBusy) { 134 | idleTimeoutId = setTimeout(function () { 135 | complete({ hadActivity: true, endTime: lastChangeTime }) 136 | }, PAGE_ACTIVITY_END_DELAY) 137 | } 138 | }) 139 | 140 | function stop() { 141 | hasCompleted = true 142 | clearTimeout(validationTimeoutId) 143 | clearTimeout(idleTimeoutId) 144 | clearTimeout(maxDurationTimeoutId) 145 | stopPageActivitiesTracking() 146 | } 147 | 148 | function complete(params) { 149 | if (hasCompleted) { 150 | return 151 | } 152 | stop() 153 | completionCallback(params) 154 | } 155 | 156 | return { stop: stop } 157 | } 158 | -------------------------------------------------------------------------------- /src/rumEventsCollection/transport/batch.js: -------------------------------------------------------------------------------- 1 | import { LifeCycleEventType } from '../../core/lifeCycle' 2 | import { Batch, HttpRequest } from '../../core/transport' 3 | import { RumEventType } from '../../helper/enums' 4 | export function startRumBatch(configuration, lifeCycle) { 5 | var batch = makeRumBatch(configuration, lifeCycle) 6 | lifeCycle.subscribe( 7 | LifeCycleEventType.RUM_EVENT_COLLECTED, 8 | function (serverRumEvent) { 9 | if (serverRumEvent.type === RumEventType.VIEW) { 10 | batch.upsert(serverRumEvent, serverRumEvent.page.id) 11 | } else { 12 | batch.add(serverRumEvent) 13 | } 14 | }, 15 | ) 16 | return { 17 | stop: function () { 18 | batch.stop() 19 | }, 20 | } 21 | } 22 | 23 | function makeRumBatch(configuration, lifeCycle) { 24 | var primaryBatch = createRumBatch(configuration.datakitUrl, lifeCycle) 25 | 26 | function createRumBatch(endpointUrl, lifeCycle) { 27 | return new Batch( 28 | new HttpRequest(endpointUrl, configuration.batchBytesLimit), 29 | configuration.maxBatchSize, 30 | configuration.batchBytesLimit, 31 | configuration.maxMessageSize, 32 | configuration.flushTimeout, 33 | lifeCycle, 34 | ) 35 | } 36 | 37 | var stopped = false 38 | return { 39 | add: function (message) { 40 | if (stopped) { 41 | return 42 | } 43 | primaryBatch.add(message) 44 | }, 45 | stop: function () { 46 | stopped = true 47 | }, 48 | upsert: function (message, key) { 49 | if (stopped) { 50 | return 51 | } 52 | primaryBatch.upsert(message, key) 53 | }, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const TerserPlugin = require('terser-webpack-plugin') 3 | module.exports = (env, args) => { 4 | let baseConfig = { 5 | mode: args.mode, 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'dataflux-rum-miniapp.js', 9 | path: path.resolve(__dirname, './demo/miniprogram'), 10 | library: { 11 | type: 'commonjs2', 12 | }, 13 | }, 14 | devtool: args.mode === 'development' ? 'inline-source-map' : 'source-map', 15 | } 16 | if (args.mode !== 'development') { 17 | baseConfig = Object.assign(baseConfig, { 18 | optimization: { 19 | minimize: true, 20 | minimizer: [ 21 | new TerserPlugin({ 22 | terserOptions: { 23 | compress: { 24 | drop_console: true, 25 | }, 26 | }, 27 | }), 28 | ], 29 | }, 30 | }) 31 | } else { 32 | baseConfig = Object.assign(baseConfig, { 33 | watchOptions: { 34 | ignored: /node_modules|demo/, //忽略不用监听变更的目录 35 | aggregateTimeout: 300, // 文件发生改变后多长时间后再重新编译(Add a delay before rebuilding once the first file changed ) 36 | poll: 1000, //每秒询问的文件变更的次数 37 | }, 38 | }) 39 | } 40 | return baseConfig 41 | } 42 | --------------------------------------------------------------------------------