├── .bowerrc
├── .editorconfig
├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── dist
├── bj-report-tryjs.js
├── bj-report-tryjs.min.js
├── bj-report.js
└── bj-report.min.js
├── example
├── aa.js
├── bb.js
├── index-for-jquery.html
├── index-for-seajs.html
├── index.html
├── report.js
├── sea-debug.js
└── zepto.js
├── package.json
├── src
├── bj-report.js
└── bj-wrap.js
└── test
├── index.html
├── index.js
├── setup.js
└── testForLocation.html
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "proxy" : "http://web-proxyhk.oa.com:8080",
3 | "https-proxy" : "http://web-proxyhk.oa.com:8080"
4 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = crlf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | # Tabs in JS unless otherwise specified
13 | [**.js]
14 | indent_style = space
15 | indent_size = 4
16 |
17 | [**.html]
18 | indent_style = space
19 | indent_size = 4
20 |
21 | [**.css]
22 | indent_style = space
23 | indent_size = 4
24 |
25 | [**.sass]
26 | indent_style = space
27 | indent_size = 4
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components*
2 | node_modules*
3 | npm-debug.log
4 | components*
5 | /temp/
6 | .idea/
7 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // 参考 http://jslinterrors.com/
3 | // ==============预定义变量============
4 | "globals":{
5 | "$": true,
6 | "jQuery": true,
7 | "exports": true,
8 | "module": true,
9 | "define": true,
10 | "require": true,
11 | "it": true,
12 | "before": true,
13 | "describe": true,
14 | "beforeEach": true,
15 | "afterEach": true
16 | },
17 |
18 | // ==============Legacy Options============
19 | // 注: 这些选项继承自jslint
20 |
21 | // 当变量名以"_"开头或结尾时,是否告警
22 | "nomen": false,
23 | // 是否只允许出现一个var在函数中
24 | "onevar": false,
25 | // 是否遇到第一个错误的时候就终止
26 | "passfail": false,
27 | // 让JSHint检查你的代码是否违反道格拉斯的JS编码风格(严格的空白规范??).
28 | "white": false,
29 | // 最大错误数目,超过则停止分析(默认:50)
30 | "maxerr": 50,
31 |
32 | // ==============Environments Options============
33 |
34 | // 是否预定义现代浏览器暴露出来的全局变量(不包括alert和console)
35 | "browser": true,
36 |
37 | // 是否预定义console,alert这种开发阶段的调试代码
38 | "devel": true,
39 |
40 | // 该选项定义了一些非标准但广泛使用的全局变量,像"escape"和"unescape".
41 | "nonstandard": true,
42 |
43 | // ==============Enforcing Options============
44 |
45 | // 禁止使用位运算符
46 | "bitwise": true,
47 | // 只允许使用camelCase or UPPER_CASE
48 | "camelcase": false,
49 | // 必须给block加括号
50 | "curly": false,
51 | // 不安全的== (会建议换成===或者!==)
52 | "eqeqeq": false,
53 | // 不安全的for in
54 | "forin": false,
55 | // 立即函数调用必须用(function(){}())而不是(function(){})()
56 | "immed": true,
57 | // 必须先定义再使用
58 | "latedef": false,
59 | // 构造函数名的首字母必须大写
60 | "newcap": true,
61 | // 禁止arguments.caller和arguments.callee的使用(ES5的严格模式中被禁用了,并且不是所有js实现中都有)
62 | "noarg": true,
63 | // 禁止空的代码块或未赋值的构造器
64 | "noempty": false,
65 | // 不允许不做赋值的构造函数
66 | "nonew": true,
67 | // 不允许使用++或者--的操作符
68 | "plusplus": false,
69 | // 正则中不允许使用.或者[^…]
70 | "regexp": false,
71 | // 未定义的XXX
72 | "undef": true,
73 | // 未使用的XXX
74 | "unused": false,
75 | // 需要使用strict mode,详见http:// ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
76 | "strict": false,
77 | // 行末有多余的空格
78 | "trailing": false,
79 |
80 | // 配置单双引号规则: "false"或者不设置 不做限制 "single" 只许单引号 "double" 只许双引号 "true" 两者选其一,但不能同时出现
81 | "quotmark": "false",
82 |
83 | // 配置缩进规则: 设置缩进对应的空格数,如不设置,则不对缩进做要求
84 | // 注: 这里配置了indent,则即使上面的white设置了false,也还是会导致 swindent 报warning(https:// github.com/jshint/jshint/issues/935)
85 | // TODO:确认jshint解决了 swindent 的支持后可重新配置此项
86 | // "indent": 4,
87 |
88 | // 配置单行代码的最大长度
89 | // "maxlen": 120,
90 |
91 | // 配置函数的最多参数个数: 参数过多时,可读性下降,可考虑用options对象字段来减少参数个数
92 | // "maxparams": 5,
93 |
94 | // 配置函数内代码块嵌套最多层数
95 | // "maxdepth": 5,
96 |
97 | // 配置函数内最多有多少条语句(一个函数声明会被当作一条语句)
98 | // "maxstatements": 50,
99 |
100 | // 配置最大圈复杂度(http://en.wikipedia.org/wiki/Cyclomatic_complexity)
101 | // "maxcomplexity": 10, // TODO:打开来优化代码复杂度
102 |
103 |
104 | // ==============Relaxing Options============
105 |
106 | // 代码末尾缺失分号,是否放过
107 | "asi": false,
108 | // for等预期是==的地方用了=,是否放过
109 | "boss": false,
110 | // 代码中有debugger语句,是否放过
111 | "debug": false,
112 | // 使用"=="来比较变量与null,是否放过
113 | "eqnull": false,
114 | // ES5语法校验报警,是否放过
115 | "es5": false,
116 | // ECMAScript 6语法校验报警,是否放过
117 | "esnext": true,
118 | // 代码中使用了eval,是否放过
119 | "evil": true,
120 | // 只允许在函数调用或赋值时使用表达式(原因???) 2 > 1 ? (alert(1)) : (alert(2));
121 | "expr": true,
122 | // 在控制语句中定义了变量,却在控制语句之外使用变量 function(){ if(1) {var x = 0;} x = x + 1;}
123 | "funcscope": false,
124 | // 使用了全局级别的严格模式,是否放过
125 | "globalstrict": false,
126 | // 使用了__iterator__这个属性(并非所有浏览器支持), 是否放过 (原因???)
127 | "iterator": false,
128 |
129 |
130 | // 是否允许单行语句块中最后一条语句不写分号
131 | "lastsemic": false,
132 | // 是否允许出现不安全的换行(示例???)
133 | "laxbreak": true,
134 | // 是否允许逗号出现在行首的换行方式
135 | "laxcomma": false,
136 | // 是否允许在循环里定义函数
137 | "loopfunc": true,
138 | // 是否允许多行字符串(原因???)
139 | "multistr": true,
140 | // 是否允许在代码中使用__proto__属性(并非所有浏览器支持)
141 | "proto": false,
142 | // 是否允许在switch语句中只出现一个case
143 | "onecase": true,
144 | // 是否允许unescaped - in the end of regular expressions.
145 | "regexdash": false,
146 | // 是否允许在代码中使用"javascript:..."这样的url
147 | "scripturl": true,
148 | // 是否使用smarttab风格混用tab和space(http:// www.emacswiki.org/emacs/SmartTabs)
149 | "smarttabs": false,
150 | // 是否允许使用shadow变量(shadow变量:当一个变量所在的作用域之外还有一个同名的变量,称为shadow变量)
151 | // http:// stackoverflow.com/questions/5373278/variable-shadowing-in-javascript
152 | "shadow": false,
153 | // 是否允许用obj['name']和obj.name两种方式访问对象的属性
154 | "sub": true,
155 | // 是否允许使用像"new function() {...}"这样怪异的构造器
156 | "supernew": true,
157 | // 是否允许在严格模式下的非构造函数中使用this(该选项只能用于函数作用域中)
158 | "validthis": false,
159 |
160 | // Missing radix parameter to parseInt (defaults to 10)
161 | "-W065": false,
162 | // Literal accessor is better written in dot notation
163 | "-W069": false
164 | }
165 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '5.4.1'
4 | before_script:
5 | - npm install -g grunt-cli
6 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /* global module */
2 | module.exports = function(grunt) {
3 |
4 | grunt.initConfig({
5 | clean: {
6 | dist: ['dist'],
7 | cleanOther: ['dist/bj-wrap.js']
8 | },
9 | uglify: {
10 | build: {
11 | files: [{
12 | 'dist/bj-report.min.js': ["dist/bj-report.js"]
13 | }, {
14 | 'dist/bj-report-tryjs.min.js': ["dist/bj-report-tryjs.js"]
15 | },]
16 | }
17 | },
18 | copy: {
19 | dist: {
20 | files: [{
21 | expand: true,
22 | cwd: 'src/',
23 | src: ['**/*.js'],
24 | dest: 'dist/'
25 | }]
26 | }
27 | },
28 | cssmin: {
29 | dist: {
30 | files: [{
31 | expand: true,
32 | cwd: 'src/',
33 | src: ['**/*.css'],
34 | dest: 'dist/'
35 | }]
36 | }
37 | },
38 | concat: {
39 | options: {
40 | separator: ';'
41 | },
42 | dist: {
43 | src: ['src/bj-report.js', 'src/bj-wrap.js'],
44 | dest: 'dist/bj-report-tryjs.js'
45 |
46 | }
47 | },
48 | mocha: {
49 | all: {
50 | src: 'test/index.html',
51 | run: false
52 | }
53 | },
54 | jshint: {
55 | options: {
56 | jshintrc: '.jshintrc'
57 | },
58 | all: ['Gruntfile.js', 'src/**/*.js']
59 | }
60 | });
61 |
62 | grunt.loadNpmTasks('grunt-mocha');
63 | grunt.loadNpmTasks('grunt-contrib-copy');
64 | grunt.loadNpmTasks('grunt-contrib-clean');
65 | grunt.loadNpmTasks('grunt-contrib-jshint');
66 | grunt.loadNpmTasks('grunt-contrib-uglify');
67 | grunt.loadNpmTasks('grunt-contrib-concat');
68 |
69 | // alias task
70 | grunt.registerTask('default', ['dev']);
71 | grunt.registerTask('dist', ['build']);
72 | grunt.registerTask('release', ['build']);
73 |
74 | // task
75 | grunt.registerTask('clear', ['clean']);
76 | grunt.registerTask('test', ['jshint', 'mocha']);
77 | grunt.registerTask('dev', ['clean:dev', 'jshint', 'uglify']);
78 | grunt.registerTask('build', ['clean:dist', 'jshint', 'copy:dist', 'concat:dist', 'clean:cleanOther', 'uglify']);
79 |
80 | };
81 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | badjs-report -- 前端日志上报与JS异常监控
2 | ---
3 |
4 | [](https://travis-ci.org/BetterJS/badjs-report)
5 | [](https://gitter.im/BetterJS?utm_source=share-link&utm_medium=link&utm_campaign=share-link)
6 | ## Author
7 | [caihuiji](https://github.com/caihuiji) [yorts52](https://github.com/yorts52)
8 |
9 | ## Install
10 |
11 | ```shell
12 | $ npm install badjs-report
13 | ```
14 | ```shell
15 | $ bower install https://github.com/BetterJS/badjs-report.git
16 | ```
17 | ```shell
18 | $ lego install badjs-report --save
19 | ```
20 |
21 | ## Getting Started
22 | > badjs-report 必须在所有类库之前加载并初始化。
23 | > 但是要在 jquery、seajs、requrejs等类库后调用spyAll()。
24 |
25 | ##### 初始化
26 | ```javascript
27 | BJ_REPORT.init({
28 | id: 1 // 不指定 id 将不上报
29 | });
30 | ```
31 | ##### 配置说明
32 | ```javascript
33 | BJ_REPORT.init({
34 | id: 1, // 上报 id, 不指定 id 将不上报
35 | uin: 123, // 指定用户 id, (默认已经读取 qq uin)
36 | delay: 1000, // 延迟多少毫秒,合并缓冲区中的上报(默认)
37 | url: "//badjs2.qq.com/badjs", // 指定上报地址
38 | ignore: [/Script error/i], // 忽略某个错误
39 | random: 1, // 抽样上报,1~0 之间数值,1为100%上报(默认 1)
40 | repeat: 5, // 重复上报次数(对于同一个错误超过多少次不上报)
41 | // 避免出现单个用户同一错误上报过多的情况
42 | onReport: function(id, errObj){}, // 当上报的时候回调。 id: 上报的 id, errObj: 错误的对象
43 | submit: null, // 覆盖原来的上报方式,可以自行修改为 post 上报等
44 | ext: {}, // 扩展属性,后端做扩展处理属性。例如:存在 msid 就会分发到 monitor,
45 | offlineLog : false, // 是否启离线日志 [默认 false]
46 | offlineLogExp : 5, // 离线有效时间,默认最近5天
47 | });
48 | ```
49 | BJ_Report 是重写了 window.onerror 进行上报的,无需编写任何捕获错误的代码
50 |
51 | ##### 手动上报
52 | ```javascript
53 | BJ_REPORT.report("error msg");
54 |
55 | BJ_REPORT.report({
56 | msg: "xx load error", // 错误信息
57 | target: "xxx.js", // 错误的来源js
58 | rowNum: 100, // 错误的行数
59 | colNum: 100, // 错误的列数
60 | });
61 |
62 | try{
63 | // something throw error ...
64 | }catch(error){
65 | BJ_REPORT.report(e);
66 | }
67 | ```
68 |
69 | ##### 延迟上报
70 |
71 | ```javascript
72 | BJ_REPORT.push("error msg");
73 |
74 | BJ_REPORT.push({
75 | msg: "xx load error", // 错误信息
76 | target: "xxx.js", // 错误的来源js
77 | rowNum: 100, // 错误的行数
78 | colNum: 100, // 错误的列数
79 | });
80 |
81 | BJ_REPORT.report();
82 |
83 | ```
84 |
85 | ##### 上报离线日志
86 |
87 | ```javascript
88 | BJ_REPORT.reportOfflineLog();
89 | ```
90 |
91 | > 什么是离线日志? [#25](https://github.com/BetterJS/badjs-report/issues/25)
92 |
93 | ##### 用法
94 | ```javascript
95 | //初始化
96 | BJ_REPORT.init({id: 1})
97 |
98 | //主动上报错误日志
99 | BJ_REPORT.report("error msg 2");
100 |
101 | //info上报,用于记录操作日志
102 | BJ_REPORT.info("info");
103 |
104 | //可以结合实时上报,跟踪问题; 不存入存储
105 | BJ_REPORT.debug("debug");
106 |
107 | //记录离线日志
108 | BJ_REPORT.offlineLog("offlineLog");
109 | ```
110 |
111 |
112 | ### 高级用法
113 | >script error 的错误,怎么解决? [#3](https://github.com/BetterJS/badjs-report/issues/3)
114 |
115 | 由于 BJ_Report 只是重写了onerror 方法而已,而且浏览器的跨域问题不能获得外链 javascript 的错误,所以使用tryJs 进行包裹。
116 | #### 包裹jquery
117 | ```javascript
118 | BJ_REPORT.tryJs().spyJquery();
119 | ```
120 | 包裹 jquery 的 event.add , event.remove , event.ajax 这几个异步方法。
121 |
122 |
123 | #### 包裹 define , require
124 | ```javascript
125 | BJ_REPORT.tryJs().spyModules();
126 | ```
127 | 包裹 模块化框架 的 define , require 方法
128 |
129 |
130 | #### 包裹 js 默认的方法
131 | ```javascript
132 | BJ_REPORT.tryJs().spySystem();
133 | ```
134 | 包裹 js 的 setTimeout , setInterval 方法
135 |
136 |
137 | #### 包裹 自定义的方法
138 | ```javascript
139 | var customFunction = function (){};
140 | customFunction = BJ_REPORT.tryJs().spyCustom(customFunction );
141 |
142 | // 只会包裹 customOne , customTwo
143 | var customObject = { customOne : function (){} , customTwo : function (){} , customVar : 1}
144 | BJ_REPORT.tryJs().spyCustom(customObject );
145 | ```
146 | 包裹 自定义的方法或则对象
147 |
148 |
149 | #### 运行所有默认的包裹
150 | ```javascript
151 | //自动运行 SpyJquery , SpyModule , SpySystem
152 | BJ_REPORT.tryJs().spyAll();
153 | ```
154 |
155 | ## update log
156 | ##### v1.3.3
157 | 1. BUGFIX
158 |
159 | ##### v1.3.1
160 | 1. 支持离线日志
161 | 2. 支持自动上报离线日志
162 |
163 | ##### v1.2.3
164 | 1. BUGFIX
165 |
166 | ##### v1.2.1
167 | 1. 增加去除重复参数
168 | 2. 修复了 webpack 引入问题
169 | 3. BUGFIX
170 |
171 | ##### v1.1.8
172 | 1. 项目重命名后更新项目路径(注: 之前名字为`report`)
173 |
174 | ##### v1.1.7
175 | 1. 合并上报的问题
176 | 2. 增加sea.use try-catch 处理
177 |
178 | ##### v1.1.6
179 | 1. add BJ_ERROR hash
180 |
181 | ##### v1.1.5
182 | 1. bugfix
183 |
184 | ##### v1.1.4
185 | 1. 增加info 和 debug 接口
186 | 2. report 增加对 error 对象处理
187 | 3. 处理 [Object event] 问题
188 |
189 | ##### v1.1.3
190 | 1. bugfix
191 |
192 | ##### v1.1.2
193 | 1. 增加抽样参数 random
194 |
195 | ##### v1.1.1
196 | 1. seajs 兼容的BUG修复
197 | 2. 增加 ext 属性,用户可以自己定义里面的值上报
198 |
199 | ##### v1.1.0
200 | 1. 增加对seajs 模块化的包裹
201 | 2. 增加对IE下面的错误的上报
202 |
203 | ##### v1.0.5
204 | 1. 修复异步环境下抛给浏览器的BUG也会上报,
205 | 2. 修复ignore 数组判断的迭代的问题
206 |
207 | ##### v1.0.4
208 | 1. 修复 spy 插件增加在 异步环境中,抛出异常捕获后,再抛给浏览器
209 | 2. 修复 增加在异步环境中,抛出异常,捕获后,将错误信息输出
210 | 3. 增加onReport 回调
211 |
212 | ##### v1.0.3
213 | 1. 修复说明文档
214 |
215 | ##### v1.0.2
216 | 1. 修复 uin 的正则
217 |
218 | ##### v1.0.1
219 | 1. 增加 spy 插件
220 |
221 | ##### v1.0.0
222 | 1. 功能上线
223 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "badjs-report",
3 | "version": "1.3.3",
4 | "homepage": "https://github.com/BetterJS/report",
5 | "author": "chriscai,kael",
6 | "description": "client report",
7 | "main": "dist/bj-report-tryjs.js",
8 | "license": "MIT",
9 | "keywords": ["report"],
10 | "ignore": [
11 | "**/.*",
12 | "node_modules",
13 | "bower_components",
14 | "test"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/dist/bj-report-tryjs.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @module report
3 | * @author kael, chriscai
4 | * @date @DATE
5 | * Copyright (c) 2014 kael, chriscai
6 | * Licensed under the MIT license.
7 | */
8 | var BJ_REPORT = (function(global) {
9 | if (global.BJ_REPORT) return global.BJ_REPORT;
10 |
11 | var _log_list = [];
12 | var _log_map = {};
13 | var _config = {
14 | id: 0, // 上报 id
15 | uin: 0, // user id
16 | url: "", // 上报 接口
17 | offline_url: "", // 离线日志上报 接口
18 | offline_auto_url: "", // 检测是否自动上报
19 | ext: null, // 扩展参数 用于自定义上报
20 | level: 4, // 错误级别 1-debug 2-info 4-error
21 | ignore: [], // 忽略某个错误, 支持 Regexp 和 Function
22 | random: 1, // 抽样 (0-1] 1-全量
23 | delay: 1000, // 延迟上报 combo 为 true 时有效
24 | submit: null, // 自定义上报方式
25 | repeat: 5, // 重复上报次数(对于同一个错误超过多少次不上报),
26 | offlineLog: false,
27 | offlineLogExp: 5, // 离线日志过期时间 , 默认5天
28 | offlineLogAuto: false, //是否自动询问服务器需要自动上报
29 | };
30 |
31 | var Offline_DB = {
32 | db: null,
33 | ready: function(callback) {
34 | var self = this;
35 | if (!window.indexedDB || !_config.offlineLog) {
36 | _config.offlineLog = false;
37 | return callback();
38 | }
39 |
40 | if (this.db) {
41 | setTimeout(function() {
42 | callback(null, self);
43 | }, 0);
44 |
45 | return;
46 | }
47 | var version = 1;
48 | var request = window.indexedDB.open("badjs", version);
49 |
50 | if (!request) {
51 | _config.offlineLog = false;
52 | return callback();
53 | }
54 |
55 | request.onerror = function(e) {
56 | callback(e);
57 | _config.offlineLog = false;
58 | console.log("indexdb request error");
59 | return true;
60 | };
61 | request.onsuccess = function(e) {
62 | self.db = e.target.result;
63 |
64 | setTimeout(function() {
65 | callback(null, self);
66 | }, 500);
67 |
68 |
69 | };
70 | request.onupgradeneeded = function(e) {
71 | var db = e.target.result;
72 | if (!db.objectStoreNames.contains('logs')) {
73 | db.createObjectStore('logs', { autoIncrement: true });
74 | }
75 | };
76 | },
77 | insertToDB: function(log) {
78 | var store = this.getStore();
79 | store.add(log);
80 | },
81 | addLog: function(log) {
82 | if (!this.db) {
83 | return;
84 | }
85 | this.insertToDB(log);
86 | },
87 | addLogs: function(logs) {
88 | if (!this.db) {
89 | return;
90 | }
91 |
92 | for (var i = 0; i < logs.length; i++) {
93 | this.addLog(logs[i]);
94 | }
95 |
96 | },
97 | getLogs: function(opt, callback) {
98 | if (!this.db) {
99 | return;
100 | }
101 | var store = this.getStore();
102 | var request = store.openCursor();
103 | var result = [];
104 | request.onsuccess = function(event) {
105 | var cursor = event.target.result;
106 | if (cursor) {
107 | if (cursor.value.time >= opt.start && cursor.value.time <= opt.end && cursor.value.id == opt.id && cursor.value.uin == opt.uin) {
108 | result.push(cursor.value);
109 | }
110 | //# cursor.continue
111 | cursor["continue"]();
112 | } else {
113 | callback(null, result);
114 | }
115 | };
116 |
117 | request.onerror = function(e) {
118 | callback(e);
119 | return true;
120 | };
121 | },
122 | clearDB: function(daysToMaintain) {
123 | if (!this.db) {
124 | return;
125 | }
126 |
127 | var store = this.getStore();
128 | if (!daysToMaintain) {
129 | store.clear();
130 | return;
131 | }
132 | var range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000);
133 | var request = store.openCursor();
134 | request.onsuccess = function(event) {
135 | var cursor = event.target.result;
136 | if (cursor && (cursor.value.time < range || !cursor.value.time)) {
137 | store["delete"](cursor.primaryKey);
138 | cursor["continue"]();
139 | }
140 | };
141 | },
142 |
143 | getStore: function() {
144 | var transaction = this.db.transaction("logs", 'readwrite');
145 | return transaction.objectStore("logs");
146 | },
147 |
148 | };
149 |
150 | var T = {
151 | isOBJByType: function(o, type) {
152 | return Object.prototype.toString.call(o) === "[object " + (type || "Object") + "]";
153 | },
154 |
155 | isOBJ: function(obj) {
156 | var type = typeof obj;
157 | return type === "object" && !!obj;
158 | },
159 | isEmpty: function(obj) {
160 | if (obj === null) return true;
161 | if (T.isOBJByType(obj, "Number")) {
162 | return false;
163 | }
164 | return !obj;
165 | },
166 | extend: function(src, source) {
167 | for (var key in source) {
168 | src[key] = source[key];
169 | }
170 | return src;
171 | },
172 | processError: function(errObj) {
173 | try {
174 | if (errObj.stack) {
175 | var url = errObj.stack.match("https?://[^\n]+");
176 | url = url ? url[0] : "";
177 | var rowCols = url.match(":(\\d+):(\\d+)");
178 | if (!rowCols) {
179 | rowCols = [0, 0, 0];
180 | }
181 |
182 | var stack = T.processStackMsg(errObj);
183 | return {
184 | msg: stack,
185 | rowNum: rowCols[1],
186 | colNum: rowCols[2],
187 | target: url.replace(rowCols[0], ""),
188 | _orgMsg: errObj.toString()
189 | };
190 | } else {
191 | //ie 独有 error 对象信息,try-catch 捕获到错误信息传过来,造成没有msg
192 | if (errObj.name && errObj.message && errObj.description) {
193 | return {
194 | msg: JSON.stringify(errObj)
195 | };
196 | }
197 | return errObj;
198 | }
199 | } catch (err) {
200 | return errObj;
201 | }
202 | },
203 |
204 | processStackMsg: function(error) {
205 | var stack = error.stack
206 | .replace(/\n/gi, "")
207 | .split(/\bat\b/)
208 | .slice(0, 9)
209 | .join("@")
210 | .replace(/\?[^:]+/gi, "");
211 | var msg = error.toString();
212 | if (stack.indexOf(msg) < 0) {
213 | stack = msg + "@" + stack;
214 | }
215 | return stack;
216 | },
217 |
218 | isRepeat: function(error) {
219 | if (!T.isOBJ(error)) return true;
220 | var msg = error.msg;
221 | var times = _log_map[msg] = (parseInt(_log_map[msg], 10) || 0) + 1;
222 | return times > _config.repeat;
223 | }
224 | };
225 |
226 | var orgError = global.onerror;
227 | // rewrite window.oerror
228 | global.onerror = function(msg, url, line, col, error) {
229 | var newMsg = msg;
230 |
231 | if (error && error.stack) {
232 | newMsg = T.processStackMsg(error);
233 | }
234 |
235 | if (T.isOBJByType(newMsg, "Event")) {
236 | newMsg += newMsg.type ?
237 | ("--" + newMsg.type + "--" + (newMsg.target ?
238 | (newMsg.target.tagName + "::" + newMsg.target.src) : "")) : "";
239 | }
240 |
241 | report.push({
242 | msg: newMsg,
243 | target: url,
244 | rowNum: line,
245 | colNum: col,
246 | _orgMsg: msg
247 | });
248 |
249 | _process_log();
250 | orgError && orgError.apply(global, arguments);
251 | };
252 |
253 |
254 |
255 | var _report_log_tostring = function(error, index) {
256 | var param = [];
257 | var params = [];
258 | var stringify = [];
259 | if (T.isOBJ(error)) {
260 | error.level = error.level || _config.level;
261 | for (var key in error) {
262 | var value = error[key];
263 | if (!T.isEmpty(value)) {
264 | if (T.isOBJ(value)) {
265 | try {
266 | value = JSON.stringify(value);
267 | } catch (err) {
268 | value = "[BJ_REPORT detect value stringify error] " + err.toString();
269 | }
270 | }
271 | stringify.push(key + ":" + value);
272 | param.push(key + "=" + encodeURIComponent(value));
273 | params.push(key + "[" + index + "]=" + encodeURIComponent(value));
274 | }
275 | }
276 | }
277 |
278 | // msg[0]=msg&target[0]=target -- combo report
279 | // msg:msg,target:target -- ignore
280 | // msg=msg&target=target -- report with out combo
281 | return [params.join("&"), stringify.join(","), param.join("&")];
282 | };
283 |
284 |
285 |
286 | var _offline_buffer = [];
287 | var _save2Offline = function(key, msgObj) {
288 | msgObj = T.extend({ id: _config.id, uin: _config.uin, time: new Date - 0 }, msgObj);
289 |
290 | if (Offline_DB.db) {
291 | Offline_DB.addLog(msgObj);
292 | return;
293 | }
294 |
295 |
296 | if (!Offline_DB.db && !_offline_buffer.length) {
297 | Offline_DB.ready(function(err, DB) {
298 | if (DB) {
299 | if (_offline_buffer.length) {
300 | DB.addLogs(_offline_buffer);
301 | _offline_buffer = [];
302 | }
303 |
304 | }
305 | });
306 | }
307 | _offline_buffer.push(msgObj);
308 | };
309 |
310 | var _autoReportOffline = function() {
311 | var script = document.createElement("script");
312 | script.src = _config.offline_auto_url || _config.url.replace(/badjs$/, "offlineAuto") + "?id=" + _config.id + "&uin=" + _config.uin;
313 | window._badjsOfflineAuto = function(isReport) {
314 | if (isReport) {
315 | BJ_REPORT.reportOfflineLog();
316 | }
317 | };
318 | document.head.appendChild(script);
319 | };
320 |
321 |
322 |
323 | var submit_log_list = [];
324 | var comboTimeout = 0;
325 | var _submit_log = function() {
326 | clearTimeout(comboTimeout);
327 | // https://github.com/BetterJS/badjs-report/issues/34
328 | comboTimeout = 0;
329 |
330 | if (!submit_log_list.length) {
331 | return;
332 | }
333 |
334 | var url = _config._reportUrl + submit_log_list.join("&") + "&count=" + submit_log_list.length + "&_t=" + (+new Date);
335 |
336 | if (_config.submit) {
337 | _config.submit(url, submit_log_list);
338 | } else {
339 | var _img = new Image();
340 | _img.src = url;
341 | }
342 |
343 | submit_log_list = [];
344 | };
345 |
346 | var _process_log = function(isReportNow) {
347 | if (!_config._reportUrl) return;
348 |
349 | var randomIgnore = Math.random() >= _config.random;
350 |
351 |
352 | while (_log_list.length) {
353 | var isIgnore = false;
354 | var report_log = _log_list.shift();
355 | //有效保证字符不要过长
356 | report_log.msg = (report_log.msg + "" || "").substr(0, 500);
357 | // 重复上报
358 | if (T.isRepeat(report_log)) continue;
359 | var log_str = _report_log_tostring(report_log, submit_log_list.length);
360 | if (T.isOBJByType(_config.ignore, "Array")) {
361 | for (var i = 0, l = _config.ignore.length; i < l; i++) {
362 | var rule = _config.ignore[i];
363 | if ((T.isOBJByType(rule, "RegExp") && rule.test(log_str[1])) ||
364 | (T.isOBJByType(rule, "Function") && rule(report_log, log_str[1]))) {
365 | isIgnore = true;
366 | break;
367 | }
368 | }
369 | }
370 | if (!isIgnore) {
371 | _config.offlineLog && _save2Offline("badjs_" + _config.id + _config.uin, report_log);
372 | if (!randomIgnore && report_log.level != 20) {
373 | submit_log_list.push(log_str[0]);
374 | _config.onReport && (_config.onReport(_config.id, report_log));
375 | }
376 |
377 | }
378 | }
379 |
380 |
381 | if (isReportNow) {
382 | _submit_log(); // 立即上报
383 | } else if (!comboTimeout) {
384 | comboTimeout = setTimeout(_submit_log, _config.delay); // 延迟上报
385 | }
386 | };
387 |
388 |
389 |
390 | var report = global.BJ_REPORT = {
391 | push: function(msg) { // 将错误推到缓存池
392 |
393 | var data = T.isOBJ(msg) ? T.processError(msg) : {
394 | msg: msg
395 | };
396 |
397 | // ext 有默认值, 且上报不包含 ext, 使用默认 ext
398 | if (_config.ext && !data.ext) {
399 | data.ext = _config.ext;
400 | }
401 | // 在错误发生时获取页面链接
402 | // https://github.com/BetterJS/badjs-report/issues/19
403 | if (!data.from) {
404 | data.from = location.href;
405 | }
406 |
407 | if (data._orgMsg) {
408 | var _orgMsg = data._orgMsg;
409 | delete data._orgMsg;
410 | data.level = 2;
411 | var newData = T.extend({}, data);
412 | newData.level = 4;
413 | newData.msg = _orgMsg;
414 | _log_list.push(data);
415 | _log_list.push(newData);
416 | } else {
417 | _log_list.push(data);
418 | }
419 |
420 | _process_log();
421 | return report;
422 | },
423 | report: function(msg, isReportNow) { // error report
424 | msg && report.push(msg);
425 |
426 | isReportNow && _process_log(true);
427 | return report;
428 | },
429 | info: function(msg) { // info report
430 | if (!msg) {
431 | return report;
432 | }
433 | if (T.isOBJ(msg)) {
434 | msg.level = 2;
435 | } else {
436 | msg = {
437 | msg: msg,
438 | level: 2
439 | };
440 | }
441 | report.push(msg);
442 | return report;
443 | },
444 | debug: function(msg) { // debug report
445 | if (!msg) {
446 | return report;
447 | }
448 | if (T.isOBJ(msg)) {
449 | msg.level = 1;
450 | } else {
451 | msg = {
452 | msg: msg,
453 | level: 1
454 | };
455 | }
456 | report.push(msg);
457 | return report;
458 | },
459 |
460 | reportOfflineLog: function() {
461 | if (!window.indexedDB) {
462 | BJ_REPORT.info("unsupport offlineLog");
463 | return;
464 | }
465 | Offline_DB.ready(function(err, DB) {
466 | if (!DB) {
467 | return;
468 | }
469 | var startDate = new Date - 0 - _config.offlineLogExp * 24 * 3600 * 1000;
470 | var endDate = new Date - 0;
471 | DB.getLogs({
472 | start: startDate,
473 | end: endDate,
474 | id: _config.id,
475 | uin: _config.uin
476 | }, function(err, result) {
477 | var iframe = document.createElement("iframe");
478 | iframe.name = "badjs_offline_" + (new Date - 0);
479 | iframe.frameborder = 0;
480 | iframe.height = 0;
481 | iframe.width = 0;
482 | iframe.src = "javascript:false;";
483 |
484 | iframe.onload = function() {
485 | var form = document.createElement("form");
486 | form.style.display = "none";
487 | form.target = iframe.name;
488 | form.method = "POST";
489 | form.action = _config.offline_url || _config.url.replace(/badjs$/, "offlineLog");
490 | form.enctype.method = 'multipart/form-data';
491 |
492 | var input = document.createElement("input");
493 | input.style.display = "none";
494 | input.type = "hidden";
495 | input.name = "offline_log";
496 | input.value = JSON.stringify({ logs: result, userAgent: navigator.userAgent, startDate: startDate, endDate: endDate, id: _config.id, uin: _config.uin });
497 |
498 | iframe.contentDocument.body.appendChild(form);
499 | form.appendChild(input);
500 | form.submit();
501 |
502 | setTimeout(function() {
503 | document.body.removeChild(iframe);
504 | }, 10000);
505 |
506 | iframe.onload = null;
507 | };
508 | document.body.appendChild(iframe);
509 | });
510 | });
511 | },
512 | offlineLog: function(msg) {
513 | if (!msg) {
514 | return report;
515 | }
516 | if (T.isOBJ(msg)) {
517 | msg.level = 20;
518 | } else {
519 | msg = {
520 | msg: msg,
521 | level: 20
522 | };
523 | }
524 | report.push(msg);
525 | return report;
526 | },
527 | init: function(config) { // 初始化
528 | if (T.isOBJ(config)) {
529 | for (var key in config) {
530 | _config[key] = config[key];
531 | }
532 | }
533 | // 没有设置id将不上报
534 | var id = parseInt(_config.id, 10);
535 | if (id) {
536 | // set default report url and uin
537 | if (/qq\.com$/gi.test(location.hostname)) {
538 | if (!_config.url) {
539 | _config.url = "//badjs2.qq.com/badjs";
540 | }
541 |
542 | if (!_config.uin) {
543 | _config.uin = parseInt((document.cookie.match(/\buin=\D+(\d+)/) || [])[1], 10);
544 | }
545 | }
546 |
547 | _config._reportUrl = (_config.url || "/badjs") +
548 | "?id=" + id +
549 | "&uin=" + _config.uin +
550 | // "&from=" + encodeURIComponent(location.href) +
551 | "&";
552 | }
553 |
554 | // if had error in cache , report now
555 | if (_log_list.length) {
556 | _process_log();
557 | }
558 |
559 | // init offline
560 | if (!Offline_DB._initing) {
561 | Offline_DB._initing = true;
562 | Offline_DB.ready(function(err, DB) {
563 | if (DB) {
564 | setTimeout(function() {
565 | DB.clearDB(_config.offlineLogExp);
566 | setTimeout(function() {
567 | _config.offlineLogAuto && _autoReportOffline();
568 | }, 5000);
569 | }, 1000);
570 | }
571 |
572 | });
573 | }
574 |
575 |
576 |
577 | return report;
578 | },
579 |
580 | __onerror__: global.onerror
581 | };
582 |
583 | typeof console !== "undefined" && console.error && setTimeout(function() {
584 | var err = ((location.hash || "").match(/([#&])BJ_ERROR=([^&$]+)/) || [])[2];
585 | err && console.error("BJ_ERROR", decodeURIComponent(err).replace(/(:\d+:\d+)\s*/g, "$1\n"));
586 | }, 0);
587 |
588 | return report;
589 |
590 | }(window));
591 |
592 | if (typeof module !== "undefined") {
593 | module.exports = BJ_REPORT;
594 | }
595 | ;(function(global) {
596 |
597 | if (!global.BJ_REPORT) {
598 | console.error("please load bg-report first");
599 | return;
600 | }
601 |
602 | var _onthrow = function(errObj) {
603 | global.BJ_REPORT.push(errObj);
604 | };
605 |
606 | var tryJs = {};
607 | global.BJ_REPORT.tryJs = function(throwCb) {
608 | throwCb && (_onthrow = throwCb);
609 | return tryJs;
610 | };
611 |
612 | // merge
613 | var _merge = function(org, obj) {
614 | for (var key in obj) {
615 | org[key] = obj[key];
616 | }
617 | };
618 |
619 | // function or not
620 | var _isFunction = function(foo) {
621 | return typeof foo === "function";
622 | };
623 |
624 | var timeoutkey;
625 |
626 | var cat = function(foo, args) {
627 | return function() {
628 | try {
629 | return foo.apply(this, args || arguments);
630 | } catch (error) {
631 |
632 | _onthrow(error);
633 |
634 | //some browser throw error (chrome) , can not find error where it throw, so print it on console;
635 | if (error.stack && console && console.error) {
636 | console.error("[BJ-REPORT]", error.stack);
637 | }
638 |
639 | // hang up browser and throw , but it should trigger onerror , so rewrite onerror then recover it
640 | if (!timeoutkey) {
641 | var orgOnerror = global.onerror;
642 | global.onerror = function() { };
643 | timeoutkey = setTimeout(function() {
644 | global.onerror = orgOnerror;
645 | timeoutkey = null;
646 | }, 50);
647 | }
648 | throw error;
649 | }
650 | };
651 | };
652 |
653 | var catArgs = function(foo) {
654 | return function() {
655 | var arg, args = [];
656 | for (var i = 0, l = arguments.length; i < l; i++) {
657 | arg = arguments[i];
658 | _isFunction(arg) && (arg = cat(arg));
659 | args.push(arg);
660 | }
661 | return foo.apply(this, args);
662 | };
663 | };
664 |
665 | var catTimeout = function(foo) {
666 | return function(cb, timeout) {
667 | // for setTimeout(string, delay)
668 | if (typeof cb === "string") {
669 | try {
670 | cb = new Function(cb);
671 | } catch (err) {
672 | throw err;
673 | }
674 | }
675 | var args = [].slice.call(arguments, 2);
676 | // for setTimeout(function, delay, param1, ...)
677 | cb = cat(cb, args.length && args);
678 | return foo(cb, timeout);
679 | };
680 | };
681 |
682 | /**
683 | * makeArgsTry
684 | * wrap a function's arguments with try & catch
685 | * @param {Function} foo
686 | * @param {Object} self
687 | * @returns {Function}
688 | */
689 | var makeArgsTry = function(foo, self) {
690 | return function() {
691 | var arg, tmp, args = [];
692 | for (var i = 0, l = arguments.length; i < l; i++) {
693 | arg = arguments[i];
694 | if (_isFunction(arg)) {
695 | if (arg.tryWrap) {
696 | arg = arg.tryWrap;
697 | } else {
698 | tmp = cat(arg);
699 | arg.tryWrap = tmp;
700 | arg = tmp;
701 | }
702 | }
703 | args.push(arg);
704 | }
705 | return foo.apply(self || this, args);
706 | };
707 | };
708 |
709 | /**
710 | * makeObjTry
711 | * wrap a object's all value with try & catch
712 | * @param {Function} foo
713 | * @param {Object} self
714 | * @returns {Function}
715 | */
716 | var makeObjTry = function(obj) {
717 | var key, value;
718 | for (key in obj) {
719 | value = obj[key];
720 | if (_isFunction(value)) obj[key] = cat(value);
721 | }
722 | return obj;
723 | };
724 |
725 | /**
726 | * wrap jquery async function ,exp : event.add , event.remove , ajax
727 | * @returns {Function}
728 | */
729 | tryJs.spyJquery = function() {
730 | var _$ = global.$;
731 |
732 | if (!_$ || !_$.event) {
733 | return tryJs;
734 | }
735 |
736 | var _add, _remove;
737 | if (_$.zepto) {
738 | _add = _$.fn.on, _remove = _$.fn.off;
739 |
740 | _$.fn.on = makeArgsTry(_add);
741 | _$.fn.off = function() {
742 | var arg, args = [];
743 | for (var i = 0, l = arguments.length; i < l; i++) {
744 | arg = arguments[i];
745 | _isFunction(arg) && arg.tryWrap && (arg = arg.tryWrap);
746 | args.push(arg);
747 | }
748 | return _remove.apply(this, args);
749 | };
750 |
751 | } else if (window.jQuery) {
752 | _add = _$.event.add, _remove = _$.event.remove;
753 |
754 | _$.event.add = makeArgsTry(_add);
755 | _$.event.remove = function() {
756 | var arg, args = [];
757 | for (var i = 0, l = arguments.length; i < l; i++) {
758 | arg = arguments[i];
759 | _isFunction(arg) && arg.tryWrap && (arg = arg.tryWrap);
760 | args.push(arg);
761 | }
762 | return _remove.apply(this, args);
763 | };
764 | }
765 |
766 | var _ajax = _$.ajax;
767 |
768 | if (_ajax) {
769 | _$.ajax = function(url, setting) {
770 | if (!setting) {
771 | setting = url;
772 | url = undefined;
773 | }
774 | makeObjTry(setting);
775 | if (url) return _ajax.call(_$, url, setting);
776 | return _ajax.call(_$, setting);
777 | };
778 | }
779 |
780 | return tryJs;
781 | };
782 |
783 | /**
784 | * wrap amd or commonjs of function ,exp : define , require ,
785 | * @returns {Function}
786 | */
787 | tryJs.spyModules = function() {
788 | var _require = global.require,
789 | _define = global.define;
790 | if (_define && _define.amd && _require) {
791 | global.require = catArgs(_require);
792 | _merge(global.require, _require);
793 | global.define = catArgs(_define);
794 | _merge(global.define, _define);
795 | }
796 |
797 | if (global.seajs && _define) {
798 | global.define = function() {
799 | var arg, args = [];
800 | for (var i = 0, l = arguments.length; i < l; i++) {
801 | arg = arguments[i];
802 | if (_isFunction(arg)) {
803 | arg = cat(arg);
804 | //seajs should use toString parse dependencies , so rewrite it
805 | arg.toString = (function(orgArg) {
806 | return function() {
807 | return orgArg.toString();
808 | };
809 | }(arguments[i]));
810 | }
811 | args.push(arg);
812 | }
813 | return _define.apply(this, args);
814 | };
815 |
816 | global.seajs.use = catArgs(global.seajs.use);
817 |
818 | _merge(global.define, _define);
819 | }
820 |
821 | return tryJs;
822 | };
823 |
824 | /**
825 | * wrap async of function in window , exp : setTimeout , setInterval
826 | * @returns {Function}
827 | */
828 | tryJs.spySystem = function() {
829 | global.setTimeout = catTimeout(global.setTimeout);
830 | global.setInterval = catTimeout(global.setInterval);
831 | return tryJs;
832 | };
833 |
834 | /**
835 | * wrap custom of function ,
836 | * @param obj - obj or function
837 | * @returns {Function}
838 | */
839 | tryJs.spyCustom = function(obj) {
840 | if (_isFunction(obj)) {
841 | return cat(obj);
842 | } else {
843 | return makeObjTry(obj);
844 | }
845 | };
846 |
847 | /**
848 | * run spyJquery() and spyModules() and spySystem()
849 | * @returns {Function}
850 | */
851 | tryJs.spyAll = function() {
852 | tryJs
853 | .spyJquery()
854 | .spyModules()
855 | .spySystem();
856 | return tryJs;
857 | };
858 |
859 | }(window));
860 |
--------------------------------------------------------------------------------
/dist/bj-report-tryjs.min.js:
--------------------------------------------------------------------------------
1 | var BJ_REPORT=function(u){if(u.BJ_REPORT)return u.BJ_REPORT;var f=[],t={},l={id:0,uin:0,url:"",offline_url:"",offline_auto_url:"",ext:null,level:4,ignore:[],random:1,delay:1e3,submit:null,repeat:5,offlineLog:!1,offlineLogExp:5,offlineLogAuto:!1},c={db:null,ready:function(n){var t=this;if(!window.indexedDB||!l.offlineLog)return l.offlineLog=!1,n();if(this.db)setTimeout(function(){n(null,t)},0);else{var e=window.indexedDB.open("badjs",1);if(!e)return l.offlineLog=!1,n();e.onerror=function(e){return n(e),l.offlineLog=!1,console.log("indexdb request error"),!0},e.onsuccess=function(e){t.db=e.target.result,setTimeout(function(){n(null,t)},500)},e.onupgradeneeded=function(e){var n=e.target.result;n.objectStoreNames.contains("logs")||n.createObjectStore("logs",{autoIncrement:!0})}}},insertToDB:function(e){this.getStore().add(e)},addLog:function(e){this.db&&this.insertToDB(e)},addLogs:function(e){if(this.db)for(var n=0;n=t.start&&n.value.time<=t.end&&n.value.id==t.id&&n.value.uin==t.uin&&o.push(n.value),n.continue()):r(null,o)},e.onerror=function(e){return r(e),!0}}},clearDB:function(e){if(this.db){var t=this.getStore();if(e){var r=Date.now()-24*(e||2)*3600*1e3;t.openCursor().onsuccess=function(e){var n=e.target.result;n&&(n.value.timel.repeat}},s=u.onerror;u.onerror=function(e,n,t,r,o){var i=e;o&&o.stack&&(i=d.processStackMsg(o)),d.isOBJByType(i,"Event")&&(i+=i.type?"--"+i.type+"--"+(i.target?i.target.tagName+"::"+i.target.src:""):""),y.push({msg:i,target:n,rowNum:t,colNum:r,_orgMsg:e}),a(),s&&s.apply(u,arguments)};var p=function(e,n){var t=[],r=[],o=[];if(d.isOBJ(e))for(var i in e.level=e.level||l.level,e){var u=e[i];if(!d.isEmpty(u)){if(d.isOBJ(u))try{u=JSON.stringify(u)}catch(e){u="[BJ_REPORT detect value stringify error] "+e.toString()}o.push(i+":"+u),t.push(i+"="+encodeURIComponent(u)),r.push(i+"["+n+"]="+encodeURIComponent(u))}}return[r.join("&"),o.join(","),t.join("&")]},g=[],m=[],v=0,h=function(){if(clearTimeout(v),v=0,m.length){var e=l._reportUrl+m.join("&")+"&count="+m.length+"&_t="+ +new Date;if(l.submit)l.submit(e,m);else(new Image).src=e;m=[]}},a=function(e){if(l._reportUrl){for(var n,t=Math.random()>=l.random;f.length;){var r=!1,o=f.shift();if(o.msg=(o.msg+""||"").substr(0,500),!d.isRepeat(o)){var i=p(o,m.length);if(d.isOBJByType(l.ignore,"Array"))for(var u=0,s=l.ignore.length;u= opt.start && cursor.value.time <= opt.end && cursor.value.id == opt.id && cursor.value.uin == opt.uin) {
108 | result.push(cursor.value);
109 | }
110 | //# cursor.continue
111 | cursor["continue"]();
112 | } else {
113 | callback(null, result);
114 | }
115 | };
116 |
117 | request.onerror = function(e) {
118 | callback(e);
119 | return true;
120 | };
121 | },
122 | clearDB: function(daysToMaintain) {
123 | if (!this.db) {
124 | return;
125 | }
126 |
127 | var store = this.getStore();
128 | if (!daysToMaintain) {
129 | store.clear();
130 | return;
131 | }
132 | var range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000);
133 | var request = store.openCursor();
134 | request.onsuccess = function(event) {
135 | var cursor = event.target.result;
136 | if (cursor && (cursor.value.time < range || !cursor.value.time)) {
137 | store["delete"](cursor.primaryKey);
138 | cursor["continue"]();
139 | }
140 | };
141 | },
142 |
143 | getStore: function() {
144 | var transaction = this.db.transaction("logs", 'readwrite');
145 | return transaction.objectStore("logs");
146 | },
147 |
148 | };
149 |
150 | var T = {
151 | isOBJByType: function(o, type) {
152 | return Object.prototype.toString.call(o) === "[object " + (type || "Object") + "]";
153 | },
154 |
155 | isOBJ: function(obj) {
156 | var type = typeof obj;
157 | return type === "object" && !!obj;
158 | },
159 | isEmpty: function(obj) {
160 | if (obj === null) return true;
161 | if (T.isOBJByType(obj, "Number")) {
162 | return false;
163 | }
164 | return !obj;
165 | },
166 | extend: function(src, source) {
167 | for (var key in source) {
168 | src[key] = source[key];
169 | }
170 | return src;
171 | },
172 | processError: function(errObj) {
173 | try {
174 | if (errObj.stack) {
175 | var url = errObj.stack.match("https?://[^\n]+");
176 | url = url ? url[0] : "";
177 | var rowCols = url.match(":(\\d+):(\\d+)");
178 | if (!rowCols) {
179 | rowCols = [0, 0, 0];
180 | }
181 |
182 | var stack = T.processStackMsg(errObj);
183 | return {
184 | msg: stack,
185 | rowNum: rowCols[1],
186 | colNum: rowCols[2],
187 | target: url.replace(rowCols[0], ""),
188 | _orgMsg: errObj.toString()
189 | };
190 | } else {
191 | //ie 独有 error 对象信息,try-catch 捕获到错误信息传过来,造成没有msg
192 | if (errObj.name && errObj.message && errObj.description) {
193 | return {
194 | msg: JSON.stringify(errObj)
195 | };
196 | }
197 | return errObj;
198 | }
199 | } catch (err) {
200 | return errObj;
201 | }
202 | },
203 |
204 | processStackMsg: function(error) {
205 | var stack = error.stack
206 | .replace(/\n/gi, "")
207 | .split(/\bat\b/)
208 | .slice(0, 9)
209 | .join("@")
210 | .replace(/\?[^:]+/gi, "");
211 | var msg = error.toString();
212 | if (stack.indexOf(msg) < 0) {
213 | stack = msg + "@" + stack;
214 | }
215 | return stack;
216 | },
217 |
218 | isRepeat: function(error) {
219 | if (!T.isOBJ(error)) return true;
220 | var msg = error.msg;
221 | var times = _log_map[msg] = (parseInt(_log_map[msg], 10) || 0) + 1;
222 | return times > _config.repeat;
223 | }
224 | };
225 |
226 | var orgError = global.onerror;
227 | // rewrite window.oerror
228 | global.onerror = function(msg, url, line, col, error) {
229 | var newMsg = msg;
230 |
231 | if (error && error.stack) {
232 | newMsg = T.processStackMsg(error);
233 | }
234 |
235 | if (T.isOBJByType(newMsg, "Event")) {
236 | newMsg += newMsg.type ?
237 | ("--" + newMsg.type + "--" + (newMsg.target ?
238 | (newMsg.target.tagName + "::" + newMsg.target.src) : "")) : "";
239 | }
240 |
241 | report.push({
242 | msg: newMsg,
243 | target: url,
244 | rowNum: line,
245 | colNum: col,
246 | _orgMsg: msg
247 | });
248 |
249 | _process_log();
250 | orgError && orgError.apply(global, arguments);
251 | };
252 |
253 |
254 |
255 | var _report_log_tostring = function(error, index) {
256 | var param = [];
257 | var params = [];
258 | var stringify = [];
259 | if (T.isOBJ(error)) {
260 | error.level = error.level || _config.level;
261 | for (var key in error) {
262 | var value = error[key];
263 | if (!T.isEmpty(value)) {
264 | if (T.isOBJ(value)) {
265 | try {
266 | value = JSON.stringify(value);
267 | } catch (err) {
268 | value = "[BJ_REPORT detect value stringify error] " + err.toString();
269 | }
270 | }
271 | stringify.push(key + ":" + value);
272 | param.push(key + "=" + encodeURIComponent(value));
273 | params.push(key + "[" + index + "]=" + encodeURIComponent(value));
274 | }
275 | }
276 | }
277 |
278 | // msg[0]=msg&target[0]=target -- combo report
279 | // msg:msg,target:target -- ignore
280 | // msg=msg&target=target -- report with out combo
281 | return [params.join("&"), stringify.join(","), param.join("&")];
282 | };
283 |
284 |
285 |
286 | var _offline_buffer = [];
287 | var _save2Offline = function(key, msgObj) {
288 | msgObj = T.extend({ id: _config.id, uin: _config.uin, time: new Date - 0 }, msgObj);
289 |
290 | if (Offline_DB.db) {
291 | Offline_DB.addLog(msgObj);
292 | return;
293 | }
294 |
295 |
296 | if (!Offline_DB.db && !_offline_buffer.length) {
297 | Offline_DB.ready(function(err, DB) {
298 | if (DB) {
299 | if (_offline_buffer.length) {
300 | DB.addLogs(_offline_buffer);
301 | _offline_buffer = [];
302 | }
303 |
304 | }
305 | });
306 | }
307 | _offline_buffer.push(msgObj);
308 | };
309 |
310 | var _autoReportOffline = function() {
311 | var script = document.createElement("script");
312 | script.src = _config.offline_auto_url || _config.url.replace(/badjs$/, "offlineAuto") + "?id=" + _config.id + "&uin=" + _config.uin;
313 | window._badjsOfflineAuto = function(isReport) {
314 | if (isReport) {
315 | BJ_REPORT.reportOfflineLog();
316 | }
317 | };
318 | document.head.appendChild(script);
319 | };
320 |
321 |
322 |
323 | var submit_log_list = [];
324 | var comboTimeout = 0;
325 | var _submit_log = function() {
326 | clearTimeout(comboTimeout);
327 | // https://github.com/BetterJS/badjs-report/issues/34
328 | comboTimeout = 0;
329 |
330 | if (!submit_log_list.length) {
331 | return;
332 | }
333 |
334 | var url = _config._reportUrl + submit_log_list.join("&") + "&count=" + submit_log_list.length + "&_t=" + (+new Date);
335 |
336 | if (_config.submit) {
337 | _config.submit(url, submit_log_list);
338 | } else {
339 | var _img = new Image();
340 | _img.src = url;
341 | }
342 |
343 | submit_log_list = [];
344 | };
345 |
346 | var _process_log = function(isReportNow) {
347 | if (!_config._reportUrl) return;
348 |
349 | var randomIgnore = Math.random() >= _config.random;
350 |
351 |
352 | while (_log_list.length) {
353 | var isIgnore = false;
354 | var report_log = _log_list.shift();
355 | //有效保证字符不要过长
356 | report_log.msg = (report_log.msg + "" || "").substr(0, 500);
357 | // 重复上报
358 | if (T.isRepeat(report_log)) continue;
359 | var log_str = _report_log_tostring(report_log, submit_log_list.length);
360 | if (T.isOBJByType(_config.ignore, "Array")) {
361 | for (var i = 0, l = _config.ignore.length; i < l; i++) {
362 | var rule = _config.ignore[i];
363 | if ((T.isOBJByType(rule, "RegExp") && rule.test(log_str[1])) ||
364 | (T.isOBJByType(rule, "Function") && rule(report_log, log_str[1]))) {
365 | isIgnore = true;
366 | break;
367 | }
368 | }
369 | }
370 | if (!isIgnore) {
371 | _config.offlineLog && _save2Offline("badjs_" + _config.id + _config.uin, report_log);
372 | if (!randomIgnore && report_log.level != 20) {
373 | submit_log_list.push(log_str[0]);
374 | _config.onReport && (_config.onReport(_config.id, report_log));
375 | }
376 |
377 | }
378 | }
379 |
380 |
381 | if (isReportNow) {
382 | _submit_log(); // 立即上报
383 | } else if (!comboTimeout) {
384 | comboTimeout = setTimeout(_submit_log, _config.delay); // 延迟上报
385 | }
386 | };
387 |
388 |
389 |
390 | var report = global.BJ_REPORT = {
391 | push: function(msg) { // 将错误推到缓存池
392 |
393 | var data = T.isOBJ(msg) ? T.processError(msg) : {
394 | msg: msg
395 | };
396 |
397 | // ext 有默认值, 且上报不包含 ext, 使用默认 ext
398 | if (_config.ext && !data.ext) {
399 | data.ext = _config.ext;
400 | }
401 | // 在错误发生时获取页面链接
402 | // https://github.com/BetterJS/badjs-report/issues/19
403 | if (!data.from) {
404 | data.from = location.href;
405 | }
406 |
407 | if (data._orgMsg) {
408 | var _orgMsg = data._orgMsg;
409 | delete data._orgMsg;
410 | data.level = 2;
411 | var newData = T.extend({}, data);
412 | newData.level = 4;
413 | newData.msg = _orgMsg;
414 | _log_list.push(data);
415 | _log_list.push(newData);
416 | } else {
417 | _log_list.push(data);
418 | }
419 |
420 | _process_log();
421 | return report;
422 | },
423 | report: function(msg, isReportNow) { // error report
424 | msg && report.push(msg);
425 |
426 | isReportNow && _process_log(true);
427 | return report;
428 | },
429 | info: function(msg) { // info report
430 | if (!msg) {
431 | return report;
432 | }
433 | if (T.isOBJ(msg)) {
434 | msg.level = 2;
435 | } else {
436 | msg = {
437 | msg: msg,
438 | level: 2
439 | };
440 | }
441 | report.push(msg);
442 | return report;
443 | },
444 | debug: function(msg) { // debug report
445 | if (!msg) {
446 | return report;
447 | }
448 | if (T.isOBJ(msg)) {
449 | msg.level = 1;
450 | } else {
451 | msg = {
452 | msg: msg,
453 | level: 1
454 | };
455 | }
456 | report.push(msg);
457 | return report;
458 | },
459 |
460 | reportOfflineLog: function() {
461 | if (!window.indexedDB) {
462 | BJ_REPORT.info("unsupport offlineLog");
463 | return;
464 | }
465 | Offline_DB.ready(function(err, DB) {
466 | if (!DB) {
467 | return;
468 | }
469 | var startDate = new Date - 0 - _config.offlineLogExp * 24 * 3600 * 1000;
470 | var endDate = new Date - 0;
471 | DB.getLogs({
472 | start: startDate,
473 | end: endDate,
474 | id: _config.id,
475 | uin: _config.uin
476 | }, function(err, result) {
477 | var iframe = document.createElement("iframe");
478 | iframe.name = "badjs_offline_" + (new Date - 0);
479 | iframe.frameborder = 0;
480 | iframe.height = 0;
481 | iframe.width = 0;
482 | iframe.src = "javascript:false;";
483 |
484 | iframe.onload = function() {
485 | var form = document.createElement("form");
486 | form.style.display = "none";
487 | form.target = iframe.name;
488 | form.method = "POST";
489 | form.action = _config.offline_url || _config.url.replace(/badjs$/, "offlineLog");
490 | form.enctype.method = 'multipart/form-data';
491 |
492 | var input = document.createElement("input");
493 | input.style.display = "none";
494 | input.type = "hidden";
495 | input.name = "offline_log";
496 | input.value = JSON.stringify({ logs: result, userAgent: navigator.userAgent, startDate: startDate, endDate: endDate, id: _config.id, uin: _config.uin });
497 |
498 | iframe.contentDocument.body.appendChild(form);
499 | form.appendChild(input);
500 | form.submit();
501 |
502 | setTimeout(function() {
503 | document.body.removeChild(iframe);
504 | }, 10000);
505 |
506 | iframe.onload = null;
507 | };
508 | document.body.appendChild(iframe);
509 | });
510 | });
511 | },
512 | offlineLog: function(msg) {
513 | if (!msg) {
514 | return report;
515 | }
516 | if (T.isOBJ(msg)) {
517 | msg.level = 20;
518 | } else {
519 | msg = {
520 | msg: msg,
521 | level: 20
522 | };
523 | }
524 | report.push(msg);
525 | return report;
526 | },
527 | init: function(config) { // 初始化
528 | if (T.isOBJ(config)) {
529 | for (var key in config) {
530 | _config[key] = config[key];
531 | }
532 | }
533 | // 没有设置id将不上报
534 | var id = parseInt(_config.id, 10);
535 | if (id) {
536 | // set default report url and uin
537 | if (/qq\.com$/gi.test(location.hostname)) {
538 | if (!_config.url) {
539 | _config.url = "//badjs2.qq.com/badjs";
540 | }
541 |
542 | if (!_config.uin) {
543 | _config.uin = parseInt((document.cookie.match(/\buin=\D+(\d+)/) || [])[1], 10);
544 | }
545 | }
546 |
547 | _config._reportUrl = (_config.url || "/badjs") +
548 | "?id=" + id +
549 | "&uin=" + _config.uin +
550 | // "&from=" + encodeURIComponent(location.href) +
551 | "&";
552 | }
553 |
554 | // if had error in cache , report now
555 | if (_log_list.length) {
556 | _process_log();
557 | }
558 |
559 | // init offline
560 | if (!Offline_DB._initing) {
561 | Offline_DB._initing = true;
562 | Offline_DB.ready(function(err, DB) {
563 | if (DB) {
564 | setTimeout(function() {
565 | DB.clearDB(_config.offlineLogExp);
566 | setTimeout(function() {
567 | _config.offlineLogAuto && _autoReportOffline();
568 | }, 5000);
569 | }, 1000);
570 | }
571 |
572 | });
573 | }
574 |
575 |
576 |
577 | return report;
578 | },
579 |
580 | __onerror__: global.onerror
581 | };
582 |
583 | typeof console !== "undefined" && console.error && setTimeout(function() {
584 | var err = ((location.hash || "").match(/([#&])BJ_ERROR=([^&$]+)/) || [])[2];
585 | err && console.error("BJ_ERROR", decodeURIComponent(err).replace(/(:\d+:\d+)\s*/g, "$1\n"));
586 | }, 0);
587 |
588 | return report;
589 |
590 | }(window));
591 |
592 | if (typeof module !== "undefined") {
593 | module.exports = BJ_REPORT;
594 | }
595 |
--------------------------------------------------------------------------------
/dist/bj-report.min.js:
--------------------------------------------------------------------------------
1 | var BJ_REPORT=function(u){if(u.BJ_REPORT)return u.BJ_REPORT;var l=[],t={},f={id:0,uin:0,url:"",offline_url:"",offline_auto_url:"",ext:null,level:4,ignore:[],random:1,delay:1e3,submit:null,repeat:5,offlineLog:!1,offlineLogExp:5,offlineLogAuto:!1},c={db:null,ready:function(n){var t=this;if(!window.indexedDB||!f.offlineLog)return f.offlineLog=!1,n();if(this.db)setTimeout(function(){n(null,t)},0);else{var e=window.indexedDB.open("badjs",1);if(!e)return f.offlineLog=!1,n();e.onerror=function(e){return n(e),f.offlineLog=!1,console.log("indexdb request error"),!0},e.onsuccess=function(e){t.db=e.target.result,setTimeout(function(){n(null,t)},500)},e.onupgradeneeded=function(e){var n=e.target.result;n.objectStoreNames.contains("logs")||n.createObjectStore("logs",{autoIncrement:!0})}}},insertToDB:function(e){this.getStore().add(e)},addLog:function(e){this.db&&this.insertToDB(e)},addLogs:function(e){if(this.db)for(var n=0;n=t.start&&n.value.time<=t.end&&n.value.id==t.id&&n.value.uin==t.uin&&r.push(n.value),n.continue()):o(null,r)},e.onerror=function(e){return o(e),!0}}},clearDB:function(e){if(this.db){var t=this.getStore();if(e){var o=Date.now()-24*(e||2)*3600*1e3;t.openCursor().onsuccess=function(e){var n=e.target.result;n&&(n.value.timef.repeat}},s=u.onerror;u.onerror=function(e,n,t,o,r){var i=e;r&&r.stack&&(i=d.processStackMsg(r)),d.isOBJByType(i,"Event")&&(i+=i.type?"--"+i.type+"--"+(i.target?i.target.tagName+"::"+i.target.src:""):""),y.push({msg:i,target:n,rowNum:t,colNum:o,_orgMsg:e}),a(),s&&s.apply(u,arguments)};var g=function(e,n){var t=[],o=[],r=[];if(d.isOBJ(e))for(var i in e.level=e.level||f.level,e){var u=e[i];if(!d.isEmpty(u)){if(d.isOBJ(u))try{u=JSON.stringify(u)}catch(e){u="[BJ_REPORT detect value stringify error] "+e.toString()}r.push(i+":"+u),t.push(i+"="+encodeURIComponent(u)),o.push(i+"["+n+"]="+encodeURIComponent(u))}}return[o.join("&"),r.join(","),t.join("&")]},p=[],m=[],v=0,h=function(){if(clearTimeout(v),v=0,m.length){var e=f._reportUrl+m.join("&")+"&count="+m.length+"&_t="+ +new Date;if(f.submit)f.submit(e,m);else(new Image).src=e;m=[]}},a=function(e){if(f._reportUrl){for(var n,t=Math.random()>=f.random;l.length;){var o=!1,r=l.shift();if(r.msg=(r.msg+""||"").substr(0,500),!d.isRepeat(r)){var i=g(r,m.length);if(d.isOBJByType(f.ignore,"Array"))for(var u=0,s=f.ignore.length;u
2 |
3 |
4 |
5 | example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/example/index-for-seajs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | example
6 |
7 |
8 |
9 |
10 |
11 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/example/report.js:
--------------------------------------------------------------------------------
1 |
2 | define(function (require, exports, module){
3 |
4 | module.exports ={
5 | init : function (){
6 | bbbb+++1
7 | }
8 | }
9 | });
10 |
11 | s13cc();
12 |
--------------------------------------------------------------------------------
/example/sea-debug.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Sea.js 3.0.0 | seajs.org/LICENSE.md
3 | */
4 | (function(global, undefined) {
5 |
6 | // Avoid conflicting when `sea.js` is loaded multiple times
7 | if (global.seajs) {
8 | return
9 | }
10 |
11 | var seajs = global.seajs = {
12 | // The current version of Sea.js being used
13 | version: "3.0.0"
14 | }
15 |
16 | var data = seajs.data = {}
17 |
18 |
19 | /**
20 | * util-lang.js - The minimal language enhancement
21 | */
22 |
23 | function isType(type) {
24 | return function(obj) {
25 | return {}.toString.call(obj) == "[object " + type + "]"
26 | }
27 | }
28 |
29 | var isObject = isType("Object")
30 | var isString = isType("String")
31 | var isArray = Array.isArray || isType("Array")
32 | var isFunction = isType("Function")
33 |
34 | var _cid = 0
35 | function cid() {
36 | return _cid++
37 | }
38 |
39 |
40 | /**
41 | * util-events.js - The minimal events support
42 | */
43 |
44 | var events = data.events = {}
45 |
46 | // Bind event
47 | seajs.on = function(name, callback) {
48 | var list = events[name] || (events[name] = [])
49 | list.push(callback)
50 | return seajs
51 | }
52 |
53 | // Remove event. If `callback` is undefined, remove all callbacks for the
54 | // event. If `event` and `callback` are both undefined, remove all callbacks
55 | // for all events
56 | seajs.off = function(name, callback) {
57 | // Remove *all* events
58 | if (!(name || callback)) {
59 | events = data.events = {}
60 | return seajs
61 | }
62 |
63 | var list = events[name]
64 | if (list) {
65 | if (callback) {
66 | for (var i = list.length - 1; i >= 0; i--) {
67 | if (list[i] === callback) {
68 | list.splice(i, 1)
69 | }
70 | }
71 | }
72 | else {
73 | delete events[name]
74 | }
75 | }
76 |
77 | return seajs
78 | }
79 |
80 | // Emit event, firing all bound callbacks. Callbacks receive the same
81 | // arguments as `emit` does, apart from the event name
82 | var emit = seajs.emit = function(name, data) {
83 | var list = events[name]
84 |
85 | if (list) {
86 | // Copy callback lists to prevent modification
87 | list = list.slice()
88 |
89 | // Execute event callbacks, use index because it's the faster.
90 | for(var i = 0, len = list.length; i < len; i++) {
91 | list[i](data)
92 | }
93 | }
94 |
95 | return seajs
96 | }
97 |
98 | /**
99 | * util-path.js - The utilities for operating path such as id, uri
100 | */
101 |
102 | var DIRNAME_RE = /[^?#]*\//
103 |
104 | var DOT_RE = /\/\.\//g
105 | var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\//
106 | var MULTI_SLASH_RE = /([^:/])\/+\//g
107 |
108 | // Extract the directory portion of a path
109 | // dirname("a/b/c.js?t=123#xx/zz") ==> "a/b/"
110 | // ref: http://jsperf.com/regex-vs-split/2
111 | function dirname(path) {
112 | return path.match(DIRNAME_RE)[0]
113 | }
114 |
115 | // Canonicalize a path
116 | // realpath("http://test.com/a//./b/../c") ==> "http://test.com/a/c"
117 | function realpath(path) {
118 | // /a/b/./c/./d ==> /a/b/c/d
119 | path = path.replace(DOT_RE, "/")
120 |
121 | /*
122 | @author wh1100717
123 | a//b/c ==> a/b/c
124 | a///b/////c ==> a/b/c
125 | DOUBLE_DOT_RE matches a/b/c//../d path correctly only if replace // with / first
126 | */
127 | path = path.replace(MULTI_SLASH_RE, "$1/")
128 |
129 | // a/b/c/../../d ==> a/b/../d ==> a/d
130 | while (path.match(DOUBLE_DOT_RE)) {
131 | path = path.replace(DOUBLE_DOT_RE, "/")
132 | }
133 |
134 | return path
135 | }
136 |
137 | // Normalize an id
138 | // normalize("path/to/a") ==> "path/to/a.js"
139 | // NOTICE: substring is faster than negative slice and RegExp
140 | function normalize(path) {
141 | var last = path.length - 1
142 | var lastC = path.charCodeAt(last)
143 |
144 | // If the uri ends with `#`, just return it without '#'
145 | if (lastC === 35 /* "#" */) {
146 | return path.substring(0, last)
147 | }
148 |
149 | return (path.substring(last - 2) === ".js" ||
150 | path.indexOf("?") > 0 ||
151 | lastC === 47 /* "/" */) ? path : path + ".js"
152 | }
153 |
154 |
155 | var PATHS_RE = /^([^/:]+)(\/.+)$/
156 | var VARS_RE = /{([^{]+)}/g
157 |
158 | function parseAlias(id) {
159 | var alias = data.alias
160 | return alias && isString(alias[id]) ? alias[id] : id
161 | }
162 |
163 | function parsePaths(id) {
164 | var paths = data.paths
165 | var m
166 |
167 | if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) {
168 | id = paths[m[1]] + m[2]
169 | }
170 |
171 | return id
172 | }
173 |
174 | function parseVars(id) {
175 | var vars = data.vars
176 |
177 | if (vars && id.indexOf("{") > -1) {
178 | id = id.replace(VARS_RE, function(m, key) {
179 | return isString(vars[key]) ? vars[key] : m
180 | })
181 | }
182 |
183 | return id
184 | }
185 |
186 | function parseMap(uri) {
187 | var map = data.map
188 | var ret = uri
189 |
190 | if (map) {
191 | for (var i = 0, len = map.length; i < len; i++) {
192 | var rule = map[i]
193 |
194 | ret = isFunction(rule) ?
195 | (rule(uri) || uri) :
196 | uri.replace(rule[0], rule[1])
197 |
198 | // Only apply the first matched rule
199 | if (ret !== uri) break
200 | }
201 | }
202 |
203 | return ret
204 | }
205 |
206 |
207 | var ABSOLUTE_RE = /^\/\/.|:\//
208 | var ROOT_DIR_RE = /^.*?\/\/.*?\//
209 |
210 | function addBase(id, refUri) {
211 | var ret
212 | var first = id.charCodeAt(0)
213 |
214 | // Absolute
215 | if (ABSOLUTE_RE.test(id)) {
216 | ret = id
217 | }
218 | // Relative
219 | else if (first === 46 /* "." */) {
220 | ret = (refUri ? dirname(refUri) : data.cwd) + id
221 | }
222 | // Root
223 | else if (first === 47 /* "/" */) {
224 | var m = data.cwd.match(ROOT_DIR_RE)
225 | ret = m ? m[0] + id.substring(1) : id
226 | }
227 | // Top-level
228 | else {
229 | ret = data.base + id
230 | }
231 |
232 | // Add default protocol when uri begins with "//"
233 | if (ret.indexOf("//") === 0) {
234 | ret = location.protocol + ret
235 | }
236 |
237 | return realpath(ret)
238 | }
239 |
240 | function id2Uri(id, refUri) {
241 | if (!id) return ""
242 |
243 | id = parseAlias(id)
244 | id = parsePaths(id)
245 | id = parseAlias(id)
246 | id = parseVars(id)
247 | id = parseAlias(id)
248 | id = normalize(id)
249 | id = parseAlias(id)
250 |
251 | var uri = addBase(id, refUri)
252 | uri = parseAlias(uri)
253 | uri = parseMap(uri)
254 |
255 | return uri
256 | }
257 |
258 | // For Developers
259 | seajs.resolve = id2Uri;
260 |
261 | // Check environment
262 | var isWebWorker = typeof window === 'undefined' && typeof importScripts !== 'undefined' && isFunction(importScripts);
263 |
264 | // Ignore about:xxx and blob:xxx
265 | var IGNORE_LOCATION_RE = /^(about|blob):/;
266 | var loaderDir;
267 | // Sea.js's full path
268 | var loaderPath;
269 | // Location is read-only from web worker, should be ok though
270 | var cwd = (!location.href || IGNORE_LOCATION_RE.test(location.href)) ? '' : dirname(location.href);
271 |
272 | if (isWebWorker) {
273 | // Web worker doesn't create DOM object when loading scripts
274 | // Get sea.js's path by stack trace.
275 | var stack;
276 | try {
277 | var up = new Error();
278 | throw up;
279 | } catch (e) {
280 | // IE won't set Error.stack until thrown
281 | stack = e.stack.split('\n');
282 | }
283 | // First line is 'Error'
284 | stack.shift();
285 |
286 | var m;
287 | // Try match `url:row:col` from stack trace line. Known formats:
288 | // Chrome: ' at http://localhost:8000/script/sea-worker-debug.js:294:25'
289 | // FireFox: '@http://localhost:8000/script/sea-worker-debug.js:1082:1'
290 | // IE11: ' at Anonymous function (http://localhost:8000/script/sea-worker-debug.js:295:5)'
291 | // Don't care about older browsers since web worker is an HTML5 feature
292 | var TRACE_RE = /.*?((?:http|https|file)(?::\/{2}[\w]+)(?:[\/|\.]?)(?:[^\s"]*)).*?/i
293 | // Try match `url` (Note: in IE there will be a tailing ')')
294 | var URL_RE = /(.*?):\d+:\d+\)?$/;
295 | // Find url of from stack trace.
296 | // Cannot simply read the first one because sometimes we will get:
297 | // Error
298 | // at Error (native) <- Here's your problem
299 | // at http://localhost:8000/_site/dist/sea.js:2:4334 <- What we want
300 | // at http://localhost:8000/_site/dist/sea.js:2:8386
301 | // at http://localhost:8000/_site/tests/specs/web-worker/worker.js:3:1
302 | while (stack.length > 0) {
303 | var top = stack.shift();
304 | m = TRACE_RE.exec(top);
305 | if (m != null) {
306 | break;
307 | }
308 | }
309 | var url;
310 | if (m != null) {
311 | // Remove line number and column number
312 | // No need to check, can't be wrong at this point
313 | var url = URL_RE.exec(m[1])[1];
314 | }
315 | // Set
316 | loaderPath = url
317 | // Set loaderDir
318 | loaderDir = dirname(url || cwd);
319 | // This happens with inline worker.
320 | // When entrance script's location.href is a blob url,
321 | // cwd will not be available.
322 | // Fall back to loaderDir.
323 | if (cwd === '') {
324 | cwd = loaderDir;
325 | }
326 | }
327 | else {
328 | var doc = document
329 | var scripts = doc.scripts
330 |
331 | // Recommend to add `seajsnode` id for the `sea.js` script element
332 | var loaderScript = doc.getElementById("seajsnode") ||
333 | scripts[scripts.length - 1]
334 |
335 | function getScriptAbsoluteSrc(node) {
336 | return node.hasAttribute ? // non-IE6/7
337 | node.src :
338 | // see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
339 | node.getAttribute("src", 4)
340 | }
341 | loaderPath = getScriptAbsoluteSrc(loaderScript)
342 | // When `sea.js` is inline, set loaderDir to current working directory
343 | loaderDir = dirname(loaderPath || cwd)
344 | }
345 |
346 | /**
347 | * util-request.js - The utilities for requesting script and style files
348 | * ref: tests/research/load-js-css/test.html
349 | */
350 | if (isWebWorker) {
351 | function requestFromWebWorker(url, callback, charset) {
352 | // Load with importScripts
353 | var error;
354 | try {
355 | importScripts(url);
356 | } catch (e) {
357 | error = e;
358 | }
359 | callback(error);
360 | }
361 | // For Developers
362 | seajs.request = requestFromWebWorker;
363 | }
364 | else {
365 | var doc = document
366 | var head = doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement
367 | var baseElement = head.getElementsByTagName("base")[0]
368 |
369 | var currentlyAddingScript
370 |
371 | function request(url, callback, charset) {
372 | var node = doc.createElement("script")
373 |
374 | if (charset) {
375 | var cs = isFunction(charset) ? charset(url) : charset
376 | if (cs) {
377 | node.charset = cs
378 | }
379 | }
380 |
381 | addOnload(node, callback, url)
382 |
383 | node.async = true
384 | node.src = url
385 |
386 | // For some cache cases in IE 6-8, the script executes IMMEDIATELY after
387 | // the end of the insert execution, so use `currentlyAddingScript` to
388 | // hold current node, for deriving url in `define` call
389 | currentlyAddingScript = node
390 |
391 | // ref: #185 & http://dev.jquery.com/ticket/2709
392 | baseElement ?
393 | head.insertBefore(node, baseElement) :
394 | head.appendChild(node)
395 |
396 | currentlyAddingScript = null
397 | }
398 |
399 | function addOnload(node, callback, url) {
400 | var supportOnload = "onload" in node
401 |
402 | if (supportOnload) {
403 | node.onload = onload
404 | node.onerror = function() {
405 | emit("error", { uri: url, node: node })
406 | onload(true)
407 | }
408 | }
409 | else {
410 | node.onreadystatechange = function() {
411 | if (/loaded|complete/.test(node.readyState)) {
412 | onload()
413 | }
414 | }
415 | }
416 |
417 | function onload(error) {
418 | // Ensure only run once and handle memory leak in IE
419 | node.onload = node.onerror = node.onreadystatechange = null
420 |
421 | // Remove the script to reduce memory leak
422 | if (!data.debug) {
423 | head.removeChild(node)
424 | }
425 |
426 | // Dereference the node
427 | node = null
428 |
429 | callback(error)
430 | }
431 | }
432 |
433 | // For Developers
434 | seajs.request = request
435 |
436 | }
437 | var interactiveScript
438 |
439 | function getCurrentScript() {
440 | if (currentlyAddingScript) {
441 | return currentlyAddingScript
442 | }
443 |
444 | // For IE6-9 browsers, the script onload event may not fire right
445 | // after the script is evaluated. Kris Zyp found that it
446 | // could query the script nodes and the one that is in "interactive"
447 | // mode indicates the current script
448 | // ref: http://goo.gl/JHfFW
449 | if (interactiveScript && interactiveScript.readyState === "interactive") {
450 | return interactiveScript
451 | }
452 |
453 | var scripts = head.getElementsByTagName("script")
454 |
455 | for (var i = scripts.length - 1; i >= 0; i--) {
456 | var script = scripts[i]
457 | if (script.readyState === "interactive") {
458 | interactiveScript = script
459 | return interactiveScript
460 | }
461 | }
462 | }
463 |
464 | /**
465 | * util-deps.js - The parser for dependencies
466 | * ref: tests/research/parse-dependencies/test.html
467 | * ref: https://github.com/seajs/searequire
468 | */
469 |
470 | function parseDependencies(s) {
471 | if(s.indexOf('require') == -1) {
472 | return []
473 | }
474 | var index = 0, peek, length = s.length, isReg = 1, modName = 0, parentheseState = 0, parentheseStack = [], res = []
475 | while(index < length) {
476 | readch()
477 | if(isBlank()) {
478 | }
479 | else if(isQuote()) {
480 | dealQuote()
481 | isReg = 1
482 | }
483 | else if(peek == '/') {
484 | readch()
485 | if(peek == '/') {
486 | index = s.indexOf('\n', index)
487 | if(index == -1) {
488 | index = s.length
489 | }
490 | }
491 | else if(peek == '*') {
492 | index = s.indexOf('*/', index)
493 | if(index == -1) {
494 | index = length
495 | }
496 | else {
497 | index += 2
498 | }
499 | }
500 | else if(isReg) {
501 | dealReg()
502 | isReg = 0
503 | }
504 | else {
505 | index--
506 | isReg = 1
507 | }
508 | }
509 | else if(isWord()) {
510 | dealWord()
511 | }
512 | else if(isNumber()) {
513 | dealNumber()
514 | }
515 | else if(peek == '(') {
516 | parentheseStack.push(parentheseState)
517 | isReg = 1
518 | }
519 | else if(peek == ')') {
520 | isReg = parentheseStack.pop()
521 | }
522 | else {
523 | isReg = peek != ']'
524 | modName = 0
525 | }
526 | }
527 | return res
528 | function readch() {
529 | peek = s.charAt(index++)
530 | }
531 | function isBlank() {
532 | return /\s/.test(peek)
533 | }
534 | function isQuote() {
535 | return peek == '"' || peek == "'"
536 | }
537 | function dealQuote() {
538 | var start = index
539 | var c = peek
540 | var end = s.indexOf(c, start)
541 | if(end == -1) {
542 | index = length
543 | }
544 | else if(s.charAt(end - 1) != '\\') {
545 | index = end + 1
546 | }
547 | else {
548 | while(index < length) {
549 | readch()
550 | if(peek == '\\') {
551 | index++
552 | }
553 | else if(peek == c) {
554 | break
555 | }
556 | }
557 | }
558 | if(modName) {
559 | res.push(s.slice(start, index - 1))
560 | modName = 0
561 | }
562 | }
563 | function dealReg() {
564 | index--
565 | while(index < length) {
566 | readch()
567 | if(peek == '\\') {
568 | index++
569 | }
570 | else if(peek == '/') {
571 | break
572 | }
573 | else if(peek == '[') {
574 | while(index < length) {
575 | readch()
576 | if(peek == '\\') {
577 | index++
578 | }
579 | else if(peek == ']') {
580 | break
581 | }
582 | }
583 | }
584 | }
585 | }
586 | function isWord() {
587 | return /[a-z_$]/i.test(peek)
588 | }
589 | function dealWord() {
590 | var s2 = s.slice(index - 1)
591 | var r = /^[\w$]+/.exec(s2)[0]
592 | parentheseState = {
593 | 'if': 1,
594 | 'for': 1,
595 | 'while': 1,
596 | 'with': 1
597 | }[r]
598 | isReg = {
599 | 'break': 1,
600 | 'case': 1,
601 | 'continue': 1,
602 | 'debugger': 1,
603 | 'delete': 1,
604 | 'do': 1,
605 | 'else': 1,
606 | 'false': 1,
607 | 'if': 1,
608 | 'in': 1,
609 | 'instanceof': 1,
610 | 'return': 1,
611 | 'typeof': 1,
612 | 'void': 1
613 | }[r]
614 | modName = /^require\s*\(\s*(['"]).+?\1\s*\)/.test(s2)
615 | if(modName) {
616 | r = /^require\s*\(\s*['"]/.exec(s2)[0]
617 | index += r.length - 2
618 | }
619 | else {
620 | index += /^[\w$]+(?:\s*\.\s*[\w$]+)*/.exec(s2)[0].length - 1
621 | }
622 | }
623 | function isNumber() {
624 | return /\d/.test(peek)
625 | || peek == '.' && /\d/.test(s.charAt(index))
626 | }
627 | function dealNumber() {
628 | var s2 = s.slice(index - 1)
629 | var r
630 | if(peek == '.') {
631 | r = /^\.\d+(?:E[+-]?\d*)?\s*/i.exec(s2)[0]
632 | }
633 | else if(/^0x[\da-f]*/i.test(s2)) {
634 | r = /^0x[\da-f]*\s*/i.exec(s2)[0]
635 | }
636 | else {
637 | r = /^\d+\.?\d*(?:E[+-]?\d*)?\s*/i.exec(s2)[0]
638 | }
639 | index += r.length - 1
640 | isReg = 0
641 | }
642 | }
643 | /**
644 | * module.js - The core of module loader
645 | */
646 |
647 | var cachedMods = seajs.cache = {}
648 | var anonymousMeta
649 |
650 | var fetchingList = {}
651 | var fetchedList = {}
652 | var callbackList = {}
653 |
654 | var STATUS = Module.STATUS = {
655 | // 1 - The `module.uri` is being fetched
656 | FETCHING: 1,
657 | // 2 - The meta data has been saved to cachedMods
658 | SAVED: 2,
659 | // 3 - The `module.dependencies` are being loaded
660 | LOADING: 3,
661 | // 4 - The module are ready to execute
662 | LOADED: 4,
663 | // 5 - The module is being executed
664 | EXECUTING: 5,
665 | // 6 - The `module.exports` is available
666 | EXECUTED: 6,
667 | // 7 - 404
668 | ERROR: 7
669 | }
670 |
671 |
672 | function Module(uri, deps) {
673 | this.uri = uri
674 | this.dependencies = deps || []
675 | this.deps = {} // Ref the dependence modules
676 | this.status = 0
677 |
678 | this._entry = []
679 | }
680 |
681 | // Resolve module.dependencies
682 | Module.prototype.resolve = function() {
683 | var mod = this
684 | var ids = mod.dependencies
685 | var uris = []
686 |
687 | for (var i = 0, len = ids.length; i < len; i++) {
688 | uris[i] = Module.resolve(ids[i], mod.uri)
689 | }
690 | return uris
691 | }
692 |
693 | Module.prototype.pass = function() {
694 | var mod = this
695 |
696 | var len = mod.dependencies.length
697 |
698 | for (var i = 0; i < mod._entry.length; i++) {
699 | var entry = mod._entry[i]
700 | var count = 0
701 | for (var j = 0; j < len; j++) {
702 | var m = mod.deps[mod.dependencies[j]]
703 | // If the module is unload and unused in the entry, pass entry to it
704 | if (m.status < STATUS.LOADED && !entry.history.hasOwnProperty(m.uri)) {
705 | entry.history[m.uri] = true
706 | count++
707 | m._entry.push(entry)
708 | if(m.status === STATUS.LOADING) {
709 | m.pass()
710 | }
711 | }
712 | }
713 | // If has passed the entry to it's dependencies, modify the entry's count and del it in the module
714 | if (count > 0) {
715 | entry.remain += count - 1
716 | mod._entry.shift()
717 | i--
718 | }
719 | }
720 | }
721 |
722 | // Load module.dependencies and fire onload when all done
723 | Module.prototype.load = function() {
724 | var mod = this
725 |
726 | // If the module is being loaded, just wait it onload call
727 | if (mod.status >= STATUS.LOADING) {
728 | return
729 | }
730 |
731 | mod.status = STATUS.LOADING
732 |
733 | // Emit `load` event for plugins such as combo plugin
734 | var uris = mod.resolve()
735 | emit("load", uris)
736 |
737 | for (var i = 0, len = uris.length; i < len; i++) {
738 | mod.deps[mod.dependencies[i]] = Module.get(uris[i])
739 | }
740 |
741 | // Pass entry to it's dependencies
742 | mod.pass()
743 |
744 | // If module has entries not be passed, call onload
745 | if (mod._entry.length) {
746 | mod.onload()
747 | return
748 | }
749 |
750 | // Begin parallel loading
751 | var requestCache = {}
752 | var m
753 |
754 | for (i = 0; i < len; i++) {
755 | m = cachedMods[uris[i]]
756 |
757 | if (m.status < STATUS.FETCHING) {
758 | m.fetch(requestCache)
759 | }
760 | else if (m.status === STATUS.SAVED) {
761 | m.load()
762 | }
763 | }
764 |
765 | // Send all requests at last to avoid cache bug in IE6-9. Issues#808
766 | for (var requestUri in requestCache) {
767 | if (requestCache.hasOwnProperty(requestUri)) {
768 | requestCache[requestUri]()
769 | }
770 | }
771 | }
772 |
773 | // Call this method when module is loaded
774 | Module.prototype.onload = function() {
775 | var mod = this
776 | mod.status = STATUS.LOADED
777 |
778 | // When sometimes cached in IE, exec will occur before onload, make sure len is an number
779 | for (var i = 0, len = (mod._entry || []).length; i < len; i++) {
780 | var entry = mod._entry[i]
781 | if (--entry.remain === 0) {
782 | entry.callback()
783 | }
784 | }
785 |
786 | delete mod._entry
787 | }
788 |
789 | // Call this method when module is 404
790 | Module.prototype.error = function() {
791 | var mod = this
792 | mod.onload()
793 | mod.status = STATUS.ERROR
794 | }
795 |
796 | // Execute a module
797 | Module.prototype.exec = function () {
798 | var mod = this
799 |
800 | // When module is executed, DO NOT execute it again. When module
801 | // is being executed, just return `module.exports` too, for avoiding
802 | // circularly calling
803 | if (mod.status >= STATUS.EXECUTING) {
804 | return mod.exports
805 | }
806 |
807 | mod.status = STATUS.EXECUTING
808 |
809 | if (mod._entry && !mod._entry.length) {
810 | delete mod._entry
811 | }
812 |
813 | //non-cmd module has no property factory and exports
814 | if (!mod.hasOwnProperty('factory')) {
815 | mod.non = true
816 | return
817 | }
818 |
819 | // Create require
820 | var uri = mod.uri
821 |
822 | function require(id) {
823 | var m = mod.deps[id] || Module.get(require.resolve(id))
824 | if (m.status == STATUS.ERROR) {
825 | throw new Error('module was broken: ' + m.uri);
826 | }
827 | return m.exec()
828 | }
829 |
830 | require.resolve = function(id) {
831 | return Module.resolve(id, uri)
832 | }
833 |
834 | require.async = function(ids, callback) {
835 | Module.use(ids, callback, uri + "_async_" + cid())
836 | return require
837 | }
838 |
839 | // Exec factory
840 | var factory = mod.factory
841 |
842 | var exports = isFunction(factory) ?
843 | factory(require, mod.exports = {}, mod) :
844 | factory
845 |
846 | if (exports === undefined) {
847 | exports = mod.exports
848 | }
849 |
850 | // Reduce memory leak
851 | delete mod.factory
852 |
853 | mod.exports = exports
854 | mod.status = STATUS.EXECUTED
855 |
856 | // Emit `exec` event
857 | emit("exec", mod)
858 |
859 | return mod.exports
860 | }
861 |
862 | // Fetch a module
863 | Module.prototype.fetch = function(requestCache) {
864 | var mod = this
865 | var uri = mod.uri
866 |
867 | mod.status = STATUS.FETCHING
868 |
869 | // Emit `fetch` event for plugins such as combo plugin
870 | var emitData = { uri: uri }
871 | emit("fetch", emitData)
872 | var requestUri = emitData.requestUri || uri
873 |
874 | // Empty uri or a non-CMD module
875 | if (!requestUri || fetchedList.hasOwnProperty(requestUri)) {
876 | mod.load()
877 | return
878 | }
879 |
880 | if (fetchingList.hasOwnProperty(requestUri)) {
881 | callbackList[requestUri].push(mod)
882 | return
883 | }
884 |
885 | fetchingList[requestUri] = true
886 | callbackList[requestUri] = [mod]
887 |
888 | // Emit `request` event for plugins such as text plugin
889 | emit("request", emitData = {
890 | uri: uri,
891 | requestUri: requestUri,
892 | onRequest: onRequest,
893 | charset: isFunction(data.charset) ? data.charset(requestUri) || 'utf-8' : data.charset
894 | })
895 |
896 | if (!emitData.requested) {
897 | requestCache ?
898 | requestCache[emitData.requestUri] = sendRequest :
899 | sendRequest()
900 | }
901 |
902 | function sendRequest() {
903 | seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
904 | }
905 |
906 | function onRequest(error) {
907 | delete fetchingList[requestUri]
908 | fetchedList[requestUri] = true
909 |
910 | // Save meta data of anonymous module
911 | if (anonymousMeta) {
912 | Module.save(uri, anonymousMeta)
913 | anonymousMeta = null
914 | }
915 |
916 | // Call callbacks
917 | var m, mods = callbackList[requestUri]
918 | delete callbackList[requestUri]
919 | while ((m = mods.shift())) {
920 | // When 404 occurs, the params error will be true
921 | if(error === true) {
922 | m.error()
923 | }
924 | else {
925 | m.load()
926 | }
927 | }
928 | }
929 | }
930 |
931 | // Resolve id to uri
932 | Module.resolve = function(id, refUri) {
933 | // Emit `resolve` event for plugins such as text plugin
934 | var emitData = { id: id, refUri: refUri }
935 | emit("resolve", emitData)
936 |
937 | return emitData.uri || seajs.resolve(emitData.id, refUri)
938 | }
939 |
940 | // Define a module
941 | Module.define = function (id, deps, factory) {
942 | var argsLen = arguments.length
943 |
944 | // define(factory)
945 | if (argsLen === 1) {
946 | factory = id
947 | id = undefined
948 | }
949 | else if (argsLen === 2) {
950 | factory = deps
951 |
952 | // define(deps, factory)
953 | if (isArray(id)) {
954 | deps = id
955 | id = undefined
956 | }
957 | // define(id, factory)
958 | else {
959 | deps = undefined
960 | }
961 | }
962 |
963 | // Parse dependencies according to the module factory code
964 | if (!isArray(deps) && isFunction(factory)) {
965 | deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString())
966 | }
967 |
968 | var meta = {
969 | id: id,
970 | uri: Module.resolve(id),
971 | deps: deps,
972 | factory: factory
973 | }
974 |
975 | // Try to derive uri in IE6-9 for anonymous modules
976 | if (!isWebWorker && !meta.uri && doc.attachEvent && typeof getCurrentScript !== "undefined") {
977 | var script = getCurrentScript()
978 |
979 | if (script) {
980 | meta.uri = script.src
981 | }
982 |
983 | // NOTE: If the id-deriving methods above is failed, then falls back
984 | // to use onload event to get the uri
985 | }
986 |
987 | // Emit `define` event, used in nocache plugin, seajs node version etc
988 | emit("define", meta)
989 |
990 | meta.uri ? Module.save(meta.uri, meta) :
991 | // Save information for "saving" work in the script onload event
992 | anonymousMeta = meta
993 | }
994 |
995 | // Save meta data to cachedMods
996 | Module.save = function(uri, meta) {
997 | var mod = Module.get(uri)
998 |
999 | // Do NOT override already saved modules
1000 | if (mod.status < STATUS.SAVED) {
1001 | mod.id = meta.id || uri
1002 | mod.dependencies = meta.deps || []
1003 | mod.factory = meta.factory
1004 | mod.status = STATUS.SAVED
1005 |
1006 | emit("save", mod)
1007 | }
1008 | }
1009 |
1010 | // Get an existed module or create a new one
1011 | Module.get = function(uri, deps) {
1012 | return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
1013 | }
1014 |
1015 | // Use function is equal to load a anonymous module
1016 | Module.use = function (ids, callback, uri) {
1017 | var mod = Module.get(uri, isArray(ids) ? ids : [ids])
1018 |
1019 | mod._entry.push(mod)
1020 | mod.history = {}
1021 | mod.remain = 1
1022 |
1023 | mod.callback = function() {
1024 | var exports = []
1025 | var uris = mod.resolve()
1026 |
1027 | for (var i = 0, len = uris.length; i < len; i++) {
1028 | exports[i] = cachedMods[uris[i]].exec()
1029 | }
1030 |
1031 | if (callback) {
1032 | callback.apply(global, exports)
1033 | }
1034 |
1035 | delete mod.callback
1036 | delete mod.history
1037 | delete mod.remain
1038 | delete mod._entry
1039 | }
1040 |
1041 | mod.load()
1042 | }
1043 |
1044 |
1045 | // Public API
1046 |
1047 | seajs.use = function(ids, callback) {
1048 | Module.use(ids, callback, data.cwd + "_use_" + cid())
1049 | return seajs
1050 | }
1051 |
1052 | Module.define.cmd = {}
1053 | global.define = Module.define
1054 |
1055 |
1056 | // For Developers
1057 |
1058 | seajs.Module = Module
1059 | data.fetchedList = fetchedList
1060 | data.cid = cid
1061 |
1062 | seajs.require = function(id) {
1063 | var mod = Module.get(Module.resolve(id))
1064 | if (mod.status < STATUS.EXECUTING) {
1065 | mod.onload()
1066 | mod.exec()
1067 | }
1068 | return mod.exports
1069 | }
1070 |
1071 | /**
1072 | * config.js - The configuration for the loader
1073 | */
1074 |
1075 | // The root path to use for id2uri parsing
1076 | data.base = loaderDir
1077 |
1078 | // The loader directory
1079 | data.dir = loaderDir
1080 |
1081 | // The loader's full path
1082 | data.loader = loaderPath
1083 |
1084 | // The current working directory
1085 | data.cwd = cwd
1086 |
1087 | // The charset for requesting files
1088 | data.charset = "utf-8"
1089 |
1090 | // data.alias - An object containing shorthands of module id
1091 | // data.paths - An object containing path shorthands in module id
1092 | // data.vars - The {xxx} variables in module id
1093 | // data.map - An array containing rules to map module uri
1094 | // data.debug - Debug mode. The default value is false
1095 |
1096 | seajs.config = function(configData) {
1097 |
1098 | for (var key in configData) {
1099 | var curr = configData[key]
1100 | var prev = data[key]
1101 |
1102 | // Merge object config such as alias, vars
1103 | if (prev && isObject(prev)) {
1104 | for (var k in curr) {
1105 | prev[k] = curr[k]
1106 | }
1107 | }
1108 | else {
1109 | // Concat array config such as map
1110 | if (isArray(prev)) {
1111 | curr = prev.concat(curr)
1112 | }
1113 | // Make sure that `data.base` is an absolute path
1114 | else if (key === "base") {
1115 | // Make sure end with "/"
1116 | if (curr.slice(-1) !== "/") {
1117 | curr += "/"
1118 | }
1119 | curr = addBase(curr)
1120 | }
1121 |
1122 | // Set config
1123 | data[key] = curr
1124 | }
1125 | }
1126 |
1127 | emit("config", configData)
1128 | return seajs
1129 | }
1130 |
1131 | })(this);
1132 |
--------------------------------------------------------------------------------
/example/zepto.js:
--------------------------------------------------------------------------------
1 | // Zepto.js
2 | // (c) 2010-2014 Thomas Fuchs
3 | // Zepto.js may be freely distributed under the MIT license.
4 |
5 | var Zepto = (function() {
6 | var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
7 | document = window.document,
8 | elementDisplay = {}, classCache = {},
9 | cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
10 | fragmentRE = /^\s*<(\w+|!)[^>]*>/,
11 | singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
12 | tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
13 | rootNodeRE = /^(?:body|html)$/i,
14 | capitalRE = /([A-Z])/g,
15 |
16 | // special attributes that should be get/set via method calls
17 | methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
18 |
19 | adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
20 | table = document.createElement('table'),
21 | tableRow = document.createElement('tr'),
22 | containers = {
23 | 'tr': document.createElement('tbody'),
24 | 'tbody': table, 'thead': table, 'tfoot': table,
25 | 'td': tableRow, 'th': tableRow,
26 | '*': document.createElement('div')
27 | },
28 | readyRE = /complete|loaded|interactive/,
29 | simpleSelectorRE = /^[\w-]*$/,
30 | class2type = {},
31 | toString = class2type.toString,
32 | zepto = {},
33 | camelize, uniq,
34 | tempParent = document.createElement('div'),
35 | propMap = {
36 | 'tabindex': 'tabIndex',
37 | 'readonly': 'readOnly',
38 | 'for': 'htmlFor',
39 | 'class': 'className',
40 | 'maxlength': 'maxLength',
41 | 'cellspacing': 'cellSpacing',
42 | 'cellpadding': 'cellPadding',
43 | 'rowspan': 'rowSpan',
44 | 'colspan': 'colSpan',
45 | 'usemap': 'useMap',
46 | 'frameborder': 'frameBorder',
47 | 'contenteditable': 'contentEditable'
48 | },
49 | isArray = Array.isArray ||
50 | function(object){ return object instanceof Array }
51 |
52 | zepto.matches = function(element, selector) {
53 | if (!selector || !element || element.nodeType !== 1) return false
54 | var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
55 | element.oMatchesSelector || element.matchesSelector
56 | if (matchesSelector) return matchesSelector.call(element, selector)
57 | // fall back to performing a selector:
58 | var match, parent = element.parentNode, temp = !parent
59 | if (temp) (parent = tempParent).appendChild(element)
60 | match = ~zepto.qsa(parent, selector).indexOf(element)
61 | temp && tempParent.removeChild(element)
62 | return match
63 | }
64 |
65 | function type(obj) {
66 | return obj == null ? String(obj) :
67 | class2type[toString.call(obj)] || "object"
68 | }
69 |
70 | function isFunction(value) { return type(value) == "function" }
71 | function isWindow(obj) { return obj != null && obj == obj.window }
72 | function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
73 | function isObject(obj) { return type(obj) == "object" }
74 | function isPlainObject(obj) {
75 | return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
76 | }
77 | function likeArray(obj) { return typeof obj.length == 'number' }
78 |
79 | function compact(array) { return filter.call(array, function(item){ return item != null }) }
80 | function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
81 | camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
82 | function dasherize(str) {
83 | return str.replace(/::/g, '/')
84 | .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
85 | .replace(/([a-z\d])([A-Z])/g, '$1_$2')
86 | .replace(/_/g, '-')
87 | .toLowerCase()
88 | }
89 | uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
90 |
91 | function classRE(name) {
92 | return name in classCache ?
93 | classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
94 | }
95 |
96 | function maybeAddPx(name, value) {
97 | return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
98 | }
99 |
100 | function defaultDisplay(nodeName) {
101 | var element, display
102 | if (!elementDisplay[nodeName]) {
103 | element = document.createElement(nodeName)
104 | document.body.appendChild(element)
105 | display = getComputedStyle(element, '').getPropertyValue("display")
106 | element.parentNode.removeChild(element)
107 | display == "none" && (display = "block")
108 | elementDisplay[nodeName] = display
109 | }
110 | return elementDisplay[nodeName]
111 | }
112 |
113 | function children(element) {
114 | return 'children' in element ?
115 | slice.call(element.children) :
116 | $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
117 | }
118 |
119 | // `$.zepto.fragment` takes a html string and an optional tag name
120 | // to generate DOM nodes nodes from the given html string.
121 | // The generated DOM nodes are returned as an array.
122 | // This function can be overriden in plugins for example to make
123 | // it compatible with browsers that don't support the DOM fully.
124 | zepto.fragment = function(html, name, properties) {
125 | var dom, nodes, container
126 |
127 | // A special case optimization for a single tag
128 | if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
129 |
130 | if (!dom) {
131 | if (html.replace) html = html.replace(tagExpanderRE, "<$1>$2>")
132 | if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
133 | if (!(name in containers)) name = '*'
134 |
135 | container = containers[name]
136 | container.innerHTML = '' + html
137 | dom = $.each(slice.call(container.childNodes), function(){
138 | container.removeChild(this)
139 | })
140 | }
141 |
142 | if (isPlainObject(properties)) {
143 | nodes = $(dom)
144 | $.each(properties, function(key, value) {
145 | if (methodAttributes.indexOf(key) > -1) nodes[key](value)
146 | else nodes.attr(key, value)
147 | })
148 | }
149 |
150 | return dom
151 | }
152 |
153 | // `$.zepto.Z` swaps out the prototype of the given `dom` array
154 | // of nodes with `$.fn` and thus supplying all the Zepto functions
155 | // to the array. Note that `__proto__` is not supported on Internet
156 | // Explorer. This method can be overriden in plugins.
157 | zepto.Z = function(dom, selector) {
158 | dom = dom || []
159 | dom.__proto__ = $.fn
160 | dom.selector = selector || ''
161 | return dom
162 | }
163 |
164 | // `$.zepto.isZ` should return `true` if the given object is a Zepto
165 | // collection. This method can be overriden in plugins.
166 | zepto.isZ = function(object) {
167 | return object instanceof zepto.Z
168 | }
169 |
170 | // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
171 | // takes a CSS selector and an optional context (and handles various
172 | // special cases).
173 | // This method can be overriden in plugins.
174 | zepto.init = function(selector, context) {
175 | var dom
176 | // If nothing given, return an empty Zepto collection
177 | if (!selector) return zepto.Z()
178 | // Optimize for string selectors
179 | else if (typeof selector == 'string') {
180 | selector = selector.trim()
181 | // If it's a html fragment, create nodes from it
182 | // Note: In both Chrome 21 and Firefox 15, DOM error 12
183 | // is thrown if the fragment doesn't begin with <
184 | if (selector[0] == '<' && fragmentRE.test(selector))
185 | dom = zepto.fragment(selector, RegExp.$1, context), selector = null
186 | // If there's a context, create a collection on that context first, and select
187 | // nodes from there
188 | else if (context !== undefined) return $(context).find(selector)
189 | // If it's a CSS selector, use it to select nodes.
190 | else dom = zepto.qsa(document, selector)
191 | }
192 | // If a function is given, call it when the DOM is ready
193 | else if (isFunction(selector)) return $(document).ready(selector)
194 | // If a Zepto collection is given, just return it
195 | else if (zepto.isZ(selector)) return selector
196 | else {
197 | // normalize array if an array of nodes is given
198 | if (isArray(selector)) dom = compact(selector)
199 | // Wrap DOM nodes.
200 | else if (isObject(selector))
201 | dom = [selector], selector = null
202 | // If it's a html fragment, create nodes from it
203 | else if (fragmentRE.test(selector))
204 | dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
205 | // If there's a context, create a collection on that context first, and select
206 | // nodes from there
207 | else if (context !== undefined) return $(context).find(selector)
208 | // And last but no least, if it's a CSS selector, use it to select nodes.
209 | else dom = zepto.qsa(document, selector)
210 | }
211 | // create a new Zepto collection from the nodes found
212 | return zepto.Z(dom, selector)
213 | }
214 |
215 | // `$` will be the base `Zepto` object. When calling this
216 | // function just call `$.zepto.init, which makes the implementation
217 | // details of selecting nodes and creating Zepto collections
218 | // patchable in plugins.
219 | $ = function(selector, context){
220 | return zepto.init(selector, context)
221 | }
222 |
223 | function extend(target, source, deep) {
224 | for (key in source)
225 | if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
226 | if (isPlainObject(source[key]) && !isPlainObject(target[key]))
227 | target[key] = {}
228 | if (isArray(source[key]) && !isArray(target[key]))
229 | target[key] = []
230 | extend(target[key], source[key], deep)
231 | }
232 | else if (source[key] !== undefined) target[key] = source[key]
233 | }
234 |
235 | // Copy all but undefined properties from one or more
236 | // objects to the `target` object.
237 | $.extend = function(target){
238 | var deep, args = slice.call(arguments, 1)
239 | if (typeof target == 'boolean') {
240 | deep = target
241 | target = args.shift()
242 | }
243 | args.forEach(function(arg){ extend(target, arg, deep) })
244 | return target
245 | }
246 |
247 | // `$.zepto.qsa` is Zepto's CSS selector implementation which
248 | // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
249 | // This method can be overriden in plugins.
250 | zepto.qsa = function(element, selector){
251 | var found,
252 | maybeID = selector[0] == '#',
253 | maybeClass = !maybeID && selector[0] == '.',
254 | nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
255 | isSimple = simpleSelectorRE.test(nameOnly)
256 | return (isDocument(element) && isSimple && maybeID) ?
257 | ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
258 | (element.nodeType !== 1 && element.nodeType !== 9) ? [] :
259 | slice.call(
260 | isSimple && !maybeID ?
261 | maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
262 | element.getElementsByTagName(selector) : // Or a tag
263 | element.querySelectorAll(selector) // Or it's not simple, and we need to query all
264 | )
265 | }
266 |
267 | function filtered(nodes, selector) {
268 | return selector == null ? $(nodes) : $(nodes).filter(selector)
269 | }
270 |
271 | $.contains = document.documentElement.contains ?
272 | function(parent, node) {
273 | return parent !== node && parent.contains(node)
274 | } :
275 | function(parent, node) {
276 | while (node && (node = node.parentNode))
277 | if (node === parent) return true
278 | return false
279 | }
280 |
281 | function funcArg(context, arg, idx, payload) {
282 | return isFunction(arg) ? arg.call(context, idx, payload) : arg
283 | }
284 |
285 | function setAttribute(node, name, value) {
286 | value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
287 | }
288 |
289 | // access className property while respecting SVGAnimatedString
290 | function className(node, value){
291 | var klass = node.className || '',
292 | svg = klass && klass.baseVal !== undefined
293 |
294 | if (value === undefined) return svg ? klass.baseVal : klass
295 | svg ? (klass.baseVal = value) : (node.className = value)
296 | }
297 |
298 | // "true" => true
299 | // "false" => false
300 | // "null" => null
301 | // "42" => 42
302 | // "42.5" => 42.5
303 | // "08" => "08"
304 | // JSON => parse if valid
305 | // String => self
306 | function deserializeValue(value) {
307 | try {
308 | return value ?
309 | value == "true" ||
310 | ( value == "false" ? false :
311 | value == "null" ? null :
312 | +value + "" == value ? +value :
313 | /^[\[\{]/.test(value) ? $.parseJSON(value) :
314 | value )
315 | : value
316 | } catch(e) {
317 | return value
318 | }
319 | }
320 |
321 | $.type = type
322 | $.isFunction = isFunction
323 | $.isWindow = isWindow
324 | $.isArray = isArray
325 | $.isPlainObject = isPlainObject
326 |
327 | $.isEmptyObject = function(obj) {
328 | var name
329 | for (name in obj) return false
330 | return true
331 | }
332 |
333 | $.inArray = function(elem, array, i){
334 | return emptyArray.indexOf.call(array, elem, i)
335 | }
336 |
337 | $.camelCase = camelize
338 | $.trim = function(str) {
339 | return str == null ? "" : String.prototype.trim.call(str)
340 | }
341 |
342 | // plugin compatibility
343 | $.uuid = 0
344 | $.support = { }
345 | $.expr = { }
346 |
347 | $.map = function(elements, callback){
348 | var value, values = [], i, key
349 | if (likeArray(elements))
350 | for (i = 0; i < elements.length; i++) {
351 | value = callback(elements[i], i)
352 | if (value != null) values.push(value)
353 | }
354 | else
355 | for (key in elements) {
356 | value = callback(elements[key], key)
357 | if (value != null) values.push(value)
358 | }
359 | return flatten(values)
360 | }
361 |
362 | $.each = function(elements, callback){
363 | var i, key
364 | if (likeArray(elements)) {
365 | for (i = 0; i < elements.length; i++)
366 | if (callback.call(elements[i], i, elements[i]) === false) return elements
367 | } else {
368 | for (key in elements)
369 | if (callback.call(elements[key], key, elements[key]) === false) return elements
370 | }
371 |
372 | return elements
373 | }
374 |
375 | $.grep = function(elements, callback){
376 | return filter.call(elements, callback)
377 | }
378 |
379 | if (window.JSON) $.parseJSON = JSON.parse
380 |
381 | // Populate the class2type map
382 | $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
383 | class2type[ "[object " + name + "]" ] = name.toLowerCase()
384 | })
385 |
386 | // Define methods that will be available on all
387 | // Zepto collections
388 | $.fn = {
389 | // Because a collection acts like an array
390 | // copy over these useful array functions.
391 | forEach: emptyArray.forEach,
392 | reduce: emptyArray.reduce,
393 | push: emptyArray.push,
394 | sort: emptyArray.sort,
395 | indexOf: emptyArray.indexOf,
396 | concat: emptyArray.concat,
397 |
398 | // `map` and `slice` in the jQuery API work differently
399 | // from their array counterparts
400 | map: function(fn){
401 | return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
402 | },
403 | slice: function(){
404 | return $(slice.apply(this, arguments))
405 | },
406 |
407 | ready: function(callback){
408 | // need to check if document.body exists for IE as that browser reports
409 | // document ready when it hasn't yet created the body element
410 | if (readyRE.test(document.readyState) && document.body) callback($)
411 | else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
412 | return this
413 | },
414 | get: function(idx){
415 | return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
416 | },
417 | toArray: function(){ return this.get() },
418 | size: function(){
419 | return this.length
420 | },
421 | remove: function(){
422 | return this.each(function(){
423 | if (this.parentNode != null)
424 | this.parentNode.removeChild(this)
425 | })
426 | },
427 | each: function(callback){
428 | emptyArray.every.call(this, function(el, idx){
429 | return callback.call(el, idx, el) !== false
430 | })
431 | return this
432 | },
433 | filter: function(selector){
434 | if (isFunction(selector)) return this.not(this.not(selector))
435 | return $(filter.call(this, function(element){
436 | return zepto.matches(element, selector)
437 | }))
438 | },
439 | add: function(selector,context){
440 | return $(uniq(this.concat($(selector,context))))
441 | },
442 | is: function(selector){
443 | return this.length > 0 && zepto.matches(this[0], selector)
444 | },
445 | not: function(selector){
446 | var nodes=[]
447 | if (isFunction(selector) && selector.call !== undefined)
448 | this.each(function(idx){
449 | if (!selector.call(this,idx)) nodes.push(this)
450 | })
451 | else {
452 | var excludes = typeof selector == 'string' ? this.filter(selector) :
453 | (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
454 | this.forEach(function(el){
455 | if (excludes.indexOf(el) < 0) nodes.push(el)
456 | })
457 | }
458 | return $(nodes)
459 | },
460 | has: function(selector){
461 | return this.filter(function(){
462 | return isObject(selector) ?
463 | $.contains(this, selector) :
464 | $(this).find(selector).size()
465 | })
466 | },
467 | eq: function(idx){
468 | return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
469 | },
470 | first: function(){
471 | var el = this[0]
472 | return el && !isObject(el) ? el : $(el)
473 | },
474 | last: function(){
475 | var el = this[this.length - 1]
476 | return el && !isObject(el) ? el : $(el)
477 | },
478 | find: function(selector){
479 | var result, $this = this
480 | if (!selector) result = $()
481 | else if (typeof selector == 'object')
482 | result = $(selector).filter(function(){
483 | var node = this
484 | return emptyArray.some.call($this, function(parent){
485 | return $.contains(parent, node)
486 | })
487 | })
488 | else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
489 | else result = this.map(function(){ return zepto.qsa(this, selector) })
490 | return result
491 | },
492 | closest: function(selector, context){
493 | var node = this[0], collection = false
494 | if (typeof selector == 'object') collection = $(selector)
495 | while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
496 | node = node !== context && !isDocument(node) && node.parentNode
497 | return $(node)
498 | },
499 | parents: function(selector){
500 | var ancestors = [], nodes = this
501 | while (nodes.length > 0)
502 | nodes = $.map(nodes, function(node){
503 | if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
504 | ancestors.push(node)
505 | return node
506 | }
507 | })
508 | return filtered(ancestors, selector)
509 | },
510 | parent: function(selector){
511 | return filtered(uniq(this.pluck('parentNode')), selector)
512 | },
513 | children: function(selector){
514 | return filtered(this.map(function(){ return children(this) }), selector)
515 | },
516 | contents: function() {
517 | return this.map(function() { return slice.call(this.childNodes) })
518 | },
519 | siblings: function(selector){
520 | return filtered(this.map(function(i, el){
521 | return filter.call(children(el.parentNode), function(child){ return child!==el })
522 | }), selector)
523 | },
524 | empty: function(){
525 | return this.each(function(){ this.innerHTML = '' })
526 | },
527 | // `pluck` is borrowed from Prototype.js
528 | pluck: function(property){
529 | return $.map(this, function(el){ return el[property] })
530 | },
531 | show: function(){
532 | return this.each(function(){
533 | this.style.display == "none" && (this.style.display = '')
534 | if (getComputedStyle(this, '').getPropertyValue("display") == "none")
535 | this.style.display = defaultDisplay(this.nodeName)
536 | })
537 | },
538 | replaceWith: function(newContent){
539 | return this.before(newContent).remove()
540 | },
541 | wrap: function(structure){
542 | var func = isFunction(structure)
543 | if (this[0] && !func)
544 | var dom = $(structure).get(0),
545 | clone = dom.parentNode || this.length > 1
546 |
547 | return this.each(function(index){
548 | $(this).wrapAll(
549 | func ? structure.call(this, index) :
550 | clone ? dom.cloneNode(true) : dom
551 | )
552 | })
553 | },
554 | wrapAll: function(structure){
555 | if (this[0]) {
556 | $(this[0]).before(structure = $(structure))
557 | var children
558 | // drill down to the inmost element
559 | while ((children = structure.children()).length) structure = children.first()
560 | $(structure).append(this)
561 | }
562 | return this
563 | },
564 | wrapInner: function(structure){
565 | var func = isFunction(structure)
566 | return this.each(function(index){
567 | var self = $(this), contents = self.contents(),
568 | dom = func ? structure.call(this, index) : structure
569 | contents.length ? contents.wrapAll(dom) : self.append(dom)
570 | })
571 | },
572 | unwrap: function(){
573 | this.parent().each(function(){
574 | $(this).replaceWith($(this).children())
575 | })
576 | return this
577 | },
578 | clone: function(){
579 | return this.map(function(){ return this.cloneNode(true) })
580 | },
581 | hide: function(){
582 | return this.css("display", "none")
583 | },
584 | toggle: function(setting){
585 | return this.each(function(){
586 | var el = $(this)
587 | ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
588 | })
589 | },
590 | prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },
591 | next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },
592 | html: function(html){
593 | return 0 in arguments ?
594 | this.each(function(idx){
595 | var originHtml = this.innerHTML
596 | $(this).empty().append( funcArg(this, html, idx, originHtml) )
597 | }) :
598 | (0 in this ? this[0].innerHTML : null)
599 | },
600 | text: function(text){
601 | return 0 in arguments ?
602 | this.each(function(idx){
603 | var newText = funcArg(this, text, idx, this.textContent)
604 | this.textContent = newText == null ? '' : ''+newText
605 | }) :
606 | (0 in this ? this[0].textContent : null)
607 | },
608 | attr: function(name, value){
609 | var result
610 | return (typeof name == 'string' && !(1 in arguments)) ?
611 | (!this.length || this[0].nodeType !== 1 ? undefined :
612 | (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
613 | ) :
614 | this.each(function(idx){
615 | if (this.nodeType !== 1) return
616 | if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
617 | else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
618 | })
619 | },
620 | removeAttr: function(name){
621 | return this.each(function(){ this.nodeType === 1 && name.split(' ').forEach(function(attribute){
622 | setAttribute(this, attribute)
623 | }, this)})
624 | },
625 | prop: function(name, value){
626 | name = propMap[name] || name
627 | return (1 in arguments) ?
628 | this.each(function(idx){
629 | this[name] = funcArg(this, value, idx, this[name])
630 | }) :
631 | (this[0] && this[0][name])
632 | },
633 | data: function(name, value){
634 | var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase()
635 |
636 | var data = (1 in arguments) ?
637 | this.attr(attrName, value) :
638 | this.attr(attrName)
639 |
640 | return data !== null ? deserializeValue(data) : undefined
641 | },
642 | val: function(value){
643 | return 0 in arguments ?
644 | this.each(function(idx){
645 | this.value = funcArg(this, value, idx, this.value)
646 | }) :
647 | (this[0] && (this[0].multiple ?
648 | $(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
649 | this[0].value)
650 | )
651 | },
652 | offset: function(coordinates){
653 | if (coordinates) return this.each(function(index){
654 | var $this = $(this),
655 | coords = funcArg(this, coordinates, index, $this.offset()),
656 | parentOffset = $this.offsetParent().offset(),
657 | props = {
658 | top: coords.top - parentOffset.top,
659 | left: coords.left - parentOffset.left
660 | }
661 |
662 | if ($this.css('position') == 'static') props['position'] = 'relative'
663 | $this.css(props)
664 | })
665 | if (!this.length) return null
666 | var obj = this[0].getBoundingClientRect()
667 | return {
668 | left: obj.left + window.pageXOffset,
669 | top: obj.top + window.pageYOffset,
670 | width: Math.round(obj.width),
671 | height: Math.round(obj.height)
672 | }
673 | },
674 | css: function(property, value){
675 | if (arguments.length < 2) {
676 | var computedStyle, element = this[0]
677 | if(!element) return
678 | computedStyle = getComputedStyle(element, '')
679 | if (typeof property == 'string')
680 | return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
681 | else if (isArray(property)) {
682 | var props = {}
683 | $.each(property, function(_, prop){
684 | props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
685 | })
686 | return props
687 | }
688 | }
689 |
690 | var css = ''
691 | if (type(property) == 'string') {
692 | if (!value && value !== 0)
693 | this.each(function(){ this.style.removeProperty(dasherize(property)) })
694 | else
695 | css = dasherize(property) + ":" + maybeAddPx(property, value)
696 | } else {
697 | for (key in property)
698 | if (!property[key] && property[key] !== 0)
699 | this.each(function(){ this.style.removeProperty(dasherize(key)) })
700 | else
701 | css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
702 | }
703 |
704 | return this.each(function(){ this.style.cssText += ';' + css })
705 | },
706 | index: function(element){
707 | return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
708 | },
709 | hasClass: function(name){
710 | if (!name) return false
711 | return emptyArray.some.call(this, function(el){
712 | return this.test(className(el))
713 | }, classRE(name))
714 | },
715 | addClass: function(name){
716 | if (!name) return this
717 | return this.each(function(idx){
718 | if (!('className' in this)) return
719 | classList = []
720 | var cls = className(this), newName = funcArg(this, name, idx, cls)
721 | newName.split(/\s+/g).forEach(function(klass){
722 | if (!$(this).hasClass(klass)) classList.push(klass)
723 | }, this)
724 | classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
725 | })
726 | },
727 | removeClass: function(name){
728 | return this.each(function(idx){
729 | if (!('className' in this)) return
730 | if (name === undefined) return className(this, '')
731 | classList = className(this)
732 | funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
733 | classList = classList.replace(classRE(klass), " ")
734 | })
735 | className(this, classList.trim())
736 | })
737 | },
738 | toggleClass: function(name, when){
739 | if (!name) return this
740 | return this.each(function(idx){
741 | var $this = $(this), names = funcArg(this, name, idx, className(this))
742 | names.split(/\s+/g).forEach(function(klass){
743 | (when === undefined ? !$this.hasClass(klass) : when) ?
744 | $this.addClass(klass) : $this.removeClass(klass)
745 | })
746 | })
747 | },
748 | scrollTop: function(value){
749 | if (!this.length) return
750 | var hasScrollTop = 'scrollTop' in this[0]
751 | if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
752 | return this.each(hasScrollTop ?
753 | function(){ this.scrollTop = value } :
754 | function(){ this.scrollTo(this.scrollX, value) })
755 | },
756 | scrollLeft: function(value){
757 | if (!this.length) return
758 | var hasScrollLeft = 'scrollLeft' in this[0]
759 | if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
760 | return this.each(hasScrollLeft ?
761 | function(){ this.scrollLeft = value } :
762 | function(){ this.scrollTo(value, this.scrollY) })
763 | },
764 | position: function() {
765 | if (!this.length) return
766 |
767 | var elem = this[0],
768 | // Get *real* offsetParent
769 | offsetParent = this.offsetParent(),
770 | // Get correct offsets
771 | offset = this.offset(),
772 | parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
773 |
774 | // Subtract element margins
775 | // note: when an element has margin: auto the offsetLeft and marginLeft
776 | // are the same in Safari causing offset.left to incorrectly be 0
777 | offset.top -= parseFloat( $(elem).css('margin-top') ) || 0
778 | offset.left -= parseFloat( $(elem).css('margin-left') ) || 0
779 |
780 | // Add offsetParent borders
781 | parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
782 | parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0
783 |
784 | // Subtract the two offsets
785 | return {
786 | top: offset.top - parentOffset.top,
787 | left: offset.left - parentOffset.left
788 | }
789 | },
790 | offsetParent: function() {
791 | return this.map(function(){
792 | var parent = this.offsetParent || document.body
793 | while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
794 | parent = parent.offsetParent
795 | return parent
796 | })
797 | }
798 | }
799 |
800 | // for now
801 | $.fn.detach = $.fn.remove
802 |
803 | // Generate the `width` and `height` functions
804 | ;['width', 'height'].forEach(function(dimension){
805 | var dimensionProperty =
806 | dimension.replace(/./, function(m){ return m[0].toUpperCase() })
807 |
808 | $.fn[dimension] = function(value){
809 | var offset, el = this[0]
810 | if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] :
811 | isDocument(el) ? el.documentElement['scroll' + dimensionProperty] :
812 | (offset = this.offset()) && offset[dimension]
813 | else return this.each(function(idx){
814 | el = $(this)
815 | el.css(dimension, funcArg(this, value, idx, el[dimension]()))
816 | })
817 | }
818 | })
819 |
820 | function traverseNode(node, fun) {
821 | fun(node)
822 | for (var i = 0, len = node.childNodes.length; i < len; i++)
823 | traverseNode(node.childNodes[i], fun)
824 | }
825 |
826 | // Generate the `after`, `prepend`, `before`, `append`,
827 | // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
828 | adjacencyOperators.forEach(function(operator, operatorIndex) {
829 | var inside = operatorIndex % 2 //=> prepend, append
830 |
831 | $.fn[operator] = function(){
832 | // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
833 | var argType, nodes = $.map(arguments, function(arg) {
834 | argType = type(arg)
835 | return argType == "object" || argType == "array" || arg == null ?
836 | arg : zepto.fragment(arg)
837 | }),
838 | parent, copyByClone = this.length > 1
839 | if (nodes.length < 1) return this
840 |
841 | return this.each(function(_, target){
842 | parent = inside ? target : target.parentNode
843 |
844 | // convert all methods to a "before" operation
845 | target = operatorIndex == 0 ? target.nextSibling :
846 | operatorIndex == 1 ? target.firstChild :
847 | operatorIndex == 2 ? target :
848 | null
849 |
850 | var parentInDocument = $.contains(document.documentElement, parent)
851 |
852 | nodes.forEach(function(node){
853 | if (copyByClone) node = node.cloneNode(true)
854 | else if (!parent) return $(node).remove()
855 |
856 | parent.insertBefore(node, target)
857 | if (parentInDocument) traverseNode(node, function(el){
858 | if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
859 | (!el.type || el.type === 'text/javascript') && !el.src)
860 | window['eval'].call(window, el.innerHTML)
861 | })
862 | })
863 | })
864 | }
865 |
866 | // after => insertAfter
867 | // prepend => prependTo
868 | // before => insertBefore
869 | // append => appendTo
870 | $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
871 | $(html)[operator](this)
872 | return this
873 | }
874 | })
875 |
876 | zepto.Z.prototype = $.fn
877 |
878 | // Export internal API functions in the `$.zepto` namespace
879 | zepto.uniq = uniq
880 | zepto.deserializeValue = deserializeValue
881 | $.zepto = zepto
882 |
883 | return $
884 | })()
885 |
886 | // If `$` is not yet defined, point it to `Zepto`
887 | window.Zepto = Zepto
888 | window.$ === undefined && (window.$ = Zepto)
889 |
890 |
891 | // Zepto.js
892 | // (c) 2010-2016 Thomas Fuchs
893 | // Zepto.js may be freely distributed under the MIT license.
894 |
895 | ;(function($){
896 | var _zid = 1, undefined,
897 | slice = Array.prototype.slice,
898 | isFunction = $.isFunction,
899 | isString = function(obj){ return typeof obj == 'string' },
900 | handlers = {},
901 | specialEvents={},
902 | focusinSupported = 'onfocusin' in window,
903 | focus = { focus: 'focusin', blur: 'focusout' },
904 | hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
905 |
906 | specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
907 |
908 | function zid(element) {
909 | return element._zid || (element._zid = _zid++)
910 | }
911 | function findHandlers(element, event, fn, selector) {
912 | event = parse(event)
913 | if (event.ns) var matcher = matcherFor(event.ns)
914 | return (handlers[zid(element)] || []).filter(function(handler) {
915 | return handler
916 | && (!event.e || handler.e == event.e)
917 | && (!event.ns || matcher.test(handler.ns))
918 | && (!fn || zid(handler.fn) === zid(fn))
919 | && (!selector || handler.sel == selector)
920 | })
921 | }
922 | function parse(event) {
923 | var parts = ('' + event).split('.')
924 | return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
925 | }
926 | function matcherFor(ns) {
927 | return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
928 | }
929 |
930 | function eventCapture(handler, captureSetting) {
931 | return handler.del &&
932 | (!focusinSupported && (handler.e in focus)) ||
933 | !!captureSetting
934 | }
935 |
936 | function realEvent(type) {
937 | return hover[type] || (focusinSupported && focus[type]) || type
938 | }
939 |
940 | function add(element, events, fn, data, selector, delegator, capture){
941 | var id = zid(element), set = (handlers[id] || (handlers[id] = []))
942 | events.split(/\s/).forEach(function(event){
943 | if (event == 'ready') return $(document).ready(fn)
944 | var handler = parse(event)
945 | handler.fn = fn
946 | handler.sel = selector
947 | // emulate mouseenter, mouseleave
948 | if (handler.e in hover) fn = function(e){
949 | var related = e.relatedTarget
950 | if (!related || (related !== this && !$.contains(this, related)))
951 | return handler.fn.apply(this, arguments)
952 | }
953 | handler.del = delegator
954 | var callback = delegator || fn
955 | handler.proxy = function(e){
956 | e = compatible(e)
957 | if (e.isImmediatePropagationStopped()) return
958 | e.data = data
959 | var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
960 | if (result === false) e.preventDefault(), e.stopPropagation()
961 | return result
962 | }
963 | handler.i = set.length
964 | set.push(handler)
965 | if ('addEventListener' in element)
966 | element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
967 | })
968 | }
969 | function remove(element, events, fn, selector, capture){
970 | var id = zid(element)
971 | ;(events || '').split(/\s/).forEach(function(event){
972 | findHandlers(element, event, fn, selector).forEach(function(handler){
973 | delete handlers[id][handler.i]
974 | if ('removeEventListener' in element)
975 | element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
976 | })
977 | })
978 | }
979 |
980 | $.event = { add: add, remove: remove }
981 |
982 | $.proxy = function(fn, context) {
983 | var args = (2 in arguments) && slice.call(arguments, 2)
984 | if (isFunction(fn)) {
985 | var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }
986 | proxyFn._zid = zid(fn)
987 | return proxyFn
988 | } else if (isString(context)) {
989 | if (args) {
990 | args.unshift(fn[context], fn)
991 | return $.proxy.apply(null, args)
992 | } else {
993 | return $.proxy(fn[context], fn)
994 | }
995 | } else {
996 | throw new TypeError("expected function")
997 | }
998 | }
999 |
1000 | $.fn.bind = function(event, data, callback){
1001 | return this.on(event, data, callback)
1002 | }
1003 | $.fn.unbind = function(event, callback){
1004 | return this.off(event, callback)
1005 | }
1006 | $.fn.one = function(event, selector, data, callback){
1007 | return this.on(event, selector, data, callback, 1)
1008 | }
1009 |
1010 | var returnTrue = function(){return true},
1011 | returnFalse = function(){return false},
1012 | ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,
1013 | eventMethods = {
1014 | preventDefault: 'isDefaultPrevented',
1015 | stopImmediatePropagation: 'isImmediatePropagationStopped',
1016 | stopPropagation: 'isPropagationStopped'
1017 | }
1018 |
1019 | function compatible(event, source) {
1020 | if (source || !event.isDefaultPrevented) {
1021 | source || (source = event)
1022 |
1023 | $.each(eventMethods, function(name, predicate) {
1024 | var sourceMethod = source[name]
1025 | event[name] = function(){
1026 | this[predicate] = returnTrue
1027 | return sourceMethod && sourceMethod.apply(source, arguments)
1028 | }
1029 | event[predicate] = returnFalse
1030 | })
1031 |
1032 | if (source.defaultPrevented !== undefined ? source.defaultPrevented :
1033 | 'returnValue' in source ? source.returnValue === false :
1034 | source.getPreventDefault && source.getPreventDefault())
1035 | event.isDefaultPrevented = returnTrue
1036 | }
1037 | return event
1038 | }
1039 |
1040 | function createProxy(event) {
1041 | var key, proxy = { originalEvent: event }
1042 | for (key in event)
1043 | if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]
1044 |
1045 | return compatible(proxy, event)
1046 | }
1047 |
1048 | $.fn.delegate = function(selector, event, callback){
1049 | return this.on(event, selector, callback)
1050 | }
1051 | $.fn.undelegate = function(selector, event, callback){
1052 | return this.off(event, selector, callback)
1053 | }
1054 |
1055 | $.fn.live = function(event, callback){
1056 | $(document.body).delegate(this.selector, event, callback)
1057 | return this
1058 | }
1059 | $.fn.die = function(event, callback){
1060 | $(document.body).undelegate(this.selector, event, callback)
1061 | return this
1062 | }
1063 |
1064 | $.fn.on = function(event, selector, data, callback, one){
1065 | var autoRemove, delegator, $this = this
1066 | if (event && !isString(event)) {
1067 | $.each(event, function(type, fn){
1068 | $this.on(type, selector, data, fn, one)
1069 | })
1070 | return $this
1071 | }
1072 |
1073 | if (!isString(selector) && !isFunction(callback) && callback !== false)
1074 | callback = data, data = selector, selector = undefined
1075 | if (callback === undefined || data === false)
1076 | callback = data, data = undefined
1077 |
1078 | if (callback === false) callback = returnFalse
1079 |
1080 | return $this.each(function(_, element){
1081 | if (one) autoRemove = function(e){
1082 | remove(element, e.type, callback)
1083 | return callback.apply(this, arguments)
1084 | }
1085 |
1086 | if (selector) delegator = function(e){
1087 | var evt, match = $(e.target).closest(selector, element).get(0)
1088 | if (match && match !== element) {
1089 | evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
1090 | return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
1091 | }
1092 | }
1093 |
1094 | add(element, event, callback, data, selector, delegator || autoRemove)
1095 | })
1096 | }
1097 | $.fn.off = function(event, selector, callback){
1098 | var $this = this
1099 | if (event && !isString(event)) {
1100 | $.each(event, function(type, fn){
1101 | $this.off(type, selector, fn)
1102 | })
1103 | return $this
1104 | }
1105 |
1106 | if (!isString(selector) && !isFunction(callback) && callback !== false)
1107 | callback = selector, selector = undefined
1108 |
1109 | if (callback === false) callback = returnFalse
1110 |
1111 | return $this.each(function(){
1112 | remove(this, event, callback, selector)
1113 | })
1114 | }
1115 |
1116 | $.fn.trigger = function(event, args){
1117 | event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
1118 | event._args = args
1119 | return this.each(function(){
1120 | // handle focus(), blur() by calling them directly
1121 | if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
1122 | // items in the collection might not be DOM elements
1123 | else if ('dispatchEvent' in this) this.dispatchEvent(event)
1124 | else $(this).triggerHandler(event, args)
1125 | })
1126 | }
1127 |
1128 | // triggers event handlers on current element just as if an event occurred,
1129 | // doesn't trigger an actual event, doesn't bubble
1130 | $.fn.triggerHandler = function(event, args){
1131 | var e, result
1132 | this.each(function(i, element){
1133 | e = createProxy(isString(event) ? $.Event(event) : event)
1134 | e._args = args
1135 | e.target = element
1136 | $.each(findHandlers(element, event.type || event), function(i, handler){
1137 | result = handler.proxy(e)
1138 | if (e.isImmediatePropagationStopped()) return false
1139 | })
1140 | })
1141 | return result
1142 | }
1143 |
1144 | // shortcut methods for `.bind(event, fn)` for each event type
1145 | ;('focusin focusout focus blur load resize scroll unload click dblclick '+
1146 | 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+
1147 | 'change select keydown keypress keyup error').split(' ').forEach(function(event) {
1148 | $.fn[event] = function(callback) {
1149 | return (0 in arguments) ?
1150 | this.bind(event, callback) :
1151 | this.trigger(event)
1152 | }
1153 | })
1154 |
1155 | $.Event = function(type, props) {
1156 | if (!isString(type)) props = type, type = props.type
1157 | var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
1158 | if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
1159 | event.initEvent(type, bubbles, true)
1160 | return compatible(event)
1161 | }
1162 |
1163 | })(Zepto)
1164 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "badjs-report",
3 | "version": "1.3.4",
4 | "main": "dist/bj-report-tryjs.js",
5 | "description": "client report",
6 | "homepage": "https://github.com/BetterJS/badjs-report",
7 | "scripts": {
8 | "test": "grunt dist",
9 | "GFW": "npm install --phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs"
10 | },
11 | "author": [
12 | {
13 | "name": "yorts52",
14 | "email": "yorts52@qq.com"
15 | },
16 | {
17 | "name": "chriscai",
18 | "email": "caihuijigood@gmail.com"
19 | }
20 | ],
21 | "repository": {
22 | "url": "https://github.com/BetterJS/badjs-report.git"
23 | },
24 | "bugs": "https://github.com/BetterJS/badjs-report.git/issues",
25 | "license": "MIT",
26 | "keywords": [
27 | "BetterJS",
28 | "badjs",
29 | "report"
30 | ],
31 | "devDependencies": {
32 | "grunt": "1.0.2",
33 | "mocha": "~5.0",
34 | "chai": "~4.1.2",
35 | "grunt-mocha": "1.0",
36 | "requirejs": "^2.3.5",
37 | "jquery": "*",
38 | "grunt-contrib-copy": "~1.0.0",
39 | "grunt-contrib-clean": "^1.1.0",
40 | "grunt-contrib-jshint": "~1.1.0",
41 | "grunt-contrib-uglify": "^3.3.0",
42 | "grunt-contrib-concat": "~1.0.1"
43 | },
44 | "lego": {
45 | "category": "35",
46 | "categoryName": "基础框架",
47 | "main": "./dist/bj-report-tryjs.js",
48 | "dependencies": {},
49 | "devDependencies": {}
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/bj-report.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @module report
3 | * @author kael, chriscai
4 | * @date @DATE
5 | * Copyright (c) 2014 kael, chriscai
6 | * Licensed under the MIT license.
7 | */
8 | var BJ_REPORT = (function(global) {
9 | if (global.BJ_REPORT) return global.BJ_REPORT;
10 |
11 | var _log_list = [];
12 | var _log_map = {};
13 | var _config = {
14 | id: 0, // 上报 id
15 | uin: 0, // user id
16 | url: "", // 上报 接口
17 | offline_url: "", // 离线日志上报 接口
18 | offline_auto_url: "", // 检测是否自动上报
19 | ext: null, // 扩展参数 用于自定义上报
20 | level: 4, // 错误级别 1-debug 2-info 4-error
21 | ignore: [], // 忽略某个错误, 支持 Regexp 和 Function
22 | random: 1, // 抽样 (0-1] 1-全量
23 | delay: 1000, // 延迟上报 combo 为 true 时有效
24 | submit: null, // 自定义上报方式
25 | repeat: 5, // 重复上报次数(对于同一个错误超过多少次不上报),
26 | offlineLog: false,
27 | offlineLogExp: 5, // 离线日志过期时间 , 默认5天
28 | offlineLogAuto: false, //是否自动询问服务器需要自动上报
29 | };
30 |
31 | var Offline_DB = {
32 | db: null,
33 | ready: function(callback) {
34 | var self = this;
35 | if (!window.indexedDB || !_config.offlineLog) {
36 | _config.offlineLog = false;
37 | return callback();
38 | }
39 |
40 | if (this.db) {
41 | setTimeout(function() {
42 | callback(null, self);
43 | }, 0);
44 |
45 | return;
46 | }
47 | var version = 1;
48 | var request = window.indexedDB.open("badjs", version);
49 |
50 | if (!request) {
51 | _config.offlineLog = false;
52 | return callback();
53 | }
54 |
55 | request.onerror = function(e) {
56 | callback(e);
57 | _config.offlineLog = false;
58 | console.log("indexdb request error");
59 | return true;
60 | };
61 | request.onsuccess = function(e) {
62 | self.db = e.target.result;
63 |
64 | setTimeout(function() {
65 | callback(null, self);
66 | }, 500);
67 |
68 |
69 | };
70 | request.onupgradeneeded = function(e) {
71 | var db = e.target.result;
72 | if (!db.objectStoreNames.contains('logs')) {
73 | db.createObjectStore('logs', { autoIncrement: true });
74 | }
75 | };
76 | },
77 | insertToDB: function(log) {
78 | var store = this.getStore();
79 | store.add(log);
80 | },
81 | addLog: function(log) {
82 | if (!this.db) {
83 | return;
84 | }
85 | this.insertToDB(log);
86 | },
87 | addLogs: function(logs) {
88 | if (!this.db) {
89 | return;
90 | }
91 |
92 | for (var i = 0; i < logs.length; i++) {
93 | this.addLog(logs[i]);
94 | }
95 |
96 | },
97 | getLogs: function(opt, callback) {
98 | if (!this.db) {
99 | return;
100 | }
101 | var store = this.getStore();
102 | var request = store.openCursor();
103 | var result = [];
104 | request.onsuccess = function(event) {
105 | var cursor = event.target.result;
106 | if (cursor) {
107 | if (cursor.value.time >= opt.start && cursor.value.time <= opt.end && cursor.value.id == opt.id && cursor.value.uin == opt.uin) {
108 | result.push(cursor.value);
109 | }
110 | //# cursor.continue
111 | cursor["continue"]();
112 | } else {
113 | callback(null, result);
114 | }
115 | };
116 |
117 | request.onerror = function(e) {
118 | callback(e);
119 | return true;
120 | };
121 | },
122 | clearDB: function(daysToMaintain) {
123 | if (!this.db) {
124 | return;
125 | }
126 |
127 | var store = this.getStore();
128 | if (!daysToMaintain) {
129 | store.clear();
130 | return;
131 | }
132 | var range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000);
133 | var request = store.openCursor();
134 | request.onsuccess = function(event) {
135 | var cursor = event.target.result;
136 | if (cursor && (cursor.value.time < range || !cursor.value.time)) {
137 | store["delete"](cursor.primaryKey);
138 | cursor["continue"]();
139 | }
140 | };
141 | },
142 |
143 | getStore: function() {
144 | var transaction = this.db.transaction("logs", 'readwrite');
145 | return transaction.objectStore("logs");
146 | },
147 |
148 | };
149 |
150 | var T = {
151 | isOBJByType: function(o, type) {
152 | return Object.prototype.toString.call(o) === "[object " + (type || "Object") + "]";
153 | },
154 |
155 | isOBJ: function(obj) {
156 | var type = typeof obj;
157 | return type === "object" && !!obj;
158 | },
159 | isEmpty: function(obj) {
160 | if (obj === null) return true;
161 | if (T.isOBJByType(obj, "Number")) {
162 | return false;
163 | }
164 | return !obj;
165 | },
166 | extend: function(src, source) {
167 | for (var key in source) {
168 | src[key] = source[key];
169 | }
170 | return src;
171 | },
172 | processError: function(errObj) {
173 | try {
174 | if (errObj.stack) {
175 | var url = errObj.stack.match("https?://[^\n]+");
176 | url = url ? url[0] : "";
177 | var rowCols = url.match(":(\\d+):(\\d+)");
178 | if (!rowCols) {
179 | rowCols = [0, 0, 0];
180 | }
181 |
182 | var stack = T.processStackMsg(errObj);
183 | return {
184 | msg: stack,
185 | rowNum: rowCols[1],
186 | colNum: rowCols[2],
187 | target: url.replace(rowCols[0], ""),
188 | _orgMsg: errObj.toString()
189 | };
190 | } else {
191 | //ie 独有 error 对象信息,try-catch 捕获到错误信息传过来,造成没有msg
192 | if (errObj.name && errObj.message && errObj.description) {
193 | return {
194 | msg: JSON.stringify(errObj)
195 | };
196 | }
197 | return errObj;
198 | }
199 | } catch (err) {
200 | return errObj;
201 | }
202 | },
203 |
204 | processStackMsg: function(error) {
205 | var stack = error.stack
206 | .replace(/\n/gi, "")
207 | .split(/\bat\b/)
208 | .slice(0, 9)
209 | .join("@")
210 | .replace(/\?[^:]+/gi, "");
211 | var msg = error.toString();
212 | if (stack.indexOf(msg) < 0) {
213 | stack = msg + "@" + stack;
214 | }
215 | return stack;
216 | },
217 |
218 | isRepeat: function(error) {
219 | if (!T.isOBJ(error)) return true;
220 | var msg = error.msg;
221 | var times = _log_map[msg] = (parseInt(_log_map[msg], 10) || 0) + 1;
222 | return times > _config.repeat;
223 | }
224 | };
225 |
226 | var orgError = global.onerror;
227 | // rewrite window.oerror
228 | global.onerror = function(msg, url, line, col, error) {
229 | var newMsg = msg;
230 |
231 | if (error && error.stack) {
232 | newMsg = T.processStackMsg(error);
233 | }
234 |
235 | if (T.isOBJByType(newMsg, "Event")) {
236 | newMsg += newMsg.type ?
237 | ("--" + newMsg.type + "--" + (newMsg.target ?
238 | (newMsg.target.tagName + "::" + newMsg.target.src) : "")) : "";
239 | }
240 |
241 | report.push({
242 | msg: newMsg,
243 | target: url,
244 | rowNum: line,
245 | colNum: col,
246 | _orgMsg: msg
247 | });
248 |
249 | _process_log();
250 | orgError && orgError.apply(global, arguments);
251 | };
252 |
253 |
254 |
255 | var _report_log_tostring = function(error, index) {
256 | var param = [];
257 | var params = [];
258 | var stringify = [];
259 | if (T.isOBJ(error)) {
260 | error.level = error.level || _config.level;
261 | for (var key in error) {
262 | var value = error[key];
263 | if (!T.isEmpty(value)) {
264 | if (T.isOBJ(value)) {
265 | try {
266 | value = JSON.stringify(value);
267 | } catch (err) {
268 | value = "[BJ_REPORT detect value stringify error] " + err.toString();
269 | }
270 | }
271 | stringify.push(key + ":" + value);
272 | param.push(key + "=" + encodeURIComponent(value));
273 | params.push(key + "[" + index + "]=" + encodeURIComponent(value));
274 | }
275 | }
276 | }
277 |
278 | // msg[0]=msg&target[0]=target -- combo report
279 | // msg:msg,target:target -- ignore
280 | // msg=msg&target=target -- report with out combo
281 | return [params.join("&"), stringify.join(","), param.join("&")];
282 | };
283 |
284 |
285 |
286 | var _offline_buffer = [];
287 | var _save2Offline = function(key, msgObj) {
288 | msgObj = T.extend({ id: _config.id, uin: _config.uin, time: new Date - 0 }, msgObj);
289 |
290 | if (Offline_DB.db) {
291 | Offline_DB.addLog(msgObj);
292 | return;
293 | }
294 |
295 |
296 | if (!Offline_DB.db && !_offline_buffer.length) {
297 | Offline_DB.ready(function(err, DB) {
298 | if (DB) {
299 | if (_offline_buffer.length) {
300 | DB.addLogs(_offline_buffer);
301 | _offline_buffer = [];
302 | }
303 |
304 | }
305 | });
306 | }
307 | _offline_buffer.push(msgObj);
308 | };
309 |
310 | var _autoReportOffline = function() {
311 | var script = document.createElement("script");
312 | script.src = _config.offline_auto_url || _config.url.replace(/badjs$/, "offlineAuto") + "?id=" + _config.id + "&uin=" + _config.uin;
313 | window._badjsOfflineAuto = function(isReport) {
314 | if (isReport) {
315 | BJ_REPORT.reportOfflineLog();
316 | }
317 | };
318 | document.head.appendChild(script);
319 | };
320 |
321 |
322 |
323 | var submit_log_list = [];
324 | var comboTimeout = 0;
325 | var _submit_log = function() {
326 | clearTimeout(comboTimeout);
327 | // https://github.com/BetterJS/badjs-report/issues/34
328 | comboTimeout = 0;
329 |
330 | if (!submit_log_list.length) {
331 | return;
332 | }
333 |
334 | var url = _config._reportUrl + submit_log_list.join("&") + "&count=" + submit_log_list.length + "&_t=" + (+new Date);
335 |
336 | if (_config.submit) {
337 | _config.submit(url, submit_log_list);
338 | } else {
339 | var _img = new Image();
340 | _img.src = url;
341 | }
342 |
343 | submit_log_list = [];
344 | };
345 |
346 | var _process_log = function(isReportNow) {
347 | if (!_config._reportUrl) return;
348 |
349 | var randomIgnore = Math.random() >= _config.random;
350 |
351 |
352 | while (_log_list.length) {
353 | var isIgnore = false;
354 | var report_log = _log_list.shift();
355 | //有效保证字符不要过长
356 | report_log.msg = (report_log.msg + "" || "").substr(0, 500);
357 | // 重复上报
358 | if (T.isRepeat(report_log)) continue;
359 | var log_str = _report_log_tostring(report_log, submit_log_list.length);
360 | if (T.isOBJByType(_config.ignore, "Array")) {
361 | for (var i = 0, l = _config.ignore.length; i < l; i++) {
362 | var rule = _config.ignore[i];
363 | if ((T.isOBJByType(rule, "RegExp") && rule.test(log_str[1])) ||
364 | (T.isOBJByType(rule, "Function") && rule(report_log, log_str[1]))) {
365 | isIgnore = true;
366 | break;
367 | }
368 | }
369 | }
370 | if (!isIgnore) {
371 | _config.offlineLog && _save2Offline("badjs_" + _config.id + _config.uin, report_log);
372 | if (!randomIgnore && report_log.level != 20) {
373 | submit_log_list.push(log_str[0]);
374 | _config.onReport && (_config.onReport(_config.id, report_log));
375 | }
376 |
377 | }
378 | }
379 |
380 |
381 | if (isReportNow) {
382 | _submit_log(); // 立即上报
383 | } else if (!comboTimeout) {
384 | comboTimeout = setTimeout(_submit_log, _config.delay); // 延迟上报
385 | }
386 | };
387 |
388 |
389 |
390 | var report = global.BJ_REPORT = {
391 | push: function(msg) { // 将错误推到缓存池
392 |
393 | var data = T.isOBJ(msg) ? T.processError(msg) : {
394 | msg: msg
395 | };
396 |
397 | // ext 有默认值, 且上报不包含 ext, 使用默认 ext
398 | if (_config.ext && !data.ext) {
399 | data.ext = _config.ext;
400 | }
401 | // 在错误发生时获取页面链接
402 | // https://github.com/BetterJS/badjs-report/issues/19
403 | if (!data.from) {
404 | data.from = location.href;
405 | }
406 |
407 | if (data._orgMsg) {
408 | var _orgMsg = data._orgMsg;
409 | delete data._orgMsg;
410 | data.level = 2;
411 | var newData = T.extend({}, data);
412 | newData.level = 4;
413 | newData.msg = _orgMsg;
414 | _log_list.push(data);
415 | _log_list.push(newData);
416 | } else {
417 | _log_list.push(data);
418 | }
419 |
420 | _process_log();
421 | return report;
422 | },
423 | report: function(msg, isReportNow) { // error report
424 | msg && report.push(msg);
425 |
426 | isReportNow && _process_log(true);
427 | return report;
428 | },
429 | info: function(msg) { // info report
430 | if (!msg) {
431 | return report;
432 | }
433 | if (T.isOBJ(msg)) {
434 | msg.level = 2;
435 | } else {
436 | msg = {
437 | msg: msg,
438 | level: 2
439 | };
440 | }
441 | report.push(msg);
442 | return report;
443 | },
444 | debug: function(msg) { // debug report
445 | if (!msg) {
446 | return report;
447 | }
448 | if (T.isOBJ(msg)) {
449 | msg.level = 1;
450 | } else {
451 | msg = {
452 | msg: msg,
453 | level: 1
454 | };
455 | }
456 | report.push(msg);
457 | return report;
458 | },
459 |
460 | reportOfflineLog: function() {
461 | if (!window.indexedDB) {
462 | BJ_REPORT.info("unsupport offlineLog");
463 | return;
464 | }
465 | Offline_DB.ready(function(err, DB) {
466 | if (!DB) {
467 | return;
468 | }
469 | var startDate = new Date - 0 - _config.offlineLogExp * 24 * 3600 * 1000;
470 | var endDate = new Date - 0;
471 | DB.getLogs({
472 | start: startDate,
473 | end: endDate,
474 | id: _config.id,
475 | uin: _config.uin
476 | }, function(err, result) {
477 | var iframe = document.createElement("iframe");
478 | iframe.name = "badjs_offline_" + (new Date - 0);
479 | iframe.frameborder = 0;
480 | iframe.height = 0;
481 | iframe.width = 0;
482 | iframe.src = "javascript:false;";
483 |
484 | iframe.onload = function() {
485 | var form = document.createElement("form");
486 | form.style.display = "none";
487 | form.target = iframe.name;
488 | form.method = "POST";
489 | form.action = _config.offline_url || _config.url.replace(/badjs$/, "offlineLog");
490 | form.enctype.method = 'multipart/form-data';
491 |
492 | var input = document.createElement("input");
493 | input.style.display = "none";
494 | input.type = "hidden";
495 | input.name = "offline_log";
496 | input.value = JSON.stringify({ logs: result, userAgent: navigator.userAgent, startDate: startDate, endDate: endDate, id: _config.id, uin: _config.uin });
497 |
498 | iframe.contentDocument.body.appendChild(form);
499 | form.appendChild(input);
500 | form.submit();
501 |
502 | setTimeout(function() {
503 | document.body.removeChild(iframe);
504 | }, 10000);
505 |
506 | iframe.onload = null;
507 | };
508 | document.body.appendChild(iframe);
509 | });
510 | });
511 | },
512 | offlineLog: function(msg) {
513 | if (!msg) {
514 | return report;
515 | }
516 | if (T.isOBJ(msg)) {
517 | msg.level = 20;
518 | } else {
519 | msg = {
520 | msg: msg,
521 | level: 20
522 | };
523 | }
524 | report.push(msg);
525 | return report;
526 | },
527 | init: function(config) { // 初始化
528 | if (T.isOBJ(config)) {
529 | T.extend(_config, config);
530 | }
531 | // 没有设置id将不上报
532 | var id = parseInt(_config.id, 10);
533 | if (id) {
534 | // set default report url and uin
535 | if (/qq\.com$/gi.test(location.hostname)) {
536 | if (!_config.url) {
537 | _config.url = "//badjs2.qq.com/badjs";
538 | }
539 |
540 | if (!_config.uin) {
541 | _config.uin = parseInt((document.cookie.match(/\buin=\D+(\d+)/) || [])[1], 10);
542 | }
543 | }
544 |
545 | _config._reportUrl = (_config.url || "/badjs") +
546 | "?id=" + id +
547 | "&uin=" + _config.uin +
548 | // "&from=" + encodeURIComponent(location.href) +
549 | "&";
550 | }
551 |
552 | // if had error in cache , report now
553 | if (_log_list.length) {
554 | _process_log();
555 | }
556 |
557 | // init offline
558 | if (!Offline_DB._initing) {
559 | Offline_DB._initing = true;
560 | Offline_DB.ready(function(err, DB) {
561 | if (DB) {
562 | setTimeout(function() {
563 | DB.clearDB(_config.offlineLogExp);
564 | setTimeout(function() {
565 | _config.offlineLogAuto && _autoReportOffline();
566 | }, 5000);
567 | }, 1000);
568 | }
569 |
570 | });
571 | }
572 |
573 |
574 |
575 | return report;
576 | },
577 |
578 | __onerror__: global.onerror
579 | };
580 |
581 | typeof console !== "undefined" && console.error && setTimeout(function() {
582 | var err = ((location.hash || "").match(/([#&])BJ_ERROR=([^&$]+)/) || [])[2];
583 | err && console.error("BJ_ERROR", decodeURIComponent(err).replace(/(:\d+:\d+)\s*/g, "$1\n"));
584 | }, 0);
585 |
586 | return report;
587 |
588 | }(window));
589 |
590 | if (typeof module !== "undefined") {
591 | module.exports = BJ_REPORT;
592 | }
593 |
--------------------------------------------------------------------------------
/src/bj-wrap.js:
--------------------------------------------------------------------------------
1 | (function(global) {
2 |
3 | if (!global.BJ_REPORT) {
4 | console.error("please load bg-report first");
5 | return;
6 | }
7 |
8 | var _onthrow = function(errObj) {
9 | global.BJ_REPORT.push(errObj);
10 | };
11 |
12 | var tryJs = {};
13 | global.BJ_REPORT.tryJs = function(throwCb) {
14 | throwCb && (_onthrow = throwCb);
15 | return tryJs;
16 | };
17 |
18 | // merge
19 | var _merge = function(org, obj) {
20 | for (var key in obj) {
21 | org[key] = obj[key];
22 | }
23 | };
24 |
25 | // function or not
26 | var _isFunction = function(foo) {
27 | return typeof foo === "function";
28 | };
29 |
30 | var timeoutkey;
31 |
32 | var cat = function(foo, args) {
33 | return function() {
34 | try {
35 | return foo.apply(this, args || arguments);
36 | } catch (error) {
37 |
38 | _onthrow(error);
39 |
40 | //some browser throw error (chrome) , can not find error where it throw, so print it on console;
41 | if (error.stack && console && console.error) {
42 | console.error("[BJ-REPORT]", error.stack);
43 | }
44 |
45 | // hang up browser and throw , but it should trigger onerror , so rewrite onerror then recover it
46 | if (!timeoutkey) {
47 | var orgOnerror = global.onerror;
48 | global.onerror = function() { };
49 | timeoutkey = setTimeout(function() {
50 | global.onerror = orgOnerror;
51 | timeoutkey = null;
52 | }, 50);
53 | }
54 | throw error;
55 | }
56 | };
57 | };
58 |
59 | var catArgs = function(foo) {
60 | return function() {
61 | var arg, args = [];
62 | for (var i = 0, l = arguments.length; i < l; i++) {
63 | arg = arguments[i];
64 | _isFunction(arg) && (arg = cat(arg));
65 | args.push(arg);
66 | }
67 | return foo.apply(this, args);
68 | };
69 | };
70 |
71 | var catTimeout = function(foo) {
72 | return function(cb, timeout) {
73 | // for setTimeout(string, delay)
74 | if (typeof cb === "string") {
75 | try {
76 | cb = new Function(cb);
77 | } catch (err) {
78 | throw err;
79 | }
80 | }
81 | var args = [].slice.call(arguments, 2);
82 | // for setTimeout(function, delay, param1, ...)
83 | cb = cat(cb, args.length && args);
84 | return foo(cb, timeout);
85 | };
86 | };
87 |
88 | /**
89 | * makeArgsTry
90 | * wrap a function's arguments with try & catch
91 | * @param {Function} foo
92 | * @param {Object} self
93 | * @returns {Function}
94 | */
95 | var makeArgsTry = function(foo, self) {
96 | return function() {
97 | var arg, tmp, args = [];
98 | for (var i = 0, l = arguments.length; i < l; i++) {
99 | arg = arguments[i];
100 | if (_isFunction(arg)) {
101 | if (arg.tryWrap) {
102 | arg = arg.tryWrap;
103 | } else {
104 | tmp = cat(arg);
105 | arg.tryWrap = tmp;
106 | arg = tmp;
107 | }
108 | }
109 | args.push(arg);
110 | }
111 | return foo.apply(self || this, args);
112 | };
113 | };
114 |
115 | /**
116 | * makeObjTry
117 | * wrap a object's all value with try & catch
118 | * @param {Function} foo
119 | * @param {Object} self
120 | * @returns {Function}
121 | */
122 | var makeObjTry = function(obj) {
123 | var key, value;
124 | for (key in obj) {
125 | value = obj[key];
126 | if (_isFunction(value)) obj[key] = cat(value);
127 | }
128 | return obj;
129 | };
130 |
131 | /**
132 | * wrap jquery async function ,exp : event.add , event.remove , ajax
133 | * @returns {Function}
134 | */
135 | tryJs.spyJquery = function() {
136 | var _$ = global.$;
137 |
138 | if (!_$ || !_$.event) {
139 | return tryJs;
140 | }
141 |
142 | var _add, _remove;
143 | if (_$.zepto) {
144 | _add = _$.fn.on, _remove = _$.fn.off;
145 |
146 | _$.fn.on = makeArgsTry(_add);
147 | _$.fn.off = function() {
148 | var arg, args = [];
149 | for (var i = 0, l = arguments.length; i < l; i++) {
150 | arg = arguments[i];
151 | _isFunction(arg) && arg.tryWrap && (arg = arg.tryWrap);
152 | args.push(arg);
153 | }
154 | return _remove.apply(this, args);
155 | };
156 |
157 | } else if (window.jQuery) {
158 | _add = _$.event.add, _remove = _$.event.remove;
159 |
160 | _$.event.add = makeArgsTry(_add);
161 | _$.event.remove = function() {
162 | var arg, args = [];
163 | for (var i = 0, l = arguments.length; i < l; i++) {
164 | arg = arguments[i];
165 | _isFunction(arg) && arg.tryWrap && (arg = arg.tryWrap);
166 | args.push(arg);
167 | }
168 | return _remove.apply(this, args);
169 | };
170 | }
171 |
172 | var _ajax = _$.ajax;
173 |
174 | if (_ajax) {
175 | _$.ajax = function(url, setting) {
176 | if (!setting) {
177 | setting = url;
178 | url = undefined;
179 | }
180 | makeObjTry(setting);
181 | if (url) return _ajax.call(_$, url, setting);
182 | return _ajax.call(_$, setting);
183 | };
184 | }
185 |
186 | return tryJs;
187 | };
188 |
189 | /**
190 | * wrap amd or commonjs of function ,exp : define , require ,
191 | * @returns {Function}
192 | */
193 | tryJs.spyModules = function() {
194 | var _require = global.require,
195 | _define = global.define;
196 | if (_define && _define.amd && _require) {
197 | global.require = catArgs(_require);
198 | _merge(global.require, _require);
199 | global.define = catArgs(_define);
200 | _merge(global.define, _define);
201 | }
202 |
203 | if (global.seajs && _define) {
204 | global.define = function() {
205 | var arg, args = [];
206 | for (var i = 0, l = arguments.length; i < l; i++) {
207 | arg = arguments[i];
208 | if (_isFunction(arg)) {
209 | arg = cat(arg);
210 | //seajs should use toString parse dependencies , so rewrite it
211 | arg.toString = (function(orgArg) {
212 | return function() {
213 | return orgArg.toString();
214 | };
215 | }(arguments[i]));
216 | }
217 | args.push(arg);
218 | }
219 | return _define.apply(this, args);
220 | };
221 |
222 | global.seajs.use = catArgs(global.seajs.use);
223 |
224 | _merge(global.define, _define);
225 | }
226 |
227 | return tryJs;
228 | };
229 |
230 | /**
231 | * wrap async of function in window , exp : setTimeout , setInterval
232 | * @returns {Function}
233 | */
234 | tryJs.spySystem = function() {
235 | global.setTimeout = catTimeout(global.setTimeout);
236 | global.setInterval = catTimeout(global.setInterval);
237 | return tryJs;
238 | };
239 |
240 | /**
241 | * wrap custom of function ,
242 | * @param obj - obj or function
243 | * @returns {Function}
244 | */
245 | tryJs.spyCustom = function(obj) {
246 | if (_isFunction(obj)) {
247 | return cat(obj);
248 | } else {
249 | return makeObjTry(obj);
250 | }
251 | };
252 |
253 | /**
254 | * run spyJquery() and spyModules() and spySystem()
255 | * @returns {Function}
256 | */
257 | tryJs.spyAll = function() {
258 | tryJs
259 | .spyJquery()
260 | .spyModules()
261 | .spySystem();
262 | return tryJs;
263 | };
264 |
265 | }(window));
266 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /* global chai, mocha, BJ_REPORT */
2 | var assert = chai.assert;
3 | var should = chai.should();
4 | var expect = chai.expect;
5 |
6 | define(['../src/bj-report.js', '../src/bj-wrap.js'], function(report) {
7 | describe('init', function() {
8 |
9 | // close repeat
10 | BJ_REPORT.init({
11 | repeat: 10000
12 | });
13 |
14 |
15 | it(' use push and report ', function(done) {
16 |
17 | BJ_REPORT.init({
18 | id: 1,
19 | url: "http://test.qq.com/report",
20 | combo: 1,
21 | delay: 200,
22 | submit: function(url) {
23 | var match1 = url.indexOf("errorTest1");
24 | var match2 = url.indexOf("errorTest2");
25 | var match3 = url.indexOf("errorTest3");
26 | var match4 = url.indexOf("errorTest4");
27 | var match5 = url.indexOf("count");
28 | should.not.equal(match1, -1);
29 | should.not.equal(match2, -1);
30 | should.not.equal(match3, -1);
31 | should.not.equal(match4, -1);
32 | should.not.equal(match5, -1);
33 | done();
34 | }
35 | });
36 | BJ_REPORT.push("errorTest1");
37 | BJ_REPORT.push("errorTest2");
38 | BJ_REPORT.push("errorTest3");
39 | BJ_REPORT.report("errorTest4", true);
40 | });
41 |
42 |
43 | it(' onerror and report ', function(done) {
44 |
45 | var count = 0;
46 | BJ_REPORT.init({
47 | id: 1,
48 | url: "http://test.qq.com/report",
49 | delay: 200,
50 | submit: function(url) {
51 | count++;
52 |
53 | }
54 | });
55 | BJ_REPORT.__onerror__("msg", 0, 0, null);
56 | BJ_REPORT.report(null, true);
57 |
58 | setTimeout(function() {
59 | should.equal(count, 1);
60 | done();
61 | }, 500);
62 | });
63 |
64 |
65 | it('onReport callback ', function(done) {
66 |
67 | var count = 0;
68 | BJ_REPORT.init({
69 | id: 1,
70 | url: "http://test.qq.com/report",
71 | delay: 200,
72 | submit: function(url) {
73 | count++;
74 | should.equal(count, 2);
75 | done();
76 | },
77 | onReport: function() {
78 | count++;
79 | }
80 | });
81 | BJ_REPORT.report("errorTest4", true);
82 | });
83 |
84 |
85 | it('ignore report1 ', function(done) {
86 |
87 | var count = 0;
88 | BJ_REPORT.init({
89 | id: 1,
90 | url: "http://test.qq.com/report",
91 | delay: 200,
92 | ignore: [
93 | /ignore/gi,
94 | function(error, str) {
95 |
96 | if (str.indexOf("ignore2") >= 0) {
97 | return true;
98 | } else {
99 | false;
100 | }
101 |
102 | }
103 | ],
104 | submit: function(url) {
105 | should.equal(count, 1);
106 | done();
107 | BJ_REPORT.init({
108 | ignore: []
109 | });
110 | },
111 | onReport: function() {
112 | count++;
113 | }
114 | });
115 | BJ_REPORT.push("ignore");
116 | BJ_REPORT.push("pass");
117 | BJ_REPORT.report("ignore2", true);
118 | });
119 |
120 |
121 | it('ignore report2 ', function(done) {
122 | var count = 0;
123 | BJ_REPORT.init({
124 | id: 1,
125 | url: "http://test.qq.com/report",
126 | delay: 200,
127 | ignore: [
128 | /ignore/gi,
129 | function(error, str) {
130 | return str.indexOf("ignore2") >= 0;
131 | }
132 | ],
133 | submit: function(url) {
134 | should.equal(count, 3);
135 | done();
136 | BJ_REPORT.init({
137 | ignore: []
138 | });
139 | },
140 | onReport: function() {
141 | count++;
142 | }
143 | });
144 | BJ_REPORT.push("pass");
145 | BJ_REPORT.push("pass");
146 | BJ_REPORT.report("pass", true);
147 | });
148 |
149 |
150 | it('report Error Event', function(done, err) {
151 |
152 | BJ_REPORT.init({
153 | id: 1,
154 | url: "http://test.qq.com/report",
155 | delay: 200,
156 | submit: function(url) {
157 | var match1 = url.indexOf("ReferenceError");
158 | should.not.equal(match1, -1);
159 | done();
160 | }
161 | }).tryJs().spyAll();
162 |
163 | try {
164 | error111; // jshint ignore:line
165 | } catch (e) {
166 | BJ_REPORT.report(e);
167 | }
168 |
169 | });
170 |
171 | it('repeat check', function(done, err) {
172 |
173 | BJ_REPORT.init({
174 | id: 1,
175 | url: "http://test.qq.com/report",
176 | delay: 1000,
177 | repeat: 1,
178 | submit: function(url) {
179 | var match_count = (url.match(/repeatTest/g) || []).length;
180 | should.equal(match_count, 1);
181 | done();
182 | // close repeat
183 | BJ_REPORT.init({
184 | repeat: 1e10
185 | });
186 | }
187 | }).tryJs().spyAll();
188 |
189 | BJ_REPORT.push('repeatTest');
190 | BJ_REPORT.push('repeatTest');
191 | BJ_REPORT.push('repeatTest');
192 | BJ_REPORT.report('repeatTest', true);
193 | });
194 | });
195 |
196 |
197 | describe('spy', function() {
198 |
199 | it('spyCustom', function(done, err) {
200 |
201 | var spyCustomFun = function() {
202 | throw "errorTest1";
203 | };
204 |
205 | spyCustomFun = BJ_REPORT.init({
206 | id: 1,
207 | url: "http://test.qq.com/report",
208 | delay: 200,
209 | submit: function(url) {
210 | var match1 = url.indexOf("errorTest1");
211 | should.not.equal(match1, -1);
212 | done();
213 | }
214 | }).tryJs().spyCustom(spyCustomFun);
215 |
216 | (function() {
217 | spyCustomFun();
218 | }).should.throw();
219 | });
220 |
221 |
222 | it('spyAll', function(done, err) {
223 | var _cb;
224 |
225 | window.require = function(requires, cb) {
226 |
227 | };
228 | window.define = function(name, cb) {
229 | if (_cb) {
230 | _cb();
231 | } else {
232 | _cb = cb;
233 | }
234 | };
235 | window.define.amd = true;
236 |
237 | BJ_REPORT.init({
238 | id: 1,
239 | url: "http://test.qq.com/report",
240 | delay: 200,
241 | submit: function(url) {
242 | var match1 = url.indexOf("testDefine");
243 | should.not.equal(match1, -1);
244 | done();
245 | }
246 | }).tryJs().spyAll();
247 |
248 | define("testDefine", function() {
249 | throw "testDefine";
250 | });
251 |
252 | (function() {
253 | console.log(window.define);
254 | define();
255 | }).should.throw();
256 | });
257 |
258 |
259 |
260 | });
261 | });
262 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | /* global chai, mocha */
2 | var assert = chai.assert;
3 | var expect = chai.expect;
4 |
5 | mocha.setup({
6 | ui: 'bdd'
7 | });
8 |
9 | require.config({
10 | baseUrl: '../src/',
11 |
12 | paths: {
13 | 'jquery': '../node_modules/jquery/dist/jquery.min'
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/test/testForLocation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test
5 |
6 |
7 |
8 |
9 |
22 |
23 |
24 |
--------------------------------------------------------------------------------