├── .gitignore ├── .jshintrc ├── LICENSE ├── Procfile ├── README.md ├── bin └── jsinspector ├── config.js ├── package.json ├── public ├── ZeroClipboard.js ├── ZeroClipboard.swf ├── asserts │ └── ProximaNova-Light.otf ├── css │ ├── client.css │ ├── device.css │ └── iphone.css ├── dashboard.html ├── detect.js ├── device.js ├── favicon.ico ├── images │ ├── android.png │ ├── copy.png │ ├── ios.png │ ├── pc.png │ └── phone.png ├── inspector.html ├── inspector.js ├── reverse.js └── zect.min.js └── server ├── asserts ├── console.js ├── inject.js ├── jsondiffpatch.js ├── jsonify.js └── socket.js ├── index.js └── routes ├── client.js └── inspector.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/node_modules 3 | /test 4 | /.tmp -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "eqeqeq": false, 3 | "browser": true, 4 | "asi": true, 5 | "multistr": true, 6 | "undef": true, 7 | "unused": true, 8 | "trailing": true, 9 | "sub": true, 10 | "node": true, 11 | "laxbreak": true, 12 | "evil": true, 13 | "eqnull": true, 14 | "proto": true, 15 | "expr": true, 16 | "globals": { 17 | "alert": true, 18 | "console": true, 19 | "jsonify": true, 20 | "insp_consoles": true, 21 | "io": true, 22 | "_jsinpector_execute": true 23 | } 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 guankaishe 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. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![jsinspector](http://switer.github.io/live/images/jsinspector.png) 2 | =================================================================== 3 | [![npm version](https://badge.fury.io/js/jsinspector.svg)](https://badge.fury.io/js/jsinspector) 4 | 5 | Mobile debug toolkit, include **console.log**, **HTML Inspector** and **script inject api**. 6 | 7 |
![show](http://7rf30v.com1.z0.glb.clouddn.com/jsinspector.gif)
8 | 9 | ## Install and Run 10 | For [node](http://nodejs.org) via [npm](http://npmjs.org): 11 | ```bash 12 | npm install jsinspector -g 13 | ``` 14 | 15 | ```bash 16 | jsinspector server 17 | ``` 18 | 19 | The server's **port** default to 9000, open `Dashboard` page in browser: 20 | ```url 21 | http://localhost:9000 22 | ``` 23 | > Note: use `jsinspector server --port PORT` to start server with specified port. 24 | 25 | 26 | ## Features 27 | 28 | - **Console from Remote** 29 | 30 | Support console of `log`, `clear`, `error`, `info`, `warn`, `time` and `timeEnd`: 31 | 32 | ```javascript 33 | console.log(window); // -> {xxx: 'String', xxx2: 'Function', ..., window: 'Global'} 34 | console.log(document); // -> {xxx: 'String', xxx2: 'Function', ..., body: 'HTMLBodyElement'} 35 | ``` 36 |
![console sync](http://7rf30v.com1.z0.glb.clouddn.com/console.png)
37 | 38 | - **Execute Script** 39 | 40 | Using `inject` method to execute script in remote browser: 41 | ```js 42 | 43 | inject('console.log("window")') 44 | 45 | // block codes 46 | inject(function () { 47 | console.log(document) 48 | }) 49 | 50 | // insert external script 51 | inject.js('http://yourhost/lib.js') 52 | 53 | // insert external style sheet 54 | inject.css('http://yourhost/style.css') 55 | ``` 56 | 57 | 58 | ## License 59 | 60 | The MIT License (MIT) 61 | 62 | Copyright (c) 2014 guankaishe -------------------------------------------------------------------------------- /bin/jsinspector: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var commander = require('commander') 4 | var meta = require('../package.json') 5 | 6 | commander 7 | .version(meta.version) 8 | .option('-p, --port ', 'Start server with specified port.') 9 | 10 | 11 | commander 12 | .command('server') 13 | .description('Start inspector server') 14 | .action(function () { 15 | var server = require('../server/index') 16 | var port = process.env.PORT || commander.port || 9000 17 | server.listen(port, function() { 18 | console.log("Inspector server listening on " + port) 19 | }) 20 | }) 21 | 22 | commander.parse(process.argv) -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path') 4 | var env = process.env.NODE_ENV 5 | var envs = process.env 6 | module.exports = { 7 | "isDev": env === 'dev', 8 | "enable_mini": env !== 'dev' ? true : false, 9 | "tmp_dir": path.join(envs.APPDATA || envs.HOME || __dirname, '.tmp') 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsinspector", 3 | "version": "2.0.4", 4 | "description": "JS Web Inspector", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test", 8 | "start": "node bin/jsinspector server" 9 | }, 10 | "bin": { 11 | "insp": "./bin/jsinspector", 12 | "jsinspector": "./bin/jsinspector" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/switer/jsinspector.git" 17 | }, 18 | "keywords": [ 19 | "web", 20 | "remote", 21 | "inspector" 22 | ], 23 | "author": "switer", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/switer/jsinspector/issues" 27 | }, 28 | "homepage": "https://github.com/switer/jsinspector", 29 | "dependencies": { 30 | "body-parser": "^1.2.0", 31 | "commander": "^2.8.1", 32 | "compression": "^1.0.2", 33 | "cookie-parser": "^1.3.5", 34 | "ejs": "^1.0.0", 35 | "express": "^4.3.1", 36 | "jsondiffpatch": "^0.1.5", 37 | "logfmt": "^1.1.2", 38 | "node-uuid": "^1.4.3", 39 | "socket.io": "^1.3.5", 40 | "uglify-js": "^2.4.23" 41 | }, 42 | "engines": { 43 | "node": "0.10.x" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/ZeroClipboard.swf -------------------------------------------------------------------------------- /public/asserts/ProximaNova-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/asserts/ProximaNova-Light.otf -------------------------------------------------------------------------------- /public/css/client.css: -------------------------------------------------------------------------------- 1 | .slogan {color: white;font-size: 32px;text-align: center;padding: 5px 0;padding-top: 20px;padding-bottom: 10px; 2 | box-shadow: 0 1px 0 rgba(0,0,0,.1);background-color: rgba(0,0,0,.05);} 3 | .list {display: block;list-style: none;padding: 0;margin: 10px 0;margin-top: 20px;} 4 | .list li {line-height: 36px;display: -webkit-box;padding: 5px 10px;margin: 10px;cursor: pointer;} 5 | .list li:hover{border-radius: 6px;background-color: rgba(255,255,255,.2);} 6 | .list li.active{background-color: rgba(0,0,0,.1);border-radius: 6px;} 7 | .list li .icon {background: url(../images/pc.png) center center no-repeat;height: 36px;width: 36px; 8 | background-size: 24px auto;display: block;margin-right: 5px;} 9 | .list li .link {overflow: hidden;text-overflow: ellipsis;display: block;width: 50%;white-space: nowrap;-webkit-box-flex:1; 10 | color: #E8E8E8;text-decoration: none;font-size: 15px;} 11 | 12 | .list li .icon.ios {background-image: url(../images/ios.png);} 13 | .list li .icon.android {background-image: url(../images/android.png);} 14 | 15 | .device .script {height: 40px;border-bottom: 1px solid #E7E7EC;line-height: 40px;padding: 0 20px;color: #333; 16 | display: -webkit-box;font-size: 16px;} 17 | .device .script .copy {width: 40px;height: 40px;display: block;margin: 0 5px;background: url(../images/copy.png) center center no-repeat; 18 | background-size: 30px auto;cursor: pointer;} 19 | .device .script input {display: block;line-height: 40px;border: 0;padding: 0;margin: 0;outline: 0; 20 | font-size: 14px;color: rgb(50, 113, 228);width: 20%;-webkit-box-flex:1;} 21 | -------------------------------------------------------------------------------- /public/css/device.css: -------------------------------------------------------------------------------- 1 | /*copy from http://codepen.io/zachacole/pen/VYMPMo */ 2 | 3 | .iphone-body { 4 | position: relative; 5 | background: #fff; 6 | height: 600px; 7 | width: 300px; 8 | border-radius: 40px; 9 | box-shadow: 0 2px 4px 2px #ccc; 10 | } 11 | 12 | .iphone-body:before { 13 | content: ""; 14 | display: block; 15 | position: relative; 16 | top: 36px; 17 | background: #e7ebec; 18 | height: 6px; 19 | width: 60px; 20 | border-radius: 6px; 21 | margin: 0 auto; 22 | } 23 | 24 | .iphone-body:after { 25 | content: ""; 26 | display: block; 27 | position: absolute; 28 | background: none; 29 | height: 34px; 30 | width: 34px; 31 | border: 6px solid #e7ebec; 32 | border-radius: 50%; 33 | margin: 0 auto; 34 | left: 0; 35 | right: 0; 36 | bottom: 16px; 37 | } 38 | 39 | .camera-1 { 40 | position: absolute; 41 | background: #e7ebec; 42 | height: 8px; 43 | width: 8px; 44 | border-radius: 8px; 45 | margin: 12px 0 0 146px; 46 | } 47 | 48 | .camera-2 { 49 | position: absolute; 50 | background: #e7ebec; 51 | height: 10px; 52 | width: 10px; 53 | border-radius: 10px; 54 | margin: 28px 0 0 90px; 55 | } 56 | 57 | .iphone-screen { 58 | position: relative; 59 | top: 50px; 60 | background: #fff; 61 | height: 440px; 62 | width: 272px; 63 | margin: 0 auto; 64 | border: 4px solid #e7ebec; 65 | border-radius: 4px; 66 | background-color: #333; 67 | } -------------------------------------------------------------------------------- /public/css/iphone.css: -------------------------------------------------------------------------------- 1 | .iPhone { 2 | background: #fff; 3 | border: solid 2px #c5c5c5; 4 | height: 500px; 5 | width: 280px; 6 | border-radius: 30px; 7 | position: relative; 8 | margin: 0 auto; 9 | box-shadow: 0 5px 8px 0 rgba(50, 50, 50, 0.25); 10 | -webkit-box-shadow: 0 5px 8px 0 rgba(50, 50, 50, 0.25); 11 | -webkit-transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 12 | -moz-transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 13 | transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 14 | } 15 | 16 | .iPhoneInner { 17 | background: #fff; 18 | border: solid 2px #b4b4b4; 19 | height: 496px; 20 | width: 276px; 21 | border-radius: 30px; 22 | position: relative; 23 | margin: 0 auto; 24 | box-shadow: 0 3px 5px 0 rgba(50, 50, 50, 0.75); 25 | -webkit-transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 26 | -moz-transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 27 | transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 28 | } 29 | 30 | .iPhoneInner .circButton { 31 | background: #fff; 32 | border: solid 2px #6f6f6f; 33 | width: 36px; 34 | height: 36px; 35 | position: relative; 36 | margin: 0 auto; 37 | border-radius: 50%; 38 | top: 48px; 39 | cursor: pointer; 40 | /*-webkit-box-shadow: inset 0 0px 3px rgba(0, 0, 0, .8);*/ 41 | /*box-shadow: inset 0 0px 3px rgba(0, 0, 0, .8);*/ 42 | } 43 | 44 | .iPhoneInner .smallTopCirc { 45 | background-color: #6f6f6f; 46 | background-image: -webkit-linear-gradient(#6f6f6f, #141414); 47 | background-image: linear-gradient(#6f6f6f, #141414); 48 | width: 6px; 49 | height: 6px; 50 | border-radius: 50%; 51 | top: 6px; 52 | position: relative; 53 | margin: 0 auto; 54 | } 55 | 56 | .iPhoneInner .oval { 57 | background-color: #6f6f6f; 58 | background-image: -webkit-linear-gradient(#6f6f6f, #141414); 59 | background-image: linear-gradient(#6f6f6f, #141414); 60 | width: 50px; 61 | height: 4px; 62 | top: 14px; 63 | border-radius: 2px; 64 | margin: 0 auto; 65 | margin-bottom: -12px; 66 | position: relative; 67 | } 68 | 69 | .iScreen { 70 | position: relative; 71 | width: 250px; 72 | height: 400px; 73 | margin: 0 auto; 74 | top: 40px; 75 | overflow: hidden; 76 | background-color: #333; 77 | background-image: -webkit-linear-gradient(#6f6f6f, #141414); 78 | background-image: linear-gradient(#6f6f6f, #141414); 79 | border: solid 1px #6f6f6f; 80 | border-radius: 3px; 81 | -webkit-transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 82 | -moz-transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 83 | transition: all 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335); 84 | } 85 | 86 | .rotate { 87 | -webkit-transform: rotate(90deg); 88 | -moz-transform: rotate(90deg); 89 | -ms-transform: rotate(90deg); 90 | -o-transform: rotate(90deg); 91 | transform: rotate(90deg); 92 | } 93 | 94 | .rotateBack { 95 | -webkit-transform: rotate(-90deg); 96 | -moz-transform: rotate(-90deg); 97 | -ms-transform: rotate(-90deg); 98 | -o-transform: rotate(-90deg); 99 | transform: rotate(-90deg); 100 | } 101 | -------------------------------------------------------------------------------- /public/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSInspector • Dashboard 7 | 8 | 9 | 33 | 34 | 35 |
36 |
37 |
38 | JSInspector 39 |
40 | 59 |
60 |
61 |
62 |   63 |
{script}
64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /public/detect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Detect.js: User-Agent Parser 3 | * https://github.com/darcyclarke/Detect.js 4 | * Dual licensed under the MIT and GPL licenses. 5 | * 6 | * @version 2.2.1 7 | * @author Darcy Clarke 8 | * @url http://darcyclarke.me 9 | * @createdat Thu Feb 13 2014 11:36:42 GMT+0000 (WET) 10 | * 11 | * Based on UA-Parser (https://github.com/tobie/ua-parser) by Tobie Langel 12 | * 13 | * Example Usage: 14 | * var agentInfo = detect.parse(navigator.userAgent); 15 | * console.log(agentInfo.browser.family); // Chrome 16 | * 17 | */ 18 | (function(root, undefined) { 19 | // Shim Array.prototype.map if necessary 20 | // Production steps of ECMA-262, Edition 5, 15.4.4.19 21 | // Reference: http://es5.github.com/#x15.4.4.19 22 | if (!Array.prototype.map) { 23 | Array.prototype.map = function(callback, thisArg) { 24 | var T, A, k; 25 | if (this == null) { 26 | throw new TypeError(" this is null or not defined"); 27 | } 28 | // 1. Let O be the result of calling ToObject passing the |this| value as the argument. 29 | var O = Object(this); 30 | // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". 31 | // 3. Let len be ToUint32(lenValue). 32 | var len = O.length >>> 0; 33 | // 4. If IsCallable(callback) is false, throw a TypeError exception. 34 | // See: http://es5.github.com/#x9.11 35 | if (typeof callback !== "function") { 36 | throw new TypeError(callback + " is not a function"); 37 | } 38 | // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. 39 | if (thisArg) { 40 | T = thisArg; 41 | } 42 | // 6. Let A be a new array created as if by the expression new Array(len) where Array is 43 | // the standard built-in constructor with that name and len is the value of len. 44 | A = new Array(len); 45 | // 7. Let k be 0 46 | k = 0; 47 | // 8. Repeat, while k < len 48 | while (k < len) { 49 | var kValue, mappedValue; 50 | // a. Let Pk be ToString(k). 51 | // This is implicit for LHS operands of the in operator 52 | // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. 53 | // This step can be combined with c 54 | // c. If kPresent is true, then 55 | if (k in O) { 56 | // i. Let kValue be the result of calling the Get internal method of O with argument Pk. 57 | kValue = O[k]; 58 | // ii. Let mappedValue be the result of calling the Call internal method of callback 59 | // with T as the this value and argument list containing kValue, k, and O. 60 | mappedValue = callback.call(T, kValue, k, O); 61 | // iii. Call the DefineOwnProperty internal method of A with arguments 62 | // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true}, 63 | // and false. 64 | // In browsers that support Object.defineProperty, use the following: 65 | // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true }); 66 | // For best browser support, use the following: 67 | A[k] = mappedValue; 68 | } 69 | // d. Increase k by 1. 70 | k++; 71 | } 72 | // 9. return A 73 | return A; 74 | }; 75 | } 76 | // Detect 77 | var detect = root.detect = function() { 78 | // Context 79 | var _this = function() {}; 80 | // Regexes 81 | var regexes = { 82 | browser_parsers: [{ 83 | regex: "^(Opera)/(\\d+)\\.(\\d+) \\(Nintendo Wii", 84 | family_replacement: "Wii", 85 | manufacturer: "Nintendo" 86 | }, { 87 | regex: "(SeaMonkey|Camino)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*)", 88 | family_replacement: "Camino", 89 | other: true 90 | }, { 91 | regex: "(Pale[Mm]oon)/(\\d+)\\.(\\d+)\\.?(\\d+)?", 92 | family_replacement: "Pale Moon (Firefox Variant)", 93 | other: true 94 | }, { 95 | regex: "(Fennec)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*)", 96 | family_replacement: "Firefox Mobile" 97 | }, { 98 | regex: "(Fennec)/(\\d+)\\.(\\d+)(pre)", 99 | family_replacment: "Firefox Mobile" 100 | }, { 101 | regex: "(Fennec)/(\\d+)\\.(\\d+)", 102 | family_replacement: "Firefox Mobile" 103 | }, { 104 | regex: "Mobile.*(Firefox)/(\\d+)\\.(\\d+)", 105 | family_replacement: "Firefox Mobile" 106 | }, { 107 | regex: "(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre)?)", 108 | family_replacement: "Firefox ($1)" 109 | }, { 110 | regex: "(Firefox)/(\\d+)\\.(\\d+)(a\\d+[a-z]*)", 111 | family_replacement: "Firefox Alpha" 112 | }, { 113 | regex: "(Firefox)/(\\d+)\\.(\\d+)(b\\d+[a-z]*)", 114 | family_replacement: "Firefox Beta" 115 | }, { 116 | regex: "(Firefox)-(?:\\d+\\.\\d+)?/(\\d+)\\.(\\d+)(a\\d+[a-z]*)", 117 | family_replacement: "Firefox Alpha" 118 | }, { 119 | regex: "(Firefox)-(?:\\d+\\.\\d+)?/(\\d+)\\.(\\d+)(b\\d+[a-z]*)", 120 | family_replacement: "Firefox Beta" 121 | }, { 122 | regex: "(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)?", 123 | family_replacement: "Firefox ($1)" 124 | }, { 125 | regex: "(Firefox).*Tablet browser (\\d+)\\.(\\d+)\\.(\\d+)", 126 | family_replacement: "MicroB", 127 | tablet: true 128 | }, { 129 | regex: "(MozillaDeveloperPreview)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)?" 130 | }, { 131 | regex: "(Flock)/(\\d+)\\.(\\d+)(b\\d+?)", 132 | family_replacement: "Flock", 133 | other: true 134 | }, { 135 | regex: "(RockMelt)/(\\d+)\\.(\\d+)\\.(\\d+)", 136 | family_replacement: "Rockmelt", 137 | other: true 138 | }, { 139 | regex: "(Navigator)/(\\d+)\\.(\\d+)\\.(\\d+)", 140 | family_replacement: "Netscape" 141 | }, { 142 | regex: "(Navigator)/(\\d+)\\.(\\d+)([ab]\\d+)", 143 | family_replacement: "Netscape" 144 | }, { 145 | regex: "(Netscape6)/(\\d+)\\.(\\d+)\\.(\\d+)", 146 | family_replacement: "Netscape" 147 | }, { 148 | regex: "(MyIBrow)/(\\d+)\\.(\\d+)", 149 | family_replacement: "My Internet Browser", 150 | other: true 151 | }, { 152 | regex: "(Opera Tablet).*Version/(\\d+)\\.(\\d+)(?:\\.(\\d+))?", 153 | family_replacement: "Opera Tablet", 154 | tablet: true 155 | }, { 156 | regex: "(Opera)/.+Opera Mobi.+Version/(\\d+)\\.(\\d+)", 157 | family_replacement: "Opera Mobile" 158 | }, { 159 | regex: "Opera Mobi", 160 | family_replacement: "Opera Mobile" 161 | }, { 162 | regex: "(Opera Mini)/(\\d+)\\.(\\d+)", 163 | family_replacement: "Opera Mini" 164 | }, { 165 | regex: "(Opera Mini)/att/(\\d+)\\.(\\d+)", 166 | family_replacement: "Opera Mini" 167 | }, { 168 | regex: "(Opera)/9.80.*Version/(\\d+)\\.(\\d+)(?:\\.(\\d+))?", 169 | family_replacement: "Opera" 170 | }, { 171 | regex: "(webOSBrowser)/(\\d+)\\.(\\d+)", 172 | family_replacement: "webOS" 173 | }, { 174 | regex: "(webOS)/(\\d+)\\.(\\d+)", 175 | family_replacement: "webOS" 176 | }, { 177 | regex: "(wOSBrowser).+TouchPad/(\\d+)\\.(\\d+)", 178 | family_replacement: "webOS TouchPad" 179 | }, { 180 | regex: "(luakit)", 181 | family_replacement: "LuaKit", 182 | other: true 183 | }, { 184 | regex: "(Lightning)/(\\d+)\\.(\\d+)([ab]?\\d+[a-z]*)", 185 | family_replacement: "Lightning", 186 | other: true 187 | }, { 188 | regex: "(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre)?) \\(Swiftfox\\)", 189 | family_replacement: "Swiftfox", 190 | other: true 191 | }, { 192 | regex: "(Firefox)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*)? \\(Swiftfox\\)", 193 | family_replacement: "Swiftfox", 194 | other: true 195 | }, { 196 | regex: "rekonq", 197 | family_replacement: "Rekonq", 198 | other: true 199 | }, { 200 | regex: "(conkeror|Conkeror)/(\\d+)\\.(\\d+)\\.?(\\d+)?", 201 | family_replacement: "Conkeror", 202 | other: true 203 | }, { 204 | regex: "(konqueror)/(\\d+)\\.(\\d+)\\.(\\d+)", 205 | family_replacement: "Konqueror", 206 | other: true 207 | }, { 208 | regex: "(WeTab)-Browser", 209 | family_replacement: "WeTab", 210 | other: true 211 | }, { 212 | regex: "(Comodo_Dragon)/(\\d+)\\.(\\d+)\\.(\\d+)", 213 | family_replacement: "Comodo Dragon", 214 | other: true 215 | }, { 216 | regex: "(YottaaMonitor)", 217 | family_replacement: "Yottaa Monitor", 218 | other: true 219 | }, { 220 | regex: "(Kindle)/(\\d+)\\.(\\d+)", 221 | family_replacement: "Kindle" 222 | }, { 223 | regex: "(Symphony) (\\d+).(\\d+)", 224 | family_replacement: "Symphony", 225 | other: true 226 | }, { 227 | regex: "Minimo", 228 | family_replacement: "Minimo", 229 | other: true 230 | }, { 231 | regex: "(CrMo)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)", 232 | family_replacement: "Chrome Mobile" 233 | }, { 234 | regex: "(CriOS)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)", 235 | family_replacement: "Chrome Mobile iOS" 236 | }, { 237 | regex: "(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) Mobile", 238 | family_replacement: "Chrome Mobile" 239 | }, { 240 | regex: "(chromeframe)/(\\d+)\\.(\\d+)\\.(\\d+)", 241 | family_replacement: "Chrome Frame" 242 | }, { 243 | regex: "(UC Browser)(\\d+)\\.(\\d+)\\.(\\d+)", 244 | family_replacement: "UC Browser", 245 | other: true 246 | }, { 247 | regex: "(SLP Browser)/(\\d+)\\.(\\d+)", 248 | family_replacement: "Tizen Browser", 249 | other: true 250 | }, { 251 | regex: "(Epiphany)/(\\d+)\\.(\\d+).(\\d+)", 252 | family_replacement: "Epiphany", 253 | other: true 254 | }, { 255 | regex: "(SE 2\\.X) MetaSr (\\d+)\\.(\\d+)", 256 | family_replacement: "Sogou Explorer", 257 | other: true 258 | }, { 259 | regex: "(Pingdom.com_bot_version_)(\\d+)\\.(\\d+)", 260 | family_replacement: "PingdomBot", 261 | other: true 262 | }, { 263 | regex: "(facebookexternalhit)/(\\d+)\\.(\\d+)", 264 | family_replacement: "FacebookBot" 265 | }, { 266 | regex: "Facebot", 267 | family_replacement: "FacebookBot" 268 | }, { 269 | regex: "(Twitterbot)/(\\d+)\\.(\\d+)", 270 | family_replacement: "TwitterBot" 271 | }, { 272 | regex: "(AdobeAIR|Chromium|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Shiira|Sunrise|Chrome|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iron|Iris|UP\\.Browser|Bunjaloo|Google Earth|Raven for Mac)/(\\d+)\\.(\\d+)\\.(\\d+)" 273 | }, { 274 | regex: "(Bolt|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Chrome|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|NetNewsWire|Iron|Space Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris)/(\\d+)\\.(\\d+)" 275 | }, { 276 | regex: "(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\\d+)\\.(\\d+)\\.(\\d+)" 277 | }, { 278 | regex: "(iCab|Lunascape|Opera|Android|Jasmine|Polaris|BREW) (\\d+)\\.(\\d+)\\.?(\\d+)?" 279 | }, { 280 | regex: "(Android) Donut", 281 | v2_replacement: "2", 282 | v1_replacement: "1" 283 | }, { 284 | regex: "(Android) Eclair", 285 | v2_replacement: "1", 286 | v1_replacement: "2" 287 | }, { 288 | regex: "(Android) Froyo", 289 | v2_replacement: "2", 290 | v1_replacement: "2" 291 | }, { 292 | regex: "(Android) Gingerbread", 293 | v2_replacement: "3", 294 | v1_replacement: "2" 295 | }, { 296 | regex: "(Android) Honeycomb", 297 | v1_replacement: "3" 298 | }, { 299 | regex: "(IEMobile)[ /](\\d+)\\.(\\d+)", 300 | family_replacement: "IE Mobile" 301 | }, { 302 | regex: "(MSIE) (\\d+)\\.(\\d+).*XBLWP7", 303 | family_replacement: "IE Large Screen" 304 | }, { 305 | regex: "(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+)" 306 | }, { 307 | regex: "(Firefox)/(\\d+)\\.(\\d+)(pre|[ab]\\d+[a-z]*)?" 308 | }, { 309 | regex: "(Obigo)InternetBrowser", 310 | other: true 311 | }, { 312 | regex: "(Obigo)\\-Browser", 313 | other: true 314 | }, { 315 | regex: "(Obigo|OBIGO)[^\\d]*(\\d+)(?:.(\\d+))?", 316 | other: true 317 | }, { 318 | regex: "(MAXTHON|Maxthon) (\\d+)\\.(\\d+)", 319 | family_replacement: "Maxthon", 320 | other: true 321 | }, { 322 | regex: "(Maxthon|MyIE2|Uzbl|Shiira)", 323 | v1_replacement: "0", 324 | other: true 325 | }, { 326 | regex: "(PLAYSTATION) (\\d+)", 327 | family_replacement: "PlayStation", 328 | manufacturer: "Sony" 329 | }, { 330 | regex: "(PlayStation Portable)[^\\d]+(\\d+).(\\d+)", 331 | manufacturer: "Sony" 332 | }, { 333 | regex: "(BrowseX) \\((\\d+)\\.(\\d+)\\.(\\d+)", 334 | other: true 335 | }, { 336 | regex: "(POLARIS)/(\\d+)\\.(\\d+)", 337 | family_replacement: "Polaris", 338 | other: true 339 | }, { 340 | regex: "(Embider)/(\\d+)\\.(\\d+)", 341 | family_replacement: "Polaris", 342 | other: true 343 | }, { 344 | regex: "(BonEcho)/(\\d+)\\.(\\d+)\\.(\\d+)", 345 | family_replacement: "Bon Echo", 346 | other: true 347 | }, { 348 | regex: "(iPod).+Version/(\\d+)\\.(\\d+)\\.(\\d+)", 349 | family_replacement: "Mobile Safari", 350 | manufacturer: "Apple" 351 | }, { 352 | regex: "(iPod).*Version/(\\d+)\\.(\\d+)", 353 | family_replacement: "Mobile Safari", 354 | manufacturer: "Apple" 355 | }, { 356 | regex: "(iPod)", 357 | family_replacement: "Mobile Safari", 358 | manufacturer: "Apple" 359 | }, { 360 | regex: "(iPhone).*Version/(\\d+)\\.(\\d+)\\.(\\d+)", 361 | family_replacement: "Mobile Safari", 362 | manufacturer: "Apple" 363 | }, { 364 | regex: "(iPhone).*Version/(\\d+)\\.(\\d+)", 365 | family_replacement: "Mobile Safari", 366 | manufacturer: "Apple" 367 | }, { 368 | regex: "(iPhone)", 369 | family_replacement: "Mobile Safari", 370 | manufacturer: "Apple" 371 | }, { 372 | regex: "(iPad).*Version/(\\d+)\\.(\\d+)\\.(\\d+)", 373 | family_replacement: "Mobile Safari", 374 | tablet: true, 375 | manufacturer: "Apple" 376 | }, { 377 | regex: "(iPad).*Version/(\\d+)\\.(\\d+)", 378 | family_replacement: "Mobile Safari", 379 | tablet: true, 380 | manufacturer: "Apple" 381 | }, { 382 | regex: "(iPad)", 383 | family_replacement: "Mobile Safari", 384 | tablet: true, 385 | manufacturer: "Apple" 386 | }, { 387 | regex: "(AvantGo) (\\d+).(\\d+)", 388 | other: true 389 | }, { 390 | regex: "(Avant)", 391 | v1_replacement: "1", 392 | other: true 393 | }, { 394 | regex: "^(Nokia)", 395 | family_replacement: "Nokia Services (WAP) Browser", 396 | manufacturer: "Nokia" 397 | }, { 398 | regex: "(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)\\.(\\d+)", 399 | manufacturer: "Nokia" 400 | }, { 401 | regex: "(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)", 402 | manufacturer: "Nokia" 403 | }, { 404 | regex: "(NokiaBrowser)/(\\d+)\\.(\\d+)", 405 | manufacturer: "Nokia" 406 | }, { 407 | regex: "(BrowserNG)/(\\d+)\\.(\\d+).(\\d+)", 408 | family_replacement: "NokiaBrowser", 409 | manufacturer: "Nokia" 410 | }, { 411 | regex: "(Series60)/5\\.0", 412 | v2_replacement: "0", 413 | v1_replacement: "7", 414 | family_replacement: "NokiaBrowser", 415 | manufacturer: "Nokia" 416 | }, { 417 | regex: "(Series60)/(\\d+)\\.(\\d+)", 418 | family_replacement: "Nokia OSS Browser", 419 | manufacturer: "Nokia" 420 | }, { 421 | regex: "(S40OviBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)", 422 | family_replacement: "Nokia Series 40 Ovi Browser", 423 | manufacturer: "Nokia" 424 | }, { 425 | regex: "(Nokia)[EN]?(\\d+)", 426 | manufacturer: "Nokia" 427 | }, { 428 | regex: "(PlayBook).+RIM Tablet OS (\\d+)\\.(\\d+)\\.(\\d+)", 429 | family_replacement: "Blackberry WebKit", 430 | tablet: true, 431 | manufacturer: "Nokia" 432 | }, { 433 | regex: "(Black[bB]erry).+Version/(\\d+)\\.(\\d+)\\.(\\d+)", 434 | family_replacement: "Blackberry WebKit", 435 | manufacturer: "RIM" 436 | }, { 437 | regex: "(Black[bB]erry)\\s?(\\d+)", 438 | family_replacement: "Blackberry", 439 | manufacturer: "RIM" 440 | }, { 441 | regex: "(OmniWeb)/v(\\d+)\\.(\\d+)", 442 | other: true 443 | }, { 444 | regex: "(Blazer)/(\\d+)\\.(\\d+)", 445 | family_replacement: "Palm Blazer", 446 | manufacturer: "Palm" 447 | }, { 448 | regex: "(Pre)/(\\d+)\\.(\\d+)", 449 | family_replacement: "Palm Pre", 450 | manufacturer: "Palm" 451 | }, { 452 | regex: "(Links) \\((\\d+)\\.(\\d+)", 453 | other: true 454 | }, { 455 | regex: "(QtWeb) Internet Browser/(\\d+)\\.(\\d+)", 456 | other: true 457 | }, { 458 | regex: "(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+))?", 459 | other: true, 460 | tablet: true 461 | }, { 462 | regex: "(AppleWebKit)/(\\d+)\\.?(\\d+)?\\+ .* Version/\\d+\\.\\d+.\\d+ Safari/", 463 | family_replacement: "WebKit Nightly" 464 | }, { 465 | regex: "(Version)/(\\d+)\\.(\\d+)(?:\\.(\\d+))?.*Safari/", 466 | family_replacement: "Safari" 467 | }, { 468 | regex: "(Safari)/\\d+" 469 | }, { 470 | regex: "(OLPC)/Update(\\d+)\\.(\\d+)", 471 | other: true 472 | }, { 473 | regex: "(OLPC)/Update()\\.(\\d+)", 474 | v1_replacement: "0", 475 | other: true 476 | }, { 477 | regex: "(SEMC\\-Browser)/(\\d+)\\.(\\d+)", 478 | other: true 479 | }, { 480 | regex: "(Teleca)", 481 | family_replacement: "Teleca Browser", 482 | other: true 483 | }, { 484 | regex: "Trident(.*)rv.(\\d+)\\.(\\d+)", 485 | family_replacement: "IE" 486 | }, { 487 | regex: "(MSIE) (\\d+)\\.(\\d+)", 488 | family_replacement: "IE" 489 | }], 490 | os_parsers: [{ 491 | regex: "(Android) (\\d+)\\.(\\d+)(?:[.\\-]([a-z0-9]+))?" 492 | }, { 493 | regex: "(Android)\\-(\\d+)\\.(\\d+)(?:[.\\-]([a-z0-9]+))?" 494 | }, { 495 | regex: "(Android) Donut", 496 | os_v2_replacement: "2", 497 | os_v1_replacement: "1" 498 | }, { 499 | regex: "(Android) Eclair", 500 | os_v2_replacement: "1", 501 | os_v1_replacement: "2" 502 | }, { 503 | regex: "(Android) Froyo", 504 | os_v2_replacement: "2", 505 | os_v1_replacement: "2" 506 | }, { 507 | regex: "(Android) Gingerbread", 508 | os_v2_replacement: "3", 509 | os_v1_replacement: "2" 510 | }, { 511 | regex: "(Android) Honeycomb", 512 | os_v1_replacement: "3" 513 | }, { 514 | regex: "(Silk-Accelerated=[a-z]{4,5})", 515 | os_replacement: "Android" 516 | }, { 517 | regex: "(Windows Phone 6\\.5)" 518 | }, { 519 | regex: "(Windows (?:NT 5\\.2|NT 5\\.1))", 520 | os_replacement: "Windows XP" 521 | }, { 522 | regex: "(XBLWP7)", 523 | os_replacement: "Windows Phone OS" 524 | }, { 525 | regex: "(Windows NT 6\\.1)", 526 | os_replacement: "Windows 7" 527 | }, { 528 | regex: "(Windows NT 6\\.0)", 529 | os_replacement: "Windows Vista" 530 | }, { 531 | regex: "(Windows 98|Windows XP|Windows ME|Windows 95|Windows CE|Windows 7|Windows NT 4\\.0|Windows Vista|Windows 2000)" 532 | }, { 533 | regex: "(Windows NT 6\\.2)", 534 | os_replacement: "Windows 8" 535 | }, { 536 | regex: "(Windows Phone 8)", 537 | os_replacement: "Windows Phone 8" 538 | }, { 539 | regex: "(Windows NT 5\\.0)", 540 | os_replacement: "Windows 2000" 541 | }, { 542 | regex: "(Windows Phone OS) (\\d+)\\.(\\d+)" 543 | }, { 544 | regex: "(Windows ?Mobile)", 545 | os_replacement: "Windows Mobile" 546 | }, { 547 | regex: "(WinNT4.0)", 548 | os_replacement: "Windows NT 4.0" 549 | }, { 550 | regex: "(Win98)", 551 | os_replacement: "Windows 98" 552 | }, { 553 | regex: "(Tizen)/(\\d+)\\.(\\d+)", 554 | other: true 555 | }, { 556 | regex: "(Mac OS X) (\\d+)[_.](\\d+)(?:[_.](\\d+))?", 557 | manufacturer: "Apple" 558 | }, { 559 | regex: "(?:PPC|Intel) (Mac OS X)", 560 | manufacturer: "Apple" 561 | }, { 562 | regex: "(CPU OS|iPhone OS) (\\d+)_(\\d+)(?:_(\\d+))?", 563 | os_replacement: "iOS", 564 | manufacturer: "Apple" 565 | }, { 566 | regex: "(iPhone|iPad|iPod); Opera", 567 | os_replacement: "iOS", 568 | manufacturer: "Apple" 569 | }, { 570 | regex: "(iPad); Opera", 571 | tablet: true, 572 | manufacturer: "Apple" 573 | }, { 574 | regex: "(iPhone|iPad|iPod).*Mac OS X.*Version/(\\d+)\\.(\\d+)", 575 | os_replacement: "iOS", 576 | manufacturer: "Apple" 577 | }, { 578 | regex: "(CrOS) [a-z0-9_]+ (\\d+)\\.(\\d+)(?:\\.(\\d+))?", 579 | os_replacement: "Chrome OS" 580 | }, { 581 | regex: "(Debian)-(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+))?", 582 | other: true 583 | }, { 584 | regex: "(Linux Mint)(?:/(\\d+))?", 585 | other: true 586 | }, { 587 | regex: "(Mandriva)(?: Linux)?/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+))?", 588 | other: true 589 | }, { 590 | regex: "(Symbian[Oo][Ss])/(\\d+)\\.(\\d+)", 591 | os_replacement: "Symbian OS" 592 | }, { 593 | regex: "(Symbian/3).+NokiaBrowser/7\\.3", 594 | os_replacement: "Symbian^3 Anna" 595 | }, { 596 | regex: "(Symbian/3).+NokiaBrowser/7\\.4", 597 | os_replacement: "Symbian^3 Belle" 598 | }, { 599 | regex: "(Symbian/3)", 600 | os_replacement: "Symbian^3" 601 | }, { 602 | regex: "(Series 60|SymbOS|S60)", 603 | os_replacement: "Symbian OS" 604 | }, { 605 | regex: "(MeeGo)", 606 | other: true 607 | }, { 608 | regex: "Symbian [Oo][Ss]", 609 | os_replacement: "Symbian OS" 610 | }, { 611 | regex: "(Black[Bb]erry)[0-9a-z]+/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+))?", 612 | os_replacement: "BlackBerry OS", 613 | manufacturer: "RIM" 614 | }, { 615 | regex: "(Black[Bb]erry).+Version/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+))?", 616 | os_replacement: "BlackBerry OS", 617 | manufacturer: "RIM" 618 | }, { 619 | regex: "(RIM Tablet OS) (\\d+)\\.(\\d+)\\.(\\d+)", 620 | os_replacement: "BlackBerry Tablet OS", 621 | tablet: true, 622 | manufacturer: "RIM" 623 | }, { 624 | regex: "(Play[Bb]ook)", 625 | os_replacement: "BlackBerry Tablet OS", 626 | tablet: true, 627 | manufacturer: "RIM" 628 | }, { 629 | regex: "(Black[Bb]erry)", 630 | os_replacement: "Blackberry OS", 631 | manufacturer: "RIM" 632 | }, { 633 | regex: "(webOS|hpwOS)/(\\d+)\\.(\\d+)(?:\\.(\\d+))?", 634 | os_replacement: "webOS" 635 | }, { 636 | regex: "(SUSE|Fedora|Red Hat|PCLinuxOS)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)", 637 | other: true 638 | }, { 639 | regex: "(SUSE|Fedora|Red Hat|Puppy|PCLinuxOS|CentOS)/(\\d+)\\.(\\d+)\\.(\\d+)", 640 | other: true 641 | }, { 642 | regex: "(Ubuntu|Kindle|Bada|Lubuntu|BackTrack|Red Hat|Slackware)/(\\d+)\\.(\\d+)" 643 | }, { 644 | regex: "(Windows|OpenBSD|FreeBSD|NetBSD|Ubuntu|Kubuntu|Android|Arch Linux|CentOS|WeTab|Slackware)" 645 | }, { 646 | regex: "(Linux|BSD)", 647 | other: true 648 | }], 649 | mobile_os_families: ["Windows Phone 6.5", "Windows CE", "Symbian OS"], 650 | device_parsers: [{ 651 | regex: "HTC ([A-Z][a-z0-9]+) Build", 652 | device_replacement: "HTC $1", 653 | manufacturer: "HTC" 654 | }, { 655 | regex: "HTC ([A-Z][a-z0-9 ]+) \\d+\\.\\d+\\.\\d+\\.\\d+", 656 | device_replacement: "HTC $1", 657 | manufacturer: "HTC" 658 | }, { 659 | regex: "HTC_Touch_([A-Za-z0-9]+)", 660 | device_replacement: "HTC Touch ($1)", 661 | manufacturer: "HTC" 662 | }, { 663 | regex: "USCCHTC(\\d+)", 664 | device_replacement: "HTC $1 (US Cellular)", 665 | manufacturer: "HTC" 666 | }, { 667 | regex: "Sprint APA(9292)", 668 | device_replacement: "HTC $1 (Sprint)", 669 | manufacturer: "HTC" 670 | }, { 671 | regex: "HTC ([A-Za-z0-9]+ [A-Z])", 672 | device_replacement: "HTC $1", 673 | manufacturer: "HTC" 674 | }, { 675 | regex: "HTC-([A-Za-z0-9]+)", 676 | device_replacement: "HTC $1", 677 | manufacturer: "HTC" 678 | }, { 679 | regex: "HTC_([A-Za-z0-9]+)", 680 | device_replacement: "HTC $1", 681 | manufacturer: "HTC" 682 | }, { 683 | regex: "HTC ([A-Za-z0-9]+)", 684 | device_replacement: "HTC $1", 685 | manufacturer: "HTC" 686 | }, { 687 | regex: "(ADR[A-Za-z0-9]+)", 688 | device_replacement: "HTC $1", 689 | manufacturer: "HTC" 690 | }, { 691 | regex: "(HTC)", 692 | manufacturer: "HTC" 693 | }, { 694 | regex: "SonyEricsson([A-Za-z0-9]+)/", 695 | device_replacement: "Ericsson $1", 696 | other: true, 697 | manufacturer: "Sony" 698 | }, { 699 | regex: "Android[\\- ][\\d]+\\.[\\d]+\\; [A-Za-z]{2}\\-[A-Za-z]{2}\\; WOWMobile (.+) Build" 700 | }, { 701 | regex: "Android[\\- ][\\d]+\\.[\\d]+\\.[\\d]+; [A-Za-z]{2}\\-[A-Za-z]{2}\\; (.+) Build" 702 | }, { 703 | regex: "Android[\\- ][\\d]+\\.[\\d]+\\-update1\\; [A-Za-z]{2}\\-[A-Za-z]{2}\\; (.+) Build" 704 | }, { 705 | regex: "Android[\\- ][\\d]+\\.[\\d]+\\; [A-Za-z]{2}\\-[A-Za-z]{2}\\; (.+) Build" 706 | }, { 707 | regex: "Android[\\- ][\\d]+\\.[\\d]+\\.[\\d]+; (.+) Build" 708 | }, { 709 | regex: "NokiaN([0-9]+)", 710 | device_replacement: "Nokia N$1", 711 | manufacturer: "Nokia" 712 | }, { 713 | regex: "Nokia([A-Za-z0-9\\v-]+)", 714 | device_replacement: "Nokia $1", 715 | manufacturer: "Nokia" 716 | }, { 717 | regex: "NOKIA ([A-Za-z0-9\\-]+)", 718 | device_replacement: "Nokia $1", 719 | manufacturer: "Nokia" 720 | }, { 721 | regex: "Nokia ([A-Za-z0-9\\-]+)", 722 | device_replacement: "Nokia $1", 723 | manufacturer: "Nokia" 724 | }, { 725 | regex: "Lumia ([A-Za-z0-9\\-]+)", 726 | device_replacement: "Lumia $1", 727 | manufacturer: "Nokia" 728 | }, { 729 | regex: "Symbian", 730 | device_replacement: "Nokia", 731 | manufacturer: "Nokia" 732 | }, { 733 | regex: "(PlayBook).+RIM Tablet OS", 734 | device_replacement: "Blackberry Playbook", 735 | tablet: true, 736 | manufacturer: "RIM" 737 | }, { 738 | regex: "(Black[Bb]erry [0-9]+);", 739 | manufacturer: "RIM" 740 | }, { 741 | regex: "Black[Bb]erry([0-9]+)", 742 | device_replacement: "BlackBerry $1", 743 | manufacturer: "RIM" 744 | }, { 745 | regex: "(Pre)/(\\d+)\\.(\\d+)", 746 | device_replacement: "Palm Pre", 747 | manufacturer: "Palm" 748 | }, { 749 | regex: "(Pixi)/(\\d+)\\.(\\d+)", 750 | device_replacement: "Palm Pixi", 751 | manufacturer: "Palm" 752 | }, { 753 | regex: "(Touchpad)/(\\d+)\\.(\\d+)", 754 | device_replacement: "HP Touchpad", 755 | manufacturer: "HP" 756 | }, { 757 | regex: "HPiPAQ([A-Za-z0-9]+)/(\\d+).(\\d+)", 758 | device_replacement: "HP iPAQ $1", 759 | manufacturer: "HP" 760 | }, { 761 | regex: "Palm([A-Za-z0-9]+)", 762 | device_replacement: "Palm $1", 763 | manufacturer: "Palm" 764 | }, { 765 | regex: "Treo([A-Za-z0-9]+)", 766 | device_replacement: "Palm Treo $1", 767 | manufacturer: "Palm" 768 | }, { 769 | regex: "webOS.*(P160UNA)/(\\d+).(\\d+)", 770 | device_replacement: "HP Veer", 771 | manufacturer: "HP" 772 | }, { 773 | regex: "(Kindle Fire)", 774 | manufacturer: "Amazon" 775 | }, { 776 | regex: "(Kindle)", 777 | manufacturer: "Amazon" 778 | }, { 779 | regex: "(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+))?", 780 | device_replacement: "Kindle Fire", 781 | tablet: true, 782 | manufacturer: "Amazon" 783 | }, { 784 | regex: "(iPad) Simulator;", 785 | manufacturer: "Apple" 786 | }, { 787 | regex: "(iPad);", 788 | manufacturer: "Apple" 789 | }, { 790 | regex: "(iPod);", 791 | manufacturer: "Apple" 792 | }, { 793 | regex: "(iPhone) Simulator;", 794 | manufacturer: "Apple" 795 | }, { 796 | regex: "(iPhone);", 797 | manufacturer: "Apple" 798 | }, { 799 | regex: "Nexus\\ ([A-Za-z0-9\\-]+)", 800 | device_replacement: "Nexus $1" 801 | }, { 802 | regex: "acer_([A-Za-z0-9]+)_", 803 | device_replacement: "Acer $1", 804 | manufacturer: "Acer" 805 | }, { 806 | regex: "acer_([A-Za-z0-9]+)_", 807 | device_replacement: "Acer $1", 808 | manufacturer: "Acer" 809 | }, { 810 | regex: "Amoi\\-([A-Za-z0-9]+)", 811 | device_replacement: "Amoi $1", 812 | other: true, 813 | manufacturer: "Amoi" 814 | }, { 815 | regex: "AMOI\\-([A-Za-z0-9]+)", 816 | device_replacement: "Amoi $1", 817 | other: true, 818 | manufacturer: "Amoi" 819 | }, { 820 | regex: "Asus\\-([A-Za-z0-9]+)", 821 | device_replacement: "Asus $1", 822 | manufacturer: "Asus" 823 | }, { 824 | regex: "ASUS\\-([A-Za-z0-9]+)", 825 | device_replacement: "Asus $1", 826 | manufacturer: "Asus" 827 | }, { 828 | regex: "BIRD\\-([A-Za-z0-9]+)", 829 | device_replacement: "Bird $1", 830 | other: true 831 | }, { 832 | regex: "BIRD\\.([A-Za-z0-9]+)", 833 | device_replacement: "Bird $1", 834 | other: true 835 | }, { 836 | regex: "BIRD ([A-Za-z0-9]+)", 837 | device_replacement: "Bird $1", 838 | other: true 839 | }, { 840 | regex: "Dell ([A-Za-z0-9]+)", 841 | device_replacement: "Dell $1", 842 | manufacturer: "Dell" 843 | }, { 844 | regex: "DoCoMo/2\\.0 ([A-Za-z0-9]+)", 845 | device_replacement: "DoCoMo $1", 846 | other: true 847 | }, { 848 | regex: "([A-Za-z0-9]+)\\_W\\;FOMA", 849 | device_replacement: "DoCoMo $1", 850 | other: true 851 | }, { 852 | regex: "([A-Za-z0-9]+)\\;FOMA", 853 | device_replacement: "DoCoMo $1", 854 | other: true 855 | }, { 856 | regex: "vodafone([A-Za-z0-9]+)", 857 | device_replacement: "Huawei Vodafone $1", 858 | other: true 859 | }, { 860 | regex: "i\\-mate ([A-Za-z0-9]+)", 861 | device_replacement: "i-mate $1", 862 | other: true 863 | }, { 864 | regex: "Kyocera\\-([A-Za-z0-9]+)", 865 | device_replacement: "Kyocera $1", 866 | other: true 867 | }, { 868 | regex: "KWC\\-([A-Za-z0-9]+)", 869 | device_replacement: "Kyocera $1", 870 | other: true 871 | }, { 872 | regex: "Lenovo\\-([A-Za-z0-9]+)", 873 | device_replacement: "Lenovo $1", 874 | manufacturer: "Lenovo" 875 | }, { 876 | regex: "Lenovo\\_([A-Za-z0-9]+)", 877 | device_replacement: "Lenovo $1", 878 | manufacturer: "Levovo" 879 | }, { 880 | regex: "LG/([A-Za-z0-9]+)", 881 | device_replacement: "LG $1", 882 | manufacturer: "LG" 883 | }, { 884 | regex: "LG-LG([A-Za-z0-9]+)", 885 | device_replacement: "LG $1", 886 | manufacturer: "LG" 887 | }, { 888 | regex: "LGE-LG([A-Za-z0-9]+)", 889 | device_replacement: "LG $1", 890 | manufacturer: "LG" 891 | }, { 892 | regex: "LGE VX([A-Za-z0-9]+)", 893 | device_replacement: "LG $1", 894 | manufacturer: "LG" 895 | }, { 896 | regex: "LG ([A-Za-z0-9]+)", 897 | device_replacement: "LG $1", 898 | manufacturer: "LG" 899 | }, { 900 | regex: "LGE LG\\-AX([A-Za-z0-9]+)", 901 | device_replacement: "LG $1", 902 | manufacturer: "LG" 903 | }, { 904 | regex: "LG\\-([A-Za-z0-9]+)", 905 | device_replacement: "LG $1", 906 | manufacturer: "LG" 907 | }, { 908 | regex: "LGE\\-([A-Za-z0-9]+)", 909 | device_replacement: "LG $1", 910 | manufacturer: "LG" 911 | }, { 912 | regex: "LG([A-Za-z0-9]+)", 913 | device_replacement: "LG $1", 914 | manufacturer: "LG" 915 | }, { 916 | regex: "(KIN)\\.One (\\d+)\\.(\\d+)", 917 | device_replacement: "Microsoft $1" 918 | }, { 919 | regex: "(KIN)\\.Two (\\d+)\\.(\\d+)", 920 | device_replacement: "Microsoft $1" 921 | }, { 922 | regex: "(Motorola)\\-([A-Za-z0-9]+)", 923 | manufacturer: "Motorola" 924 | }, { 925 | regex: "MOTO\\-([A-Za-z0-9]+)", 926 | device_replacement: "Motorola $1", 927 | manufacturer: "Motorola" 928 | }, { 929 | regex: "MOT\\-([A-Za-z0-9]+)", 930 | device_replacement: "Motorola $1", 931 | manufacturer: "Motorola" 932 | }, { 933 | regex: "Philips([A-Za-z0-9]+)", 934 | device_replacement: "Philips $1", 935 | manufacturer: "Philips" 936 | }, { 937 | regex: "Philips ([A-Za-z0-9]+)", 938 | device_replacement: "Philips $1", 939 | manufacturer: "Philips" 940 | }, { 941 | regex: "SAMSUNG-([A-Za-z0-9\\-]+)", 942 | device_replacement: "Samsung $1", 943 | manufacturer: "Samsung" 944 | }, { 945 | regex: "SAMSUNG\\; ([A-Za-z0-9\\-]+)", 946 | device_replacement: "Samsung $1", 947 | manufacturer: "Samsung" 948 | }, { 949 | regex: "Softbank/1\\.0/([A-Za-z0-9]+)", 950 | device_replacement: "Softbank $1", 951 | other: true 952 | }, { 953 | regex: "Softbank/2\\.0/([A-Za-z0-9]+)", 954 | device_replacement: "Softbank $1", 955 | other: true 956 | }, { 957 | regex: "(hiptop|avantgo|plucker|xiino|blazer|elaine|up.browser|up.link|mmp|smartphone|midp|wap|vodafone|o2|pocket|mobile|pda)", 958 | device_replacement: "Generic Smartphone" 959 | }, { 960 | regex: "^(1207|3gso|4thp|501i|502i|503i|504i|505i|506i|6310|6590|770s|802s|a wa|acer|acs\\-|airn|alav|asus|attw|au\\-m|aur |aus |abac|acoo|aiko|alco|alca|amoi|anex|anny|anyw|aptu|arch|argo|bell|bird|bw\\-n|bw\\-u|beck|benq|bilb|blac|c55/|cdm\\-|chtm|capi|comp|cond|craw|dall|dbte|dc\\-s|dica|ds\\-d|ds12|dait|devi|dmob|doco|dopo|el49|erk0|esl8|ez40|ez60|ez70|ezos|ezze|elai|emul|eric|ezwa|fake|fly\\-|fly\\_|g\\-mo|g1 u|g560|gf\\-5|grun|gene|go.w|good|grad|hcit|hd\\-m|hd\\-p|hd\\-t|hei\\-|hp i|hpip|hs\\-c|htc |htc\\-|htca|htcg)", 961 | device_replacement: "Generic Feature Phone" 962 | }, { 963 | regex: "^(htcp|htcs|htct|htc\\_|haie|hita|huaw|hutc|i\\-20|i\\-go|i\\-ma|i230|iac|iac\\-|iac/|ig01|im1k|inno|iris|jata|java|kddi|kgt|kgt/|kpt |kwc\\-|klon|lexi|lg g|lg\\-a|lg\\-b|lg\\-c|lg\\-d|lg\\-f|lg\\-g|lg\\-k|lg\\-l|lg\\-m|lg\\-o|lg\\-p|lg\\-s|lg\\-t|lg\\-u|lg\\-w|lg/k|lg/l|lg/u|lg50|lg54|lge\\-|lge/|lynx|leno|m1\\-w|m3ga|m50/|maui|mc01|mc21|mcca|medi|meri|mio8|mioa|mo01|mo02|mode|modo|mot |mot\\-|mt50|mtp1|mtv |mate|maxo|merc|mits|mobi|motv|mozz|n100|n101|n102|n202|n203|n300|n302|n500|n502|n505|n700|n701|n710|nec\\-|nem\\-|newg|neon)", 964 | device_replacement: "Generic Feature Phone" 965 | }, { 966 | regex: "^(netf|noki|nzph|o2 x|o2\\-x|opwv|owg1|opti|oran|ot\\-s|p800|pand|pg\\-1|pg\\-2|pg\\-3|pg\\-6|pg\\-8|pg\\-c|pg13|phil|pn\\-2|pt\\-g|palm|pana|pire|pock|pose|psio|qa\\-a|qc\\-2|qc\\-3|qc\\-5|qc\\-7|qc07|qc12|qc21|qc32|qc60|qci\\-|qwap|qtek|r380|r600|raks|rim9|rove|s55/|sage|sams|sc01|sch\\-|scp\\-|sdk/|se47|sec\\-|sec0|sec1|semc|sgh\\-|shar|sie\\-|sk\\-0|sl45|slid|smb3|smt5|sp01|sph\\-|spv |spv\\-|sy01|samm|sany|sava|scoo|send|siem|smar|smit|soft|sony|t\\-mo|t218|t250|t600|t610|t618|tcl\\-|tdg\\-|telm|tim\\-|ts70|tsm\\-|tsm3|tsm5|tx\\-9|tagt)", 967 | device_replacement: "Generic Feature Phone" 968 | }, { 969 | regex: "^(talk|teli|topl|tosh|up.b|upg1|utst|v400|v750|veri|vk\\-v|vk40|vk50|vk52|vk53|vm40|vx98|virg|vite|voda|vulc|w3c |w3c\\-|wapj|wapp|wapu|wapm|wig |wapi|wapr|wapv|wapy|wapa|waps|wapt|winc|winw|wonu|x700|xda2|xdag|yas\\-|your|zte\\-|zeto|aste|audi|avan|blaz|brew|brvw|bumb|ccwa|cell|cldc|cmd\\-|dang|eml2|fetc|hipt|http|ibro|idea|ikom|ipaq|jbro|jemu|jigs|keji|kyoc|kyok|libw|m\\-cr|midp|mmef|moto|mwbp|mywa|newt|nok6|o2im|pant|pdxg|play|pluc|port|prox|rozo|sama|seri|smal|symb|treo|upsi|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|wap\\-|webc|whit|wmlb|xda\\-|xda\\_)", 970 | device_replacement: "Generic Feature Phone" 971 | }, { 972 | regex: "(bot|borg|google(^tv)|yahoo|slurp|msnbot|msrbot|openbot|archiver|netresearch|lycos|scooter|altavista|teoma|gigabot|baiduspider|blitzbot|oegp|charlotte|furlbot|http%20client|polybot|htdig|ichiro|mogimogi|larbin|pompos|scrubby|searchsight|seekbot|semanticdiscovery|silk|snappy|speedy|spider|voila|vortex|voyager|zao|zeal|fast\\-webcrawler|converacrawler|dataparksearch|findlinks)", 973 | device_replacement: "Spider" 974 | }], 975 | mobile_browser_families: ["Firefox Mobile", "Opera Mobile", "Opera Mini", "Mobile Safari", "webOS", "IE Mobile", "Playstation Portable", "Nokia", "Blackberry", "Palm", "Silk", "Android", "Maemo", "Obigo", "Netfront", "AvantGo", "Teleca", "SEMC-Browser", "Bolt", "Iris", "UP.Browser", "Symphony", "Minimo", "Bunjaloo", "Jasmine", "Dolfin", "Polaris", "BREW", "Chrome Mobile", "Chrome Mobile iOS", "UC Browser", "Tizen Browser"] 976 | }; 977 | // Parsers 978 | _this.parsers = ["device_parsers", "browser_parsers", "os_parsers", "mobile_os_families", "mobile_browser_families"]; 979 | // Types 980 | _this.types = ["browser", "os", "device"]; 981 | // Regular Expressions 982 | _this.regexes = regexes || function() { 983 | var results = {}; 984 | _this.parsers.map(function(parser) { 985 | results[parser] = []; 986 | }); 987 | return results; 988 | }(); 989 | // Families 990 | _this.families = function() { 991 | var results = {}; 992 | _this.types.map(function(type) { 993 | results[type] = []; 994 | }); 995 | return results; 996 | }(); 997 | // Utility Variables 998 | var ArrayProto = Array.prototype, 999 | ObjProto = Object.prototype, 1000 | FuncProto = Function.prototype, 1001 | nativeForEach = ArrayProto.forEach, 1002 | nativeIndexOf = ArrayProto.indexOf; 1003 | // Find Utility 1004 | var find = function(ua, obj) { 1005 | var ret = {}; 1006 | for (var i = 0; i < obj.length; i++) { 1007 | ret = obj[i](ua); 1008 | if (ret) { 1009 | break; 1010 | } 1011 | } 1012 | return ret; 1013 | }; 1014 | // Remove Utility 1015 | var remove = function(arr, props) { 1016 | each(arr, function(obj) { 1017 | each(props, function(prop) { 1018 | delete obj[prop]; 1019 | }); 1020 | }); 1021 | }; 1022 | // Contains Utility 1023 | var contains = function(obj, target) { 1024 | var found = false; 1025 | if (obj == null) return found; 1026 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 1027 | found = any(obj, function(value) { 1028 | return value === target; 1029 | }); 1030 | return found; 1031 | }; 1032 | // Each Utility 1033 | var each = forEach = function(obj, iterator, context) { 1034 | if (obj == null) return; 1035 | if (nativeForEach && obj.forEach === nativeForEach) { 1036 | obj.forEach(iterator, context); 1037 | } else if (obj.length === +obj.length) { 1038 | for (var i = 0, l = obj.length; i < l; i++) { 1039 | iterator.call(context, obj[i], i, obj); 1040 | } 1041 | } else { 1042 | for (var key in obj) { 1043 | if (_.has(obj, key)) { 1044 | iterator.call(context, obj[key], key, obj); 1045 | } 1046 | } 1047 | } 1048 | }; 1049 | // Extend Utiltiy 1050 | var extend = function(obj) { 1051 | each(slice.call(arguments, 1), function(source) { 1052 | for (var prop in source) { 1053 | obj[prop] = source[prop]; 1054 | } 1055 | }); 1056 | return obj; 1057 | }; 1058 | // Check String Utility 1059 | var check = function(str) { 1060 | return !!(str && typeof str != "undefined" && str != null); 1061 | }; 1062 | // To Version String Utility 1063 | var toVersionString = function(obj) { 1064 | var output = ""; 1065 | obj = obj || {}; 1066 | if (check(obj)) { 1067 | if (check(obj.major)) { 1068 | output += obj.major; 1069 | if (check(obj.minor)) { 1070 | output += "." + obj.minor; 1071 | if (check(obj.patch)) { 1072 | output += "." + obj.patch; 1073 | } 1074 | } 1075 | } 1076 | } 1077 | return output; 1078 | }; 1079 | // To String Utility 1080 | var toString = function(obj) { 1081 | obj = obj || {}; 1082 | var suffix = toVersionString(obj); 1083 | if (suffix) suffix = " " + suffix; 1084 | return obj && check(obj.family) ? obj.family + suffix : ""; 1085 | }; 1086 | // Parse User-Agent String 1087 | _this.parse = function(ua) { 1088 | // Parsers Utility 1089 | var parsers = function(type) { 1090 | return _this.regexes[type + "_parsers"].map(function(obj) { 1091 | var regexp = new RegExp(obj.regex), 1092 | rep = obj[(type === "browser" ? "family" : type) + "_replacement"], 1093 | major_rep = obj.major_version_replacement; 1094 | 1095 | function parser(ua) { 1096 | var m = ua.match(regexp); 1097 | if (!m) return null; 1098 | var ret = {}; 1099 | ret.family = (rep ? rep.replace("$1", m[1]) : m[1]) || "other"; 1100 | ret.major = parseInt(major_rep ? major_rep : m[2]) || null; 1101 | ret.minor = m[3] ? parseInt(m[3]) : null; 1102 | ret.patch = m[4] ? parseInt(m[4]) : null; 1103 | ret.tablet = obj.tablet; 1104 | ret.man = obj.manufacturer || null; 1105 | return ret; 1106 | } 1107 | return parser; 1108 | }); 1109 | }; 1110 | // User Agent 1111 | var UserAgent = function() {}; 1112 | // Browsers Parsed 1113 | var browser_parsers = parsers("browser"); 1114 | // Operating Systems Parsed 1115 | var os_parsers = parsers("os"); 1116 | // Devices Parsed 1117 | var device_parsers = parsers("device"); 1118 | // Set Agent 1119 | var a = new UserAgent(); 1120 | // Remember the original user agent string 1121 | a.source = ua; 1122 | // Set Browser 1123 | a.browser = find(ua, browser_parsers); 1124 | if (check(a.browser)) { 1125 | a.browser.name = toString(a.browser); 1126 | a.browser.version = toVersionString(a.browser); 1127 | } else { 1128 | a.browser = {}; 1129 | } 1130 | // Set OS 1131 | a.os = find(ua, os_parsers); 1132 | if (check(a.os)) { 1133 | a.os.name = toString(a.os); 1134 | a.os.version = toVersionString(a.os); 1135 | } else { 1136 | a.os = {}; 1137 | } 1138 | // Set Device 1139 | a.device = find(ua, device_parsers); 1140 | if (check(a.device)) { 1141 | a.device.name = toString(a.device); 1142 | a.device.version = toVersionString(a.device); 1143 | } else { 1144 | a.device = { 1145 | tablet: false, 1146 | family: "Other" 1147 | }; 1148 | } 1149 | // Determine Device Type 1150 | var mobile_agents = {}; 1151 | var mobile_browser_families = _this.regexes.mobile_browser_families.map(function(str) { 1152 | mobile_agents[str] = true; 1153 | }); 1154 | var mobile_os_families = _this.regexes.mobile_os_families.map(function(str) { 1155 | mobile_agents[str] = true; 1156 | }); 1157 | // Is Spider 1158 | if (a.browser.family === "Spider") { 1159 | a.device.type = "Spider"; 1160 | } else if (a.browser.tablet || a.os.tablet || a.device.tablet) { 1161 | a.device.type = "Tablet"; 1162 | } else if (mobile_agents.hasOwnProperty(a.browser.family)) { 1163 | a.device.type = "Mobile"; 1164 | } else { 1165 | a.device.type = "Desktop"; 1166 | } 1167 | // Determine Device Manufacturer 1168 | a.device.manufacturer = a.browser.man || a.os.man || a.device.man || null; 1169 | // Cleanup Objects 1170 | remove([a.browser, a.os, a.device], ["tablet", "man"]); 1171 | // Return Agent 1172 | return a; 1173 | }; 1174 | // Return context 1175 | return _this; 1176 | }(); 1177 | // Export the Underscore object for **Node.js** and **"CommonJS"**, 1178 | // backwards-compatibility for the old `require()` API. If we're not 1179 | // CommonJS, add `_` to the global object via a string identifier 1180 | // the Closure Compiler "advanced" mode. Registration as an AMD 1181 | // via define() happens at the end of this file 1182 | if (typeof exports !== "undefined") { 1183 | if (typeof module !== "undefined" && module.exports) { 1184 | exports = module.exports = detect; 1185 | } 1186 | exports.detect = detect; 1187 | } else { 1188 | root["detect"] = detect; 1189 | } 1190 | // AMD define happens at the end for compatibility with AMD 1191 | // that don't enforce next-turn semantics on modules 1192 | if (typeof define === "function" && define.amd) { 1193 | define(function(require) { 1194 | return detect; 1195 | }); 1196 | } 1197 | })(window); -------------------------------------------------------------------------------- /public/device.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clients app entry 3 | */ 4 | !function (){ 5 | 'use strict'; 6 | 7 | var socket = io.connect(window.location.origin + '/device') 8 | function $get(options, success, error) { 9 | var xhr = new XMLHttpRequest(); 10 | xhr.onreadystatechange = function() { 11 | if (xhr.readyState == 4 && xhr.status == 200 && xhr.responseText) { 12 | success && success(xhr.responseText, xhr); 13 | } else if (xhr.readyState == 4 && xhr.status != 200) { 14 | error && error(xhr.response, xhr); 15 | } 16 | } 17 | xhr.open('GET', options.url); 18 | xhr.send(null); 19 | } 20 | var script = ''.replace('$host', location.origin) 21 | Zect.namespace('r') 22 | new Zect({ 23 | el: '#app', 24 | data: function() { 25 | return { 26 | clients: [], 27 | clientInfos: {}, 28 | activeDevice: '', 29 | inited: false, 30 | hoverMask: false, 31 | script: script, 32 | copyScript: script 33 | } 34 | }, 35 | ready: function() { 36 | this._$previewFrame = this.$el.querySelector('.previewFrame') 37 | this.$data.inited = true 38 | this.fetch() 39 | socket.on('device:update', function() { 40 | this.fetch() 41 | }.bind(this)) 42 | 43 | var client = new ZeroClipboard( this.$el.querySelector(".copy") ); 44 | var vm = this 45 | var pendding 46 | client.on( "ready", function( readyEvent ) { 47 | client.on( "aftercopy", function( event ) { 48 | if (pendding) return 49 | vm.$data.script = 'Copy success, paste it into HTML Document ~' 50 | pendding = true 51 | setTimeout(function () { 52 | pendding = false 53 | vm.$data.script = script 54 | }, 1500) 55 | }); 56 | }); 57 | }, 58 | methods: { 59 | fetch: function() { 60 | var vm = this 61 | $get({ 62 | url: '/clients' 63 | }, function(data) { 64 | data = JSON.parse(data) 65 | vm.$data.clientInfos = data 66 | vm.$data.clients = Object.keys(data).map(function(k) { 67 | var item = data[k] 68 | return { 69 | cid: item.clientId, 70 | ua: item.userAgent, 71 | info: detect.parse(item.userAgent) 72 | } 73 | }) 74 | }) 75 | }, 76 | onPreview: function (e) { 77 | var cid = e.currentTarget.dataset.cid 78 | this.$data.activeDevice = cid 79 | this.updatePreview(cid) 80 | }, 81 | onView: function () { 82 | if (!this.$data.activeDevice) return 83 | 84 | window.open('/inspector.html?cid=' + this.$data.activeDevice, 'jsinspector:this.$data.activeDevice') 85 | }, 86 | onHoverMask: function () { 87 | this.$data.hoverMask = true 88 | }, 89 | onLeaveMask: function () { 90 | this.$data.hoverMask = false 91 | }, 92 | updatePreview: function(cid) { 93 | this._$previewFrame.src = '/preview/' + cid 94 | } 95 | } 96 | }) 97 | }(); 98 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/favicon.ico -------------------------------------------------------------------------------- /public/images/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/images/android.png -------------------------------------------------------------------------------- /public/images/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/images/copy.png -------------------------------------------------------------------------------- /public/images/ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/images/ios.png -------------------------------------------------------------------------------- /public/images/pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/images/pc.png -------------------------------------------------------------------------------- /public/images/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switer/jsinspector/8a0a5436d267eb17abac1c2fafd106df55f6bd05/public/images/phone.png -------------------------------------------------------------------------------- /public/inspector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JSInspector • Device 8 | 9 | 10 | 40 | 41 | 42 | 43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /public/inspector.js: -------------------------------------------------------------------------------- 1 | !(function () { 2 | 'use strict'; 3 | 4 | function detectmob() { 5 | if ( navigator.userAgent.match(/Android/i) 6 | || navigator.userAgent.match(/webOS/i) 7 | || navigator.userAgent.match(/iPhone/i) 8 | || navigator.userAgent.match(/iPad/i) 9 | || navigator.userAgent.match(/iPod/i) 10 | || navigator.userAgent.match(/BlackBerry/i) 11 | || navigator.userAgent.match(/Windows Phone/i) 12 | ) { 13 | return true; 14 | } else { 15 | return false; 16 | } 17 | } 18 | 19 | var isMobile = detectmob() 20 | var slice = Array.prototype.slice 21 | var socket = io.connect(window.location.origin + '/inpsector') 22 | var cliendId = queryParse().cid 23 | var inspectedWindow = document.querySelector('#inspectedWindow') 24 | var documentBase = '' 25 | var serverTime = Number('<%= serverTime %>') 26 | var clientTime = + new Date 27 | 28 | function queryParse() { 29 | var search = location.search 30 | if (!search) return {}; 31 | var spliter = '&'; 32 | var query = search.replace(/^\?/, ''), 33 | queries = {}, 34 | splits = query ? query.split(spliter) : null; 35 | if (splits && splits.length > 0) { 36 | splits.forEach(function(item) { 37 | item = item.split('='); 38 | var key = item[0], 39 | value = item[1]; 40 | queries[key] = value; 41 | }); 42 | } 43 | return queries; 44 | } 45 | /** 46 | * update inspected device view 47 | **/ 48 | var responsiveElements = [].slice.call(document.querySelectorAll('.iPhone,.iPhoneInner,.iScreen,#inspectedWindow')) 49 | var widthPatches = [30, 26, 0, 0] 50 | var heightPatches = [100, 96, 0, 0] 51 | 52 | function $update (data) { 53 | if (data.browser && data.browser.clientWidth) { 54 | // inspectedWindow.style.width = data.browser.clientWidth + 'px' 55 | var width = data.browser.clientWidth 56 | var height = data.browser.clientHeight 57 | var cwidth = document.documentElement.clientWidth - 50 58 | var cheight = document.documentElement.clientHeight - 130 59 | 60 | width = width > cwidth ? cwidth : width 61 | height = height > cheight ? cheight : height 62 | 63 | responsiveElements.forEach(function (el, index) { 64 | el.style.width = (width + widthPatches[index]) + 'px' 65 | el.style.height = (height + heightPatches[index]) + 'px' 66 | }) 67 | // inspectedWindow.style.width = '320px' 68 | } 69 | 70 | var ispDoc = inspectedWindow.contentDocument 71 | var ispWin = inspectedWindow.contentWindow 72 | var needReload = !!data.html 73 | 74 | if (needReload) { // full amount download 75 | documentBase = data.html; 76 | writeDocument(ispDoc, ispWin, documentBase); 77 | } 78 | 79 | if (data.meta.scrollTop !== undefined) { 80 | // update some metas only 81 | ispWin.scrollTo(0, data.meta.scrollTop) 82 | } 83 | 84 | // element partial scrolling 85 | ;(data.meta.scrollElements || []).forEach(function (item) { 86 | var el = ispDoc.querySelector(item.xpath) 87 | if (!el) return 88 | if (needReload) { 89 | setTimeout(function () { 90 | el.scrollTop = item.scrollTop 91 | }, 100) 92 | } else { 93 | el.scrollTop = item.scrollTop 94 | } 95 | }) 96 | 97 | if (data.meta.consoles) { 98 | var consoles = data.meta.consoles; 99 | consoles.forEach(function (item) { 100 | console[item.type].apply(console, eval('(' + item.args + ')')); 101 | }); 102 | } 103 | } 104 | /** 105 | * Write iframe document 106 | **/ 107 | function writeDocument (ispDoc, ispWin, html) { 108 | ispDoc.open(); 109 | // remove inspector CORS frame src and save dom for Xpath 110 | html = html.replace(/= MAX_RETRY_TIMES) { 168 | alert(err); 169 | } else { 170 | retryTimes ++; 171 | initialize(); 172 | } 173 | }); 174 | } 175 | 176 | /** 177 | * initialize after window load 178 | **/ 179 | window.addEventListener('load', function () { 180 | initialize(); 181 | }); 182 | 183 | inject.on(function (payload) { 184 | socket.emit('inspector:input', { 185 | id: cliendId, 186 | payload: payload 187 | }) 188 | }) 189 | })(); -------------------------------------------------------------------------------- /public/reverse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * reverse inject script to client side 3 | **/ 4 | !function (exports) { 5 | 'use strict'; 6 | 7 | var type = function (obj) { 8 | return Object.prototype.toString.call(obj).match(/object ([a-zA-Z]+)/)[1].toLowerCase() 9 | } 10 | function commander () { 11 | var args = Array.prototype.slice.call(arguments); 12 | var command = args.shift(); 13 | while (args.length) { 14 | command = command.replace(/%s/, args.shift()) 15 | } 16 | return command; 17 | } 18 | var inject = function () { 19 | var command; 20 | if (type(arguments[0]) == 'function') { 21 | command = '(' + arguments[0].toString() + ')()' 22 | } else { 23 | command = commander.apply(null, arguments) 24 | } 25 | inject.push({ 26 | type: 'eval', 27 | value: command 28 | }) 29 | } 30 | 31 | inject.commands = [] 32 | 33 | /** 34 | * access 35 | **/ 36 | inject.clear = function () { 37 | inject.commands = [] 38 | } 39 | inject.push = function (payload) { 40 | // payload = JSON.stringify(payload) 41 | inject.commands.push(payload) 42 | handlers.forEach(function (h) { 43 | h.call(null, payload) 44 | }) 45 | } 46 | 47 | /** 48 | * events 49 | **/ 50 | var handlers = [] 51 | inject.on = function (handler) { 52 | handlers.push(handler) 53 | } 54 | /** 55 | * resources 56 | **/ 57 | inject.js = function () { 58 | var args = Array.prototype.slice.call(arguments); 59 | inject.push({ 60 | type: 'js', 61 | value: args 62 | }) 63 | } 64 | inject.css = function () { 65 | var args = Array.prototype.slice.call(arguments); 66 | inject.push({ 67 | type: 'css', 68 | value: args 69 | }) 70 | } 71 | exports.inject = inject; 72 | 73 | }(window); -------------------------------------------------------------------------------- /public/zect.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Zect v1.2.0 3 | * (c) 2015 guankaishe 4 | * Released under the MIT License. 5 | */ 6 | !function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):"object"==typeof exports?exports.Zect=b():a.Zect=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){a.exports=c(1)},function(a,b,c){"use strict";function d(a){var b=k([a]);return e.call(this,b)}function e(a){function b(a,b){a&&a.bindings&&a.bindings.push(b)}function c(a,b,c){var e,f=!0;switch(a.nodeType){case 1:if(a=d(a),e=h(a,b,c)){f=!1;break}if(j(a,b),e=i(a,x,b,c)){f=!1;break}break;case 3:e=k(a,x,b,c),f=!1;break;case 11:break;default:f=!1}return{into:!!f,inst:!e&&c?new r(a):e}}function d(a){var b,c,e=H+"repeat",f=H+"if";if(a.hasAttribute(f))c=f,b="is";else{if(!a.hasAttribute(e))return a;c=e,b="items"}var g=a.getAttribute(c),h=document.createElement(c);for(a.removeAttribute(c),a.parentNode&&a.parentNode.replaceChild(h,a),h.appendChild(a),h.setAttribute(b,g);a.hasAttribute(f)||a.hasAttribute(e);)d(a);return h}function e(a){var b,c=a.toLowerCase();return D.some(function(a){return a.hasOwnProperty(c)?(b=a[c],!0):void 0}),b}function h(a,c,d){var e=a.tagName;switch(!0){case m.IfElement(e):var f=new w(x,c,a,y["if"],H+"if",l(a).attr("is"));return d||f.$mount(a),F.push(f),b(c,f),f;case m.RepeatElement(e):var f=new w(x,c,a,y.repeat,H+"repeat",l(a).attr("items"));return d||f.$mount(a),F.push(f),b(c,f),f}}function i(a,c,d){function g(a){var b,a=a.replace(/^[^:]+:/,function(a){return b=a.replace(/:$/,"").trim(),""}).trim();return{name:b,expr:a,vars:s.extract(a)}}function h(a){var b=g(a);A[b.name]=b,(b.vars||[]).forEach(function(a){!B[a]&&(B[a]=[]),!~B[a].indexOf(b.name)&&B[a].push(b.name)})}var i=l(a),j=H+"component",k=i.attr(j)||a.tagName,m=e(k);if(m&&(i.removeAttr(j),a!==c.$el)){var n=l(a).attr(H+"ref"),p=H+"data",r=H+"methods",t=i.attr(p),u=i.attr(r);i.removeAttr(p).removeAttr(r);var v,w,x,y=C(t),z=s.execLiteral,A={},B={},v=z(t,c,d),w=z(u,c,d);x=new m({el:a,data:v,methods:w,$parent:c,$childrens:f(a.childNodes)});var D=y?s.strip(t):"",E=s.sep;return D&&(D.match(E)?D.replace(new RegExp(E+"\\s*$"),"").split(E).forEach(h):h(D)),y&&c.$data.$watch(function(a){var b;o.objEach(B,function(e,f){0===a.indexOf(e)&&(!b&&(b={}),f.forEach(function(a){b[a]=q(c,d,A[a].expr)}))}),b&&x.$set(b)}),n&&(c.$refs[n]=x),G.push(x),b(d,x),x.$update=function(){y&&x.$set(z(t,c,d))},x}}function j(a,c){var d=(a.nodeValue,f(a.attributes)),e={attrs:{},dires:{}};d.forEach(function(b){var c=b.name,d=b.value;if(!~I.indexOf(c)){if(C(c))e.attrs[c]=d;else if(0===c.indexOf(H))e.dires[c]=d;else{if(!C(d.trim()))return;e.attrs[c]=d}a.removeAttribute(c)}}),o.objEach(e.attrs,function(d,e){var f=new u(x,c,a,d,e);F.push(f),b(c,f)}),E.forEach(function(d){o.objEach(d,function(d,f){var g=H+d,h=e.dires[g];if(e.dires.hasOwnProperty(g)){var i,j=";";f.multi&&h.match(j)?s.strip(h).split(j).forEach(function(d){d.trim()&&(i=new t(x,c,a,f,g,"{"+d+"}"),F.push(i),b(c,i))}):(i=new t(x,c,a,f,g,h),F.push(i),b(c,i))}})})}function k(a,c,d){var e,f=a.nodeValue,g=s.veil(f),h=s.exprRegexp,i=g.split(h),j=g.match(h);return j&&j.length&&(e=new v(c,d,a,f,i,j),F.push(e),b(d,e)),e}var x=this,A=a.el,D=[B,a.components||{}],E=z.concat([a.directives||{}]),F=[],G=[],H=p.namespace,I=[H+"component",H+"data",H+"methods",H+"ref"],J=a.$childrens;if(x.$parent=a.$parent||null,A&&a.template)A.innerHTML=a.template;else if(a.template)A=document.createElement("div"),A.innerHTML=a.template;else if("string"==o.type(A))A=document.querySelector(A);else if(!m.Element(A))throw new Error("Unmatch el option");if(1==A.children.length&&A.firstElementChild.tagName.toLowerCase()==H+"template"){var K=A.firstElementChild,L=f(K.childNodes),M=f(K.attributes);A.removeChild(K),l(L).appendTo(A),M.forEach(function(a){var b;if("class"==a.name)b=a.value+(A.className?" "+A.className:"");else{if(A.hasAttribute(a.name))return;b=a.value}A.setAttribute(a.name,b)})}var N=f(A.querySelectorAll("content"));if(N){var O;J&&J.length&&(O=document.createDocumentFragment(),f(J).forEach(function(a){O.appendChild(a)})),N.forEach(function(a){if(!J||!J.length)return l(a).remove();var b,c,d=l(a),e=d.attr("select");e&&(b=O.querySelector(e))&&~(c=J.indexOf(b))?(d.replace(b),J.splice(c,1)):e||(d.replace(O),J=null)})}x.$el=A,x.$component=e,x.$refs={};var P={};o.objEach(a.methods,function(a,b){return"function"!==o.type(b)?console.warn(a+" is not a function."):void(x[a]=P[a]=b.bind(x))}),x.$methods=P;var Q,R={};if(Object.defineProperty(x,"$data",{enumerable:!0,get:function(){return Q||R},set:function(a){Q.$set(a)}}),x.$set=function(){Q.$set.apply(Q,arguments)},x.$get=function(){return Q.$get.apply(Q,arguments)},x.$watch=function(a){return Q.$watch.apply(Q,arguments)},x.$unwatch=function(a){return Q.$unwatch.apply(Q,arguments)},a.$data)Q=a.$data,a.created&&a.created();else{a.created&&a.created(),o.merge(R,g(a,"data"));var S={props:R,computed:a.computed,computedContext:x};Q=new n(S)}x.$compile=function(a,b){var d;return o.walk(a,function(e){var f=e===a,g=c(e,b,f);return f&&(d=g.inst),g.into}),d};var T=a.destroy;x.$destroy=function(){x.$destroyed||(T&&T.call(x),[G,F].forEach(function(a){a.forEach(function(a){a.$destroy()})}),Q.$destroy(),x.$el=null,x.$get=null,x.$set=null,x.$refs=null,x.$watch=null,x.$unwatch=null,x.$compile=null,x.$component=null,E=null,D=null,F=null,G=null,x.$destroyed=!0)},x.$compiler=x.$compile(A),a.ready&&a.ready.call(x)}function f(a){return a?[].slice.call(a):a}function g(a,b){var c=a[b];return"function"==o.type(c)?c.call(a):c}function h(a){return o.extend.apply(o,a)}function i(a,b){var c=a.prototype;return a.prototype=Object.create(b.prototype),c}function j(a){var b={};return h([b].concat(a)),["data","methods","directives","components"].forEach(function(c){b[c]=h([{}].concat(a.map(function(a){return g(a,c)})))}),b}function k(a){var b=[];a.forEach(function(a){a&&(b.push(a),a.mixins&&(b=b.concat(a.mixins)))});var c=j(b),d=c.methods=c.methods||{};return a.forEach(function(a){var b=a&&a.methods&&a.methods.mixins;b&&b.forEach(function(a){o.objEach(a,function(a,b){"mixins"!==a&&(d[a]=b)})})}),delete c.methods.mixins,c}var l=c(2),m=c(3),n=c(4),o=c(5),p=c(6),q=c(7),r=c(8),s=c(9),t=r.Directive,u=r.Attribute,v=r.Text,w=r.Element,x=c(10)(d),y=c(11)(d),z=[x,{}],A=z[1],B={},C=s.isExpr;d.extend=function(a){function b(b){var c=k([a,b]);return e.call(this,c)}return i(b,d),b},d.component=function(a,b){var c=d.extend(b);return B[a.toLowerCase()]=c,c},d.directive=function(a,b){A[a]=b},d.namespace=function(a){p.namespace=a},i(d,r),a.exports=d},function(a,b,c){"use strict";function d(a){if("string"==j.type(a))return e(j.copyArray(document.querySelectorAll(a)));if("array"==j.type(a))return e(a);if(a instanceof e)return a;if(k.DOM(a))return e(new f(a));throw new Error("Unexpect selector !")}function e(a){if(a instanceof e)return a;var b=new f;return a.forEach(function(a){b.push(a)}),b}function f(){this.push=function(){Array.prototype.push.apply(this,arguments)},this.forEach=function(){Array.prototype.forEach.apply(this,arguments)},this.push.apply(this,arguments)}function g(a){return a&&a.parentNode}function h(){return document.createDocumentFragment()}function i(a,b){return a.appendChild(b)}var j=c(5),k=c(3);f.prototype=Object.create(e.prototype);var l=e.prototype;l.find=function(a){var b=[];return this.forEach(function(c){b=b.concat(j.copyArray(c.querySelectorAll(a)))}),e(b)},l.attr=function(a,b){var c=arguments.length,d=this[0];if(c>1)d.setAttribute(a,b);else if(1==c)return(d.getAttribute(a)||"").toString();return this},l.removeAttr=function(a){return this.forEach(function(b){b.removeAttribute(a)}),this},l.addClass=function(a){return this.forEach(function(b){var c=b.className.split(" ");~c.indexOf(a)||c.push(a),b.className=c.join(" ")}),this},l.removeClass=function(a){return this.forEach(function(b){var c=b.className.split(" "),d=c.indexOf(a);~d&&c.splice(d,1),b.className=c.join(" ")}),this},l.each=function(a){return this.forEach(a),this},l.on=function(a,b,c){return this.forEach(function(d){d.addEventListener(a,b,c)}),this},l.off=function(a,b){return this.forEach(function(c){c.removeEventListener(a,b)}),this},l.html=function(a){var b=arguments.length;if(b>=1)this.forEach(function(b){b.innerHTML=a});else if(this.length)return this[0].innerHTML;return this},l.parent=function(){return this.length?e([g(this[0])]):null},l.remove=function(){return this.forEach(function(a){var b=g(a);b&&b.removeChild(a)}),this},l.insertBefore=function(a){var b;return this.length?(1==this.length?b=this[0]:(b=h(),this.forEach(function(a){i(b,a)})),g(a).insertBefore(b,a),this):this},l.insertAfter=function(a){var b;return this.length?(1==this.length?b=this[0]:(b=h(),this.forEach(function(a){i(b,a)})),g(a).insertBefore(b,a.nextSibling),this):this},l.get=function(a){return this[a]},l.append=function(a){return this.length&&i(this[0],a),this},l.appendTo=function(a){if(1==this.length)i(a,this[0]);else if(this.length>1){var b=h();this.forEach(function(a){i(b,a)}),i(a,b)}},l.replace=function(a){var b=this[0];return g(b).replaceChild(a,b),this},a.exports=d},function(a,b,c){"use strict";var d=c(6);a.exports={Element:function(a){return a instanceof HTMLElement||a instanceof DocumentFragment},DOM:function(a){return this.Element(a)||a instanceof Comment},IfElement:function(a){return a==(d.namespace+"if").toUpperCase()},RepeatElement:function(a){return a==(d.namespace+"repeat").toUpperCase()}}},function(a,b,c){!function(b,c){a.exports=c()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){"use strict";a.exports=c(1)},function(a,b,c){"use strict";function d(){return A++}function e(a){a=a||{},g.call(this,a)}function f(a){function b(b){g.call(this,a,b)}return b.prototype=Object.create(e.prototype),b}function g(a,b){function c(){return N||""}function f(a,b){R[a]=b,o.def(J,a,{enumerable:!1,value:b})}function g(){return u("Instance already has bean destroyed"),I}function n(a){var b=arguments,d=p(q(c(),a));b[0]=z+":"+d,L.emit(z,d),K.emit.apply(K,b),b=o.copyArray(b),b[0]=d,b.unshift("*"),K.emit.apply(K,b)}function A(a,b){o.patch(Y,b,[]);var c=Y[b];s(c,a)||c.push(a)}function B(a,b,c){var d;return j(a,e)&&a.__kp__===c&&a.__root__===O?(d=a,d._$emitter(K),d._$_emitter(L)):d=new e({props:b,emitter:K,_emitter:L,__kp__:c}),d.__root__||o.def(d,"__root__",{enumerable:!1,value:O}),d}function C(a,b,d){var f=r(b),g=d?d:q(c(),a);switch(f==w&&m(b,function(b,c,d,e){var f=o.copyArray(b),h=d.apply(b,e);return _[a]=C(a,b,g),"splice"==c?n(a,b,f,c,e):n(a,b,f,c),h}),f){case x:var h={},i=b;return j(b,e)&&(i=b.$props()),o.objEach(i,function(a,b){h[a]=C(a,b,q(g,a))}),B(b,h,g);case w:return b.forEach(function(a,c){b[c]=C(c,a,q(g,c))}),b;default:return b}}function D(a,b){var d=p(a).split("."),f=d[0];if(s(X,f))return J[f]=b,!1;if(!s($,f))return u('Property "'+f+'" has not been observed'),!1;var g=l.get(_,a),h=j(b,Object),i=d.join("."),k=d.pop(),m=d.join("."),r=l.get(_,m),v=j(r,e);v?t(r,k)?r.$set(k,b):r.$add(k,b):(l.set(_,a,h?C(k,b,q(c(),i)):b),o.diff(b,g)&&n(a,b,g))}function E(a,b){return I?g():void D(a,b)}function F(a){return I?g():void(a&&r(a)==x&&o.objEach(a,function(a,b){E(a,b)}))}function G(a,b,c){if(a.match(/[\.\[\]]/))throw new Error('Propname shoudn\'t contains "." or "[" or "]"');return s($,a)?arguments.length>1?!0:!1:(_[a]=C(a,o.copyValue(b)),$.push(a),o.def(J,a,{enumerable:!0,get:function(){return _[a]},set:function(b){E(a,b)}}),void(!c&&n(a,b)))}function H(a,b,c,d,e){if(!s(X,a)){X.push(a),W[a]={deps:b,get:c,set:d},(b||[]).forEach(function(b){for(;b;)A(a,b),b=l.digest(b)}),o.patch(Z,a,{});var f=Z[a];f.cur=c?c.call(M,J):void 0,o.def(J,a,{enumerable:void 0===e?!0:!!e,get:function(){return f.cur},set:function(){d&&d.apply(M,arguments)}}),n(a,f.cur)}}var I,J=this,K=a.emitter||new k(J),L=a._emitter||new k(J),M=t(a,"computedContext")?a.computedContext:J,N=a.__kp__,O=d(),P=!!a.emitter,Q=!!a._emitter,R={};f("__muxid__",O),f("__kp__",N);var S=a.props,T={},U=r(S);U==y?T=S():U==x&&(T=S),S=null;var V=a.computed,W={},X=[],Y={},Z={},$=[],_={};o.objEach(T,function(a,b){G(a,b,!0)}),T=null,o.objEach(V,function(a,b){H(a,b.deps,b.get,b.set,b["enum"])}),V=null,L.on(z,function(a){var b=[],c=[];if(Object.keys(Y).length){for(;a;)Y[a]&&(c=c.concat(Y[a])),a=l.digest(a);c.length&&(c.reduce(function(a,b){return s(a,b)||a.push(b),a},b),b.forEach(function(a){o.patch(Z,a,{});var b=Z[a],c=b.pre=b.cur,d=b.cur=(W[a].get||i).call(M,J);o.diff(d,c)&&n(a,d,c)}))}},O),f("$add",function(){var a,b,c=arguments,d=c[0];switch(r(d)){case v:a=d,c.length>1?(b=c[1],G(a,b)&&E(a,b)):G(a);break;case w:d.forEach(function(a){G(a)});break;case x:var e;o.objEach(d,function(a,b){G(a,b)&&(!e&&(e={}),e[a]=b)}),e&&F(e);break;default:u("Unexpect params")}return this}),f("$computed",function(a,b,c,d,e){return r(a)==v?H.apply(null,arguments):r(a)==x?o.objEach(arguments[0],function(a,b){H(a,b.deps,b.get,b.set,b["enum"])}):u('$computed params show be "(String, Array, Function, Function)" or "(Object)"'),this}),f("$set",function(){var a=arguments,b=a.length;return b>=2||1==b&&r(a[0])==v?E(a[0],a[1]):1==b&&r(a[0])==x?F(a[0]):u("Unexpect $set params"),this}),f("$get",function(a){if(s($,a))return _[a];if(s(X,a))return(W[a].get||i).call(M,J);var b=p(a),c=b.split(".");return s($,c[0])?l.get(_,b):void 0}),f("$watch",function(){var a,b,d=arguments,e=d.length,f=d[0];if(e>=2)a="change:"+p(q(c(),f)),b=d[1];else{if(1!=e||r(f)!=y)return u("Unexpect $watch params"),i;a="*",b=f}K.on(a,b,O);var g=this;return function(){g.$unwatch.apply(g,d)}}),f("$unwatch",function(){var a,b,d=arguments,e=d.length,f=d[0];switch(!0){case e>=2:a=[d[1]];case 1==e&&r(f)==v:!a&&(a=[]),b=z+":"+p(q(c(),f)),a.unshift(b);break;case 1==e&&r(f)==y:a=["*",f];break;case 0==e:a=[];break;default:u("Unexpect param type of "+f)}return a&&(a.push(O),K.off.apply(K,a)),this}),f("$props",function(){return o.copyObject(_)}),f("$emitter",function(a,b){return 0==arguments.length?K:(K=a,h(this.$props(),a,b),this)}),f("_$emitter",function(a){K=a}),f("_$_emitter",function(a){j(a,k)&&(L=a)}),f("$destroy",function(){o.objEach(R,function(a,b){r(b)==y&&"$destroyed"!=a&&(R[a]=g)}),P?K.off(O):K.off(),Q?L.off(O):L.off(),K=null,L=null,W=null,X=null,Y=null,Z=null,$=null,_=null,I=!0}),f("$destroyed",function(){return I}),F(b)}function h(a,b,c){if(r(a)==x){var d=a;j(a,e)&&(a._$emitter(b,c),d=a.$props()),o.objEach(d,function(a,d){h(d,b,c)})}else r(a)==w&&a.forEach(function(a){h(a,b,c)})}function i(){}function j(a,b){return a instanceof b}var k=c(2),l=c(3),m=c(4),n=c(5),o=c(6),p=l.normalize,q=l.join,r=o.type,s=o.indexOf,t=o.hasOwn,u=n.warn,v="string",w="array",x="object",y="function",z="change",A=0;e.extend=function(a){return f(a||{})},e.config=function(a){a.warn===!1?n.disable():n.enable()},e.emitter=function(a){return new k(a)},e.keyPath=l,e.utils=o,a.exports=e},function(a,b,c){"use strict";function d(a){this._obs={},this._context=a}var e=c(6),f=e.patch,g=e.type,h="__default_scope__",i=d.prototype;i.on=function(a,b,c){c=c||h,f(this._obs,a,[]),this._obs[a].push({cb:b,scope:c})},i.off=function(){var a,b,c,d=arguments,e=d.length;if(e>=3)a=[d[0]],b=d[1],c=d[2];else if(2==e&&"function"==g(d[0]))a=Object.keys(this._obs),b=d[0],c=d[1];else if(2==e)a=[d[0]],c=d[1];else{if(1!=e)return this._obs=[],this;a=Object.keys(this._obs),c=d[0]}c=c||h;var f=this;return a.forEach(function(a){var d=f._obs[a];if(d){var e=[];b?d.forEach(function(a){(a.cb!==b||a.scope!==c)&&e.push(a)}):d.forEach(function(a){a.scope!==c&&e.push(a)}),f._obs[a]=e}}),this},i.emit=function(a){var b=this._obs[a];if(b){var c=[].slice.call(arguments);c.shift();var d=this;b.forEach(function(a){a.cb&&a.cb.apply(d._context||null,c)})}},a.exports=d},function(a,b,c){"use strict";function d(a){return new String(a).replace(/\[([^\[\]]+)\]/g,function(a,b){return"."+b.replace(/^["']|["']$/g,"")})}function e(a,b,c,e){var f=d(b).split("."),g=f.pop(),h=a;return f.forEach(function(a){h=h[a]}),e?e(h,g,c):h[g]=c,a}function f(){return void 0}function g(a){return a===f()||null===a}function h(a,b){var c=d(b).split("."),e=a;return c.forEach(function(a){return g(e)?!(e=f()):void(e=e[a])}),e}function i(a,b){var c=!!a;return c||(a=""),/^\[.*\]$/.exec(b)?a+b:"number"==typeof b?a+"["+b+"]":c?a+"."+b:b}function j(a){var b=/(\.[^\.]+|\[([^\[\]])+\])$/;return b.exec(a)?a.replace(b,""):""}a.exports={normalize:d,set:e,get:h,join:i,digest:j}},function(a,b,c){"use strict";var d=c(6),e=["splice","push","pop","shift","unshift","reverse","sort","$concat"],f=Array.prototype.push,g=Array.prototype.slice,h={$concat:function(){var a=g.call(arguments),b=this;return a.forEach(function(a){"array"==d.type(a)?a.forEach(function(a){f.call(b,a)}):f.call(b,a)}),b}},i="__hook__";a.exports=function(a,b){e.forEach(function(c){if(a[c]&&a[c][i])return void a[c][i](b);var e=a[c]||h[c];d.def(a,c,{enumerable:!1,value:function(){return b(a,c,e,arguments)}}),d.def(a[c],i,{enumerable:!1,value:function(a){b=a}})})}},function(a,b,c){"use strict";var d=!0;a.exports={enable:function(){d=!0},disable:function(){d=!1},warn:function(a){return d?console.warn?console.warn(a):void console.log(a):void 0}}},function(a,b,c){"use strict";function d(a,b){return a&&a.hasOwnProperty(b)}a.exports={type:function(a){return/\[object (\w+)\]/.exec(Object.prototype.toString.call(a))[1].toLowerCase()},objEach:function(a,b){if(a)for(var c in a)if(d(a,c)&&b(c,a[c])===!1)break},patch:function(a,b,c){!a[b]&&(a[b]=c)},diff:function(a,b,c){var d=this;if(c=void 0==c?4:c,0>=c)return a!==b;if("array"==this.type(a)&&"array"==this.type(b))return a.length!==b.length?!0:a.some(function(a,e){return d.diff(a,b[e],c-1)});if("object"==this.type(a)&&"object"==this.type(b)){var e=Object.keys(a),f=Object.keys(b);if(e.length!=f.length)return!0;var d=this;return e.some(function(e){return!~f.indexOf(e)||d.diff(a[e],b[e],c-1)})}return a!==b},copyArray:function(a){for(var b=a.length,c=new Array(b);b--;)c[b]=a[b];return c},copyObject:function(a){var b={};return this.objEach(a,function(a,c){b[a]=c}),b},copyValue:function(a){var b=this.type(a);switch(b){case"object":return this.copyObject(a);case"array":return this.copyArray(a);default:return a}},def:function(){return Object.defineProperty.apply(Object,arguments)},indexOf:function(a,b){return~a.indexOf(b)},merge:function(a,b){return b?(this.objEach(b,function(b,c){a[b]=c}),a):a},hasOwn:d}}])})},function(a,b,c){"use strict";function d(a){return Object.keys(a)}var e=c(4),f=e.utils,g=e.keyPath.normalize,h=e.keyPath.digest,i={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},j=new RegExp(d(i).join("|"),"g");a.exports={type:f.type,diff:f.diff,merge:f.merge,objEach:f.objEach,copyArray:f.copyArray,copyObject:f.copyObject,extend:function(a){if("object"!=this.type(a))return a;for(var b,c,d=1,e=arguments.length;e>d;d++){b=arguments[d];for(c in b)a[c]=b[c]}return a},valueDiff:function(a,b){return a!==b||a instanceof Object},walk:function(a,b){var c=b(a)!==!1,d=this;if(c){var e=[].slice.call(a.childNodes);e.forEach(function(a){d.walk(a,b)})}},domRange:function(a,b,c){for(var d=[],e=a.childNodes,f=!1,g=0;g]+?>/),d=b.match(/<\/[^<]+?>$/);return[c?c[0]:"",d?d[0]:""]},relative:function(a,b){if(a=g(a),b=g(b),a==b)return!0;var c=0===a.indexOf(b),d=a.replace(b,"").match(/^[\.\[]/);return c&&d},escape:function(a){return"string"==!this.type(a)?a:a.replace(j,function(a){return i[a]})},normalize:g,digest:h}},function(a,b,c){"use strict";var d="z-";a.exports={set namespace(a){d=a+"-"},get namespace(){return d}}},function(module,exports,__webpack_require__){function _execute($vm,$scope){var $parent=$scope&&$scope.$parent?util.extend({},$scope.$parent.methods,$scope.$parent.data):{};$scope=$scope||{},$scope=util.extend({},$vm.$methods,$vm.$data,$scope.methods,$scope.data);try{return util.immutable(eval("with($scope){(%s)}".replace("%s",arguments[2])))}catch(e){switch(arguments[2]=/^\{/.test(arguments[2])?". "+arguments[2]:". {"+arguments[2]+"}",e.name){case"ReferenceError":console.warn(e.message+arguments[2]);break;default:console.error(arguments[3]?"'"+arguments[3]+"': ":"",e.message+arguments[2],arguments[4]||"")}return""}}var util=__webpack_require__(5);module.exports=_execute},function(a,b,c){"use strict";function d(){}function e(a,b,c){function e(a){f.some(function(b,c){return q(a,b)?!0:void 0})&&c.apply(null,arguments)}var f=[];return b&&b.length?(b.forEach(function(a){if(!~t.indexOf(a))for(;a;)~f.indexOf(a)||f.push(a),a=n.digest(a)}),f.length?(a.$watch(e),function(){a.$unwatch(e)}):d):d}function f(a){this.$el=a}function g(a,b){return a.appendChild(b)}function h(a){return document.createComment(a)}function i(a,b,c){return a.insertBefore(b,c)}function j(a){return a&&a.parentNode}function k(a){return a&&a.nextSibling}function l(a,b){return b&&b.parentNode===a}var m=c(2),n=c(5),o=c(9),p=c(7),q=n.relative,r=o.isExpr,s=o.extract,t=["$index","$value","$parent","$vm","$scope"],u=o.strip,v=f.prototype;f.inherit=function(a){function b(){a.apply(this,arguments)}return b.prototype=Object.create(f.prototype),b},v.$bundle=function(){return this.$el},v.$floor=function(){return this.$el},v.$ceil=function(){return this.$el},v.$mount=function(a){return m(a).replace(this.$bundle()),this},v.$remove=function(){var a=this.$bundle();return j(a)&&m(a).remove(),this},v.$appendTo=function(a){return g(a,this.$bundle()),this},v.$insertBefore=function(a){return i(j(a),this.$bundle(),a),this},v.$insertAfter=function(a){return i(j(a),this.$bundle(),k(a)),this},v.$destroy=function(){return this.$el=null,this},v.$update=d;var w=0,x=(f.Directive=f.inherit(function(a,b,c,d,f,g){function h(c){return p(a,b,c,f)}function i(a){var b=h(g);if(n.diff(b,o)){var c=o;o=b,x&&x.call(j,b,c,a)}}var j=this,k=[],l=!!r(g);if(l&&(g=u(g)),d.multi){var m;g=g.replace(/^[^:]+:/,function(a){return m=a.replace(/:$/,"").trim(),""}).trim(),k.push(m)}j.$el=c,j.$vm=a,j.$id=w++,j.$scope=b;var o,q,t=d.bind,v=d.unbind,x=d.update;n.objEach(d,function(a,b){j[a]=b}),o=l?h(g):g,k.push(o),k.push(g),d.watch!==!1&&l&&(q=e(a,s(g),i)),j.$destroy=function(){v&&v.call(j),q&&q(),j.$el=null,j.$vm=null,j.$scope=null},j.$update=i,t&&t.apply(j,k,g),x&&x.call(j,o)}),0);f.Element=f.inherit(function(a,b,c,d,f,k){function m(a,b,c,d,e,f){var g=o(k);if(A&&A.call(v,g,q,a))return B&&B.call(v,g,h,a);if(n.diff(g,q)){var h=q;q=g,z&&z.call(v,g,h,a,d,e,f)}}function o(c){return p(a,b,c,f)}var q,t,v=this,w=d.bind,y=d.unbind,z=d.update,A=d.delta,B=d.deltaUpdate,C=!!r(k);C&&(k=u(k)),v.$id=x++,v.$vm=a,v.$el=c,v.$scope=b;var D=n.tagHTML(c);v.$before=h(D[0]),v.$after=h(D[1]),v.$container=document.createDocumentFragment(),g(v.$container,v.$before),g(v.$container,v.$after),n.objEach(d,function(a,b){v[a]=b}),v.$bundle=function(){var a=this.$ceil(),b=this.$floor(),c=this.$container,d=this;return l(c,a)||(n.domRange(j(a),a,b).forEach(function(a){g(d.$container,a)}),i(c,a,c.firstChild),g(c,b)),c},v.$floor=function(){return this.$after},v.$ceil=function(){return this.$before},v.$destroy=function(){y&&y.call(v),t&&t(),v.$el=null,v.$vm=null,v.$scope=null},v.$update=m,q=C?o(k):k,d.watch!==!1&&C&&(t=e(a,s(k),m)),w&&w.call(v,q,k),z&&z.call(v,q)}),f.Text=f.inherit(function(a,b,c,d,f,l){function m(c){return p(a,b,c,null)}function q(){var a=[];f.forEach(function(b,c){a.push(b),cc?c:a,j.length>2){var d=[].slice.call(j,2).map(function(b,c){return k(b,a+c)});this.$vms.splice.apply(this.$vms,[a,b].concat(d)),e(d.map(function(a){return a.$compiler.$bundle()})).insertAfter(0==a?p:this.$vms[a-1].$compiler.$bundle());var f=a+d.length;this.$vms.forEach(function(a,b){b>=f&&m(a,b)})}else this.$vms.splice.apply(this.$vms,j).forEach(function(a,b){l(a)}),this.$vms.forEach(function(b,c){c>=a&&m(b,c)})},push:function(){var b=a.length-1,c=k(a[b],b);this.$vms.push(c),c.$compiler.$insertBefore(o)},pop:function(){var a=this.$vms.pop();l(a)},shift:function(){var a=this.$vms.shift();l(a),this.$vms.forEach(function(a,b){m(a,b)})},unshift:function(){var b=k(a[0],0);this.$vms.unshift(b),b.$compiler.$insertAfter(p),this.$vms.forEach(function(a,b){0!=b&&m(a,b)})},$concat:function(){var b=this.$vms.length;e(a.slice(b).map(function(a,c){var d=k(a,c+b);return n.$vms.push(d),d.$compiler.$bundle()})).insertBefore(o)}},r=q[i];if(this._noArrayFilter&&r)return r.call(this),void(this.last=g.copyArray(a));var s=new Array(a.length),t=this.last?g.copyArray(this.last):t,u=this.$vms?g.copyArray(this.$vms):u,v=[];a.forEach(function(a,b){var c;if(t){var e=-1;if(t.some(function(b,c){return g.diff(b,a)?void 0:(e=c,!0)}),~e){c=u[e],t.splice(e,1),u.splice(e,1),c.$index=b,c.$value=a;var f=c.$scope.data=d(a);f.$index=b,f.$value=a,v.push(c)}else c=k(a,b)}else c=k(a,b);s[b]=c}),this.$vms=s,this.last=g.copyArray(a);for(var w=s.length,x=0;w>x;){var y=s[x++];y.$compiler.$insertBefore(o)}v.forEach(function(a){a.$scope.$update()}),v=null,u&&u.forEach(l)}}}}}},function(a,b,c){"use strict";function d(a,b){this.data=a,this.bindings=[],this.children=[],this.$parent=b||{}}d.prototype.$update=function(){this.bindings.forEach(function(a){a.$update()}),this.children.forEach(function(a){a.$update()})},a.exports=d}])}); -------------------------------------------------------------------------------- /server/asserts/console.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Console.log overide 3 | **/ 4 | !function (exports) { 5 | 'use strict'; 6 | 7 | var slice = Array.prototype.slice; 8 | 9 | function NOOP () {} 10 | function _log (type, args) { 11 | console['_'+type].apply(console, args) 12 | } 13 | 14 | function _logProxy (type, args, noLocal) { 15 | !noLocal && _log(type, args); 16 | 17 | args = slice.call(args); 18 | args.forEach(function (item, index) { 19 | try { 20 | args[index] = JSON.parse(JSON.stringify(item)); 21 | } catch (e) { 22 | args[index] = jsonify(item); 23 | } 24 | }); 25 | 26 | _jsinspector_consoles.push({ 27 | type: type, 28 | args: JSON.stringify(args) 29 | }); 30 | } 31 | function _logHook (type) { 32 | return function () { 33 | _logProxy(type, arguments); 34 | } 35 | } 36 | 37 | if (!exports._jsinspector_console_inited) { 38 | 39 | // mark as inited 40 | exports._jsinspector_console_inited = true; 41 | exports._jsinspector_consoles = []; 42 | 43 | var proxyMethods = ['log', 'clear', 'error', 'info', 'warn', 'time', 'timeEnd'] 44 | 45 | /** 46 | * Saving native methods 47 | **/ 48 | proxyMethods.forEach(function (m) { 49 | console['_' + m] = console[m] || NOOP 50 | }) 51 | 52 | /** 53 | * console methods override 54 | **/ 55 | proxyMethods.forEach(function (m) { 56 | console[m] = _logHook(m) 57 | }) 58 | 59 | var _times = {}; 60 | console.time = function (name) { 61 | _times[name] = Date.now(); 62 | } 63 | console.timeEnd = function (name) { 64 | if (_times[name] === undefined) return; 65 | var end = Date.now() - _times[name]; 66 | _logProxy('log', ['%c' + name + ': ' + end + 'ms', 'color: blue']) 67 | } 68 | /** 69 | * Catch global error 70 | */ 71 | var _errorProxy = function(errorEvent) { 72 | var args = [ 73 | errorEvent.message + ' %c' + errorEvent.filename + ':' + errorEvent.lineno, 74 | 'color:gray;' 75 | ]; 76 | if (errorEvent.error && errorEvent.error.stack) { 77 | args.push(errorEvent.error.stack); 78 | } 79 | _logProxy('error', args, true); 80 | } 81 | window.addEventListener('error', _errorProxy) 82 | 83 | // init clear console 84 | console.clear(); 85 | } 86 | 87 | }(this); 88 | -------------------------------------------------------------------------------- /server/asserts/inject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry of client script 3 | * @return {[type]} [description] 4 | */ 5 | ;(function () { 6 | 'use strict'; 7 | var slice = Array.prototype.slice 8 | var clientId = '<%= clientId %>' 9 | var _requestHandlers = {} 10 | var dataPacketQueue = [] 11 | var localBaseDocumentData 12 | var serverTime = <%= serverTime %> 13 | var clientTime = +new Date 14 | var differ, socket, anchor 15 | 16 | function fillURL(url) { 17 | if (/\w+:/.test(url)) return url 18 | if (url === null || url === undefined || url === '') return '' 19 | if (!anchor) anchor = document.createElement('a') 20 | anchor.href = url 21 | return (anchor.origin || '') + (anchor.pathname || '') + anchor.search 22 | } 23 | /** 24 | * Get document html 25 | **/ 26 | var lastOuterHTML 27 | function getDocument () { 28 | 29 | var doc = document.documentElement 30 | 31 | if (lastOuterHTML === doc.outerHTML) return null 32 | 33 | var scripts = slice.call(document.scripts) 34 | var links = slice.call(doc.querySelectorAll('link[rel="stylesheet"],a,link[rel="icon"]')) 35 | var styles = slice.call(doc.querySelectorAll('style')) 36 | var images = slice.call(doc.querySelectorAll('img')) 37 | var inputs = slice.call(doc.querySelectorAll('input,textarea')) 38 | var selects = slice.call(doc.querySelectorAll('select')) 39 | 40 | 41 | // avoid regexp match uncorrectly 42 | scripts.forEach(function (item) { 43 | if (!item.__jsinspecotr_fixed_url) { 44 | item.__jsinspecotr_fixed_url = 1 45 | if (item.getAttribute('src')) { 46 | item.setAttribute('src', fillURL(item.src)) 47 | } 48 | item.setAttribute('jsi-script', '') 49 | } 50 | }); 51 | links.forEach(function (item) { 52 | if (item.__jsinspecotr_fixed_url) return 53 | item.__jsinspecotr_fixed_url = true 54 | item.setAttribute('href', fillURL(item.href || '')) 55 | }); 56 | images.forEach(function (item) { 57 | if (item.__jsinspecotr_fixed_url) return 58 | item.__jsinspecotr_fixed_url = true 59 | item.setAttribute('src', fillURL(item.src)) 60 | }); 61 | styles.forEach(function (item) { 62 | if (item.innerHTML) { 63 | item.innerHTML = item.innerHTML.replace( 64 | /url\(([\'\"])*[\/]*(?!http:\/\/|data:|https:\/\/)/g, 65 | 'url($1' + window.location.protocol + '//' + window.location.host + '/' 66 | ); 67 | } 68 | }); 69 | inputs.forEach(function (item) { 70 | if (item.getAttribute('value') !== item.value) { 71 | item.setAttribute('value', item.value); 72 | } 73 | }); 74 | selects.forEach(function (item) { 75 | var options = slice.call(item.querySelectorAll('option')); 76 | options.forEach(function (option) { 77 | if (item.value == option.value) { 78 | option.setAttribute('selected', 'true'); 79 | return true; 80 | } 81 | }); 82 | }); 83 | // get document doctype 84 | var node = document.doctype 85 | var doctype = ''; 91 | 92 | var content = lastOuterHTML = doc.outerHTML 93 | content = content 94 | // .replace(/]*?href="([^\"]+)"[^\>]*?\/?>/g, function (m, u) { 95 | // if (/\bjsi-link/.test(m)) { 96 | // var l = m.replace(/\bhref="[^\"]+"/, 'href="' + fillURL(u) + '"') 97 | // return l 98 | // } else return m 99 | // }) 100 | .replace(/]*?>[\s\S]*?<\/script>/g, function (m) { 101 | var l = m.match(/^]*?>/m)[0] 102 | var scriptTypeReg = /\stype="(application|text)\/javascript"/m 103 | if (!/\sjsi-script=/.test(l)) return m 104 | m = m.replace(/\sjsi-script=""/, '') 105 | if (/\stype="/.test(l) && !scriptTypeReg.test(l)) return m 106 | else if (scriptTypeReg.test(l)) return m.replace(scriptTypeReg, function (n, t) { 107 | return ' type="jsi-' + t + '\/javascript"' 108 | }) 109 | else return m.replace(/^' + selector.join('>') 287 | } 288 | document.documentElement.addEventListener('scroll', function (e) { 289 | var tar = e.target 290 | var xpath = elementPath(tar) 291 | !~scrollElements.indexOf(xpath) && scrollElements.push(xpath) 292 | var dataPacket = { 293 | meta: { 294 | scrollElements: scrollMetas() 295 | } 296 | } 297 | sendToQueue(dataPacket); 298 | }, true) 299 | } 300 | 301 | function dirtyChecking (hasDirtyCallback) { 302 | setTimeout(function() { 303 | var dataPacket = genDetalData(); 304 | if (dataPacket) { 305 | // update base document content 306 | localBaseDocumentData.html = dataPacket.html; 307 | if (dataPacket.delta) delete dataPacket.html; 308 | // sync the meta 309 | localBaseDocumentData.meta.scrollTop = dataPacket.meta.scrollTop 310 | // dirty checking callback 311 | hasDirtyCallback(dataPacket); 312 | } 313 | // polling 314 | dirtyChecking(hasDirtyCallback); 315 | }, 200); 316 | } 317 | 318 | function consoleChecking (hasConsoleCallback) { 319 | setTimeout(function() { 320 | var dataPacket; 321 | var consoles = window._jsinspector_consoles 322 | 323 | if (consoles.length > 0) { 324 | dataPacket = { 325 | meta: { 326 | consoles: consoles, 327 | } 328 | } 329 | window._jsinspector_consoles = [] 330 | hasConsoleCallback(dataPacket) 331 | } 332 | consoleChecking(hasConsoleCallback) 333 | }, 10) 334 | } 335 | /** 336 | * Generate delta data when has dirty data, else if return null 337 | **/ 338 | function genDetalData () { 339 | var checkedDoc = getDocument(), 340 | dirtyDataPacket; 341 | 342 | if (checkedDoc !== localBaseDocumentData.html) { 343 | dirtyDataPacket = { 344 | html: checkedDoc, 345 | time: new Date - (clientTime - serverTime), 346 | delta: <%= delta %> ? differ.diff(localBaseDocumentData.html, checkedDoc) : null, 347 | meta: { 348 | scrollTop: document.body.scrollTop 349 | } 350 | } 351 | // dirty checking callback 352 | return dirtyDataPacket; 353 | } 354 | 355 | return null; 356 | } 357 | 358 | function deltaPost (data, success, error) { 359 | $send({ 360 | type: 'update', 361 | data: data 362 | }, success, error); 363 | } 364 | 365 | function sendToQueue (dataPacket) { 366 | dataPacketQueue.push(dataPacket); 367 | // trigger a event to notify queue manager 368 | onDataPacketPush(); 369 | } 370 | 371 | /* =================================================================== */ 372 | var queueProcessing = false; 373 | 374 | /** 375 | * queue 376 | **/ 377 | function onDataPacketPush () { 378 | // task queue manager is working now, lock 379 | if (queueProcessing) return 380 | queueProcess() 381 | } 382 | 383 | var MAX_RETRY_TIMES = 5 384 | var retryTimes = 0 385 | 386 | function queueProcess () { 387 | if (dataPacketQueue.length === 0) { 388 | // stop when the queue is empty 389 | queueProcessing = false; 390 | return; 391 | } 392 | queueProcessing = true; 393 | 394 | var taskData = dataPacketQueue[0]; 395 | 396 | deltaPost(taskData, function () { 397 | // reset retry time 398 | retryTimes = 0; 399 | 400 | dataPacketQueue.shift(); 401 | // aync for the performance 402 | setTimeout( function() { 403 | queueProcess(); 404 | }, 50); 405 | 406 | }, function (err) { 407 | 408 | if (retryTimes >= MAX_RETRY_TIMES) { // max retry times 409 | alert(err || 'network error!'); 410 | } else { // retry 411 | retryTimes ++; 412 | setTimeout( function() { 413 | queueProcess(); 414 | }); 415 | } 416 | }); 417 | } 418 | // document base content initial upload 419 | documentInitalize(function () { 420 | // delta upload after base document upload done 421 | deltaCheckingStart(); 422 | }); 423 | } 424 | 425 | })(); 426 | -------------------------------------------------------------------------------- /server/asserts/jsondiffpatch.js: -------------------------------------------------------------------------------- 1 | !function(t){if("object"==typeof exports)module.exports=t();else if("function"==typeof define&&define.amd)define(t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.jsondiffpatch=t()}}(function(){return function t(e,r,n){function i(f,o){if(!r[f]){if(!e[f]){var a="function"==typeof require&&require;if(!o&&a)return a(f,!0);if(s)return s(f,!0);throw new Error("Cannot find module '"+f+"'")}var h=r[f]={exports:{}};e[f][0].call(h.exports,function(t){var r=e[f][1][t];return i(r?r:t)},h,h.exports,t,e,r,n)}return r[f].exports}for(var s="function"==typeof require&&require,f=0;fi.length?t:i,h=t.length>i.length?i:t,l=a.indexOf(h);if(-1!=l)return o=[[r,a.substring(0,l)],[n,h],[r,a.substring(l+h.length)]],t.length>i.length&&(o[0][0]=o[2][0]=e),o;if(1==h.length)return[[e,t],[r,i]];a=h=null;var c=this.diff_halfMatch_(t,i);if(c){var u=c[0],p=c[1],d=c[2],g=c[3],v=c[4],_=this.diff_main(u,d,s,f),y=this.diff_main(p,g,s,f);return _.concat([[n,v]],y)}return s&&t.length>100&&i.length>100?this.diff_lineMode_(t,i,f):this.diff_bisect_(t,i,f)},t.prototype.diff_lineMode_=function(t,i,s){var f=this.diff_linesToChars_(t,i);t=f[0],i=f[1];var o=f[2],a=this.diff_bisect_(t,i,s);this.diff_charsToLines_(a,o),this.diff_cleanupSemantic(a),a.push([n,""]);for(var h=0,l=0,c=0,u="",p="";h=1&&c>=1){var f=this.diff_main(u,p,!1,s);a.splice(h-l-c,l+c),h=h-l-c;for(var d=f.length-1;d>=0;d--)a.splice(h,0,f[d]);h+=f.length}c=0,l=0,u="",p=""}h++}return a.pop(),a},t.prototype.diff_bisect_=function(t,n,i){for(var s=t.length,f=n.length,o=Math.ceil((s+f)/2),a=o,h=2*o,l=new Array(h),c=new Array(h),u=0;h>u;u++)l[u]=-1,c[u]=-1;l[a+1]=0,c[a+1]=0;for(var p=s-f,d=p%2!=0,g=0,v=0,_=0,y=0,m=0;o>m&&!((new Date).getTime()>i);m++){for(var x=-m+g;m-v>=x;x+=2){var b,w=a+x;b=x==-m||x!=m&&l[w-1]b&&f>M&&t.charAt(b)==n.charAt(M);)b++,M++;if(l[w]=b,b>s)v+=2;else if(M>f)g+=2;else if(d){var C=a+p-x;if(C>=0&&h>C&&-1!=c[C]){var A=s-c[C];if(b>=A)return this.diff_bisectSplit_(t,n,b,M,i)}}}for(var R=-m+_;m-y>=R;R+=2){var A,C=a+R;A=R==-m||R!=m&&c[C-1]A&&f>P&&t.charAt(s-A-1)==n.charAt(f-P-1);)A++,P++;if(c[C]=A,A>s)y+=2;else if(P>f)_+=2;else if(!d){var w=a+p-R;if(w>=0&&h>w&&-1!=l[w]){var b=l[w],M=a+b-w;if(A=s-A,b>=A)return this.diff_bisectSplit_(t,n,b,M,i)}}}}return[[e,t],[r,n]]},t.prototype.diff_bisectSplit_=function(t,e,r,n,i){var s=t.substring(0,r),f=e.substring(0,n),o=t.substring(r),a=e.substring(n),h=this.diff_main(s,f,!1,i),l=this.diff_main(o,a,!1,i);return h.concat(l)},t.prototype.diff_linesToChars_=function(t,e){function r(t){for(var e="",r=0,s=-1,f=n.length;sr;)t.substring(s,i)==e.substring(s,i)?(r=i,s=r):n=i,i=Math.floor((n-r)/2+r);return i},t.prototype.diff_commonSuffix=function(t,e){if(!t||!e||t.charAt(t.length-1)!=e.charAt(e.length-1))return 0;for(var r=0,n=Math.min(t.length,e.length),i=n,s=0;i>r;)t.substring(t.length-i,t.length-s)==e.substring(e.length-i,e.length-s)?(r=i,s=r):n=i,i=Math.floor((n-r)/2+r);return i},t.prototype.diff_commonOverlap_=function(t,e){var r=t.length,n=e.length;if(0==r||0==n)return 0;r>n?t=t.substring(r-n):n>r&&(e=e.substring(0,r));var i=Math.min(r,n);if(t==e)return i;for(var s=0,f=1;;){var o=t.substring(i-f),a=e.indexOf(o);if(-1==a)return s;f+=a,(0==a||t.substring(i-f)==e.substring(0,f))&&(s=f,f++)}},t.prototype.diff_halfMatch_=function(t,e){function r(t,e,r){for(var n,i,s,o,a=t.substring(r,r+Math.floor(t.length/4)),h=-1,l="";-1!=(h=e.indexOf(a,h+1));){var c=f.diff_commonPrefix(t.substring(r),e.substring(h)),u=f.diff_commonSuffix(t.substring(0,r),e.substring(0,h));l.length=t.length?[n,i,s,o,l]:null}if(this.Diff_Timeout<=0)return null;var n=t.length>e.length?t:e,i=t.length>e.length?e:t;if(n.length<4||2*i.lengtha[4].length?o:a:a:o;var h,l,c,u;t.length>e.length?(h=s[0],l=s[1],c=s[2],u=s[3]):(c=s[0],u=s[1],h=s[2],l=s[3]);var p=s[4];return[h,l,c,u,p]},t.prototype.diff_cleanupSemantic=function(t){for(var i=!1,s=[],f=0,o=null,a=0,h=0,l=0,c=0,u=0;a0?s[f-1]:-1,h=0,l=0,c=0,u=0,o=null,i=!0)),a++;for(i&&this.diff_cleanupMerge(t),this.diff_cleanupSemanticLossless(t),a=1;a=p.length/2||g>=d.length/2)&&(t.splice(a,0,[n,d.substring(0,g)]),t[a-1][1]=p.substring(0,p.length-g),t[a+1][1]=d.substring(g),a++),a++}a++}},t.prototype.diff_cleanupSemanticLossless=function(t){function e(t,e){if(!t||!e)return 5;var n=0;return(t.charAt(t.length-1).match(r)||e.charAt(0).match(r))&&(n++,(t.charAt(t.length-1).match(i)||e.charAt(0).match(i))&&(n++,(t.charAt(t.length-1).match(s)||e.charAt(0).match(s))&&(n++,(t.match(f)||e.match(o))&&n++))),n}for(var r=/[^a-zA-Z0-9]/,i=/\s/,s=/[\r\n]/,f=/\n\r?\n$/,o=/^\r?\n\r?\n/,a=1;a=_&&(_=y,d=h,g=l,v=c)}t[a-1][1]!=d&&(d?t[a-1][1]=d:(t.splice(a-1,1),a--),t[a][1]=g,v?t[a+1][1]=v:(t.splice(a+1,1),a--))}a++}},t.prototype.diff_cleanupEfficiency=function(t){for(var i=!1,s=[],f=0,o="",a=0,h=!1,l=!1,c=!1,u=!1;a0?s[f-1]:-1,c=u=!1),i=!0)),a++;i&&this.diff_cleanupMerge(t)},t.prototype.diff_cleanupMerge=function(t){t.push([n,""]);for(var i,s=0,f=0,o=0,a="",h="";s1?(0!==f&&0!==o&&(i=this.diff_commonPrefix(h,a),0!==i&&(s-f-o>0&&t[s-f-o-1][0]==n?t[s-f-o-1][1]+=h.substring(0,i):(t.splice(0,0,[n,h.substring(0,i)]),s++),h=h.substring(i),a=a.substring(i)),i=this.diff_commonSuffix(h,a),0!==i&&(t[s][1]=h.substring(h.length-i)+t[s][1],h=h.substring(0,h.length-i),a=a.substring(0,a.length-i))),0===f?t.splice(s-f-o,f+o,[r,h]):0===o?t.splice(s-f-o,f+o,[e,a]):t.splice(s-f-o,f+o,[e,a],[r,h]),s=s-f-o+(f?1:0)+(o?1:0)+1):0!==s&&t[s-1][0]==n?(t[s-1][1]+=t[s][1],t.splice(s,1)):s++,o=0,f=0,a="",h=""}""===t[t.length-1][1]&&t.pop();var l=!1;for(s=1;sn));i++)o=s,a=f;return t.length!=i&&t[i][0]===e?a:a+(n-o)},t.prototype.diff_prettyHtml=function(t){for(var i=[],s=0,f=/&/g,o=//g,h=/\n/g,l=0;l");switch(c){case r:i[l]=''+p+"";break;case e:i[l]=''+p+"";break;case n:i[l]=""+p+""}c!==e&&(s+=u.length)}return i.join("")},t.prototype.diff_text1=function(t){for(var e=[],n=0;nthis.Match_MaxBits)throw new Error("Pattern too long for this browser.");var i=this.match_alphabet_(e),s=this,f=this.Match_Threshold,o=t.indexOf(e,r);-1!=o&&(f=Math.min(n(0,o),f),o=t.lastIndexOf(e,r+e.length),-1!=o&&(f=Math.min(n(0,o),f)));var a=1<h;)n(p,r+l)<=f?h=l:u=l,l=Math.floor((u-h)/2+h);u=l;var d=Math.max(1,r-l+1),g=Math.min(r+l,t.length)+e.length,v=Array(g+2);v[g+1]=(1<=d;_--){var y=i[t.charAt(_-1)];if(v[_]=0===p?(v[_+1]<<1|1)&y:(v[_+1]<<1|1)&y|((c[_+1]|c[_])<<1|1)|c[_+1],v[_]&a){var m=n(p,_-1);if(f>=m){if(f=m,o=_-1,!(o>r))break;d=Math.max(1,2*r-o)}}}if(n(p+1,r)>f)break;c=v}return o},t.prototype.match_alphabet_=function(t){for(var e={},r=0;r2&&(this.diff_cleanupSemantic(a),this.diff_cleanupEfficiency(a));else if(i&&"object"==typeof i&&"undefined"==typeof s&&"undefined"==typeof f)a=i,o=this.diff_text1(a);else if("string"==typeof i&&s&&"object"==typeof s&&"undefined"==typeof f)o=i,a=s;else{if("string"!=typeof i||"string"!=typeof s||!f||"object"!=typeof f)throw new Error("Unknown call format to patch_make.");o=i,a=f}if(0===a.length)return[];for(var h=[],l=new t.patch_obj,c=0,u=0,p=0,d=o,g=o,v=0;v=2*this.Patch_Margin&&c&&(this.patch_addContext_(l,d),h.push(l),l=new t.patch_obj,c=0,d=g,u=p)}_!==r&&(u+=y.length),_!==e&&(p+=y.length)}return c&&(this.patch_addContext_(l,d),h.push(l)),h},t.prototype.patch_deepCopy=function(e){for(var r=[],n=0;nthis.Match_MaxBits?(h=this.match_main(i,c.substring(0,this.Match_MaxBits),l),-1!=h&&(u=this.match_main(i,c.substring(c.length-this.Match_MaxBits),l+c.length-this.Match_MaxBits),(-1==u||h>=u)&&(h=-1))):h=this.match_main(i,c,l),-1==h)o[a]=!1,f-=t[a].length2-t[a].length1;else{o[a]=!0,f=h-l;var p;if(p=-1==u?i.substring(h,h+c.length):i.substring(h,u+this.Match_MaxBits),c==p)i=i.substring(0,h)+this.diff_text2(t[a].diffs)+i.substring(h+c.length);else{var d=this.diff_main(c,p,!1);if(c.length>this.Match_MaxBits&&this.diff_levenshtein(d)/c.length>this.Patch_DeleteThreshold)o[a]=!1;else{this.diff_cleanupSemanticLossless(d);for(var g,v=0,_=0;_=i;i++)r+=String.fromCharCode(i);for(var i=0;if[0][1].length){var o=e-f[0][1].length;f[0][1]=r.substring(f[0][1].length)+f[0][1],s.start1-=o,s.start2-=o,s.length1+=o,s.length2+=o}if(s=t[t.length-1],f=s.diffs,0==f.length||f[f.length-1][0]!=n)f.push([n,r]),s.length1+=e,s.length2+=e;else if(e>f[f.length-1][1].length){var o=e-f[f.length-1][1].length;f[f.length-1][1]+=r.substring(0,o),s.length1+=o,s.length2+=o}return r},t.prototype.patch_splitMax=function(i){for(var s=this.Match_MaxBits,f=0;fs){var o=i[f];i.splice(f--,1);for(var a=o.start1,h=o.start2,l="";0!==o.diffs.length;){var c=new t.patch_obj,u=!0;for(c.start1=a-l.length,c.start2=h-l.length,""!==l&&(c.length1=c.length2=l.length,c.diffs.push([n,l]));0!==o.diffs.length&&c.length12*s?(c.length1+=d.length,a+=d.length,u=!1,c.diffs.push([p,d]),o.diffs.shift()):(d=d.substring(0,s-c.length1-this.Patch_Margin),c.length1+=d.length,a+=d.length,p===n?(c.length2+=d.length,h+=d.length):u=!1,c.diffs.push([p,d]),d==o.diffs[0][1]?o.diffs.shift():o.diffs[0][1]=o.diffs[0][1].substring(d.length))}l=this.diff_text2(c.diffs),l=l.substring(l.length-this.Patch_Margin);var g=this.diff_text1(o.diffs).substring(0,this.Patch_Margin);""!==g&&(c.length1+=g.length,c.length2+=g.length,0!==c.diffs.length&&c.diffs[c.diffs.length-1][0]===n?c.diffs[c.diffs.length-1][1]+=g:c.diffs.push([n,g])),u||i.splice(++f,0,c)}}},t.prototype.patch_toText=function(t){for(var e=[],r=0;r0)){var n=r.shift();n()}},!0),function(t){r.push(t),window.postMessage("process-tick","*")}}return function(t){setTimeout(t,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.binding=function(){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(){throw new Error("process.chdir is not supported")}},{}],3:[function(t,e,r){var n=t("../pipe").Pipe,i=function(){};i.prototype.setResult=function(t){return this.result=t,this.hasResult=!0,this},i.prototype.exit=function(){return this.exiting=!0,this},i.prototype.switchTo=function(t,e){return"string"==typeof t||t instanceof n?this.nextPipe=t:(this.next=t,e&&(this.nextPipe=e)),this},i.prototype.push=function(t,e){return t.parent=this,"undefined"!=typeof e&&(t.childName=e),t.root=this.root||this,t.options=t.options||this.options,this.children?(this.children[this.children.length-1].next=t,this.children.push(t)):(this.children=[t],this.nextAfterChildren=this.next||null,this.next=t),t.next=this,this},r.Context=i},{"../pipe":16}],4:[function(t,e,r){var n=t("./context").Context,i=function(t,e){this.left=t,this.right=e,this.pipe="diff"};i.prototype=new n,r.DiffContext=i},{"./context":3}],5:[function(t,e,r){var n=t("./context").Context,i=function(t,e){this.left=t,this.delta=e,this.pipe="patch"};i.prototype=new n,r.PatchContext=i},{"./context":3}],6:[function(t,e,r){var n=t("./context").Context,i=function(t){this.delta=t,this.pipe="reverse"};i.prototype=new n,r.ReverseContext=i},{"./context":3}],7:[function(t,e){e.exports=function(t,e){var r;return"string"==typeof e&&(r=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/.exec(e))?new Date(Date.UTC(+r[1],+r[2]-1,+r[3],+r[4],+r[5],+r[6])):e}},{}],8:[function(t,e,r){var n=t("./processor").Processor,i=t("./pipe").Pipe,s=t("./contexts/diff").DiffContext,f=t("./contexts/patch").PatchContext,o=t("./contexts/reverse").ReverseContext,a=t("./filters/trivial"),h=t("./filters/nested"),l=t("./filters/arrays"),c=t("./filters/dates"),u=t("./filters/texts"),p=function(t){this.processor=new n(t),this.processor.pipe(new i("diff").append(h.collectChildrenDiffFilter,a.diffFilter,c.diffFilter,u.diffFilter,h.objectsDiffFilter,l.diffFilter).shouldHaveResult()),this.processor.pipe(new i("patch").append(h.collectChildrenPatchFilter,l.collectChildrenPatchFilter,a.patchFilter,u.patchFilter,h.patchFilter,l.patchFilter).shouldHaveResult()),this.processor.pipe(new i("reverse").append(h.collectChildrenReverseFilter,l.collectChildrenReverseFilter,a.reverseFilter,u.reverseFilter,h.reverseFilter,l.reverseFilter).shouldHaveResult())};p.prototype.options=function(){return this.processor.options.apply(this.processor,arguments)},p.prototype.diff=function(t,e){return this.processor.process(new s(t,e))},p.prototype.patch=function(t,e){return this.processor.process(new f(t,e))},p.prototype.reverse=function(t){return this.processor.process(new o(t))},p.prototype.unpatch=function(t,e){return this.patch(t,this.reverse(e))},r.DiffPatcher=p},{"./contexts/diff":4,"./contexts/patch":5,"./contexts/reverse":6,"./filters/arrays":10,"./filters/dates":11,"./filters/nested":13,"./filters/texts":14,"./filters/trivial":15,"./pipe":16,"./processor":17}],9:[function(t,e,r){(function(e){var n=t("./diffpatcher").DiffPatcher;r.DiffPatcher=n,r.create=function(t){return new n(t)},r.dateReviver=t("./date-reviver");var i;r.diff=function(){return i||(i=new n),i.diff.apply(i,arguments)},r.patch=function(){return i||(i=new n),i.patch.apply(i,arguments)},r.unpatch=function(){return i||(i=new n),i.unpatch.apply(i,arguments)},r.reverse=function(){return i||(i=new n),i.reverse.apply(i,arguments)};var s="undefined"!=typeof e&&"string"==typeof e.execPath;if(s){var f=t("./formatters/index");r.formatters=f,r.console=f.console}else r.homepage="https://github.com/benjamine/jsondiffpatch",r.version="0.1.5"}).call(this,t("/home/sheila/proj/JsonDiffPatch/node_modules/gulp-browserify/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js"))},{"./date-reviver":7,"./diffpatcher":8,"/home/sheila/proj/JsonDiffPatch/node_modules/gulp-browserify/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":2}],10:[function(t,e,r){var n=t("../contexts/diff").DiffContext,i=t("../contexts/patch").PatchContext,s=t("../contexts/reverse").ReverseContext,f=t("./lcs"),o=3,a="function"==typeof Array.isArray?Array.isArray:function(t){return t instanceof Array},h="function"==typeof Array.prototype.indexOf?function(t,e){return t.indexOf(e)}:function(t,e){for(var r=t.length,n=0;r>n;n++)if(t[n]===e)return n;return-1},l=function(t){if(t.leftIsArray){for(var e,r,i,s,a=t.options&&t.options.objectHash,l=function(t,e,r,n,i){var s=t[r],f=e[n];if(s===f)return!0;if("object"!=typeof s||"object"!=typeof f)return!1;if(!a)return!1;var o,h;return"number"==typeof r?(i.hashCache1=i.hashCache1||[],o=i.hashCache1[r],"undefined"==typeof o&&(i.hashCache1[r]=o=a(s,r))):o=a(s),"undefined"==typeof o?!1:("number"==typeof n?(i.hashCache2=i.hashCache2||[],h=i.hashCache2[n],"undefined"==typeof h&&(i.hashCache2[n]=h=a(f,n))):h=a(f),"undefined"==typeof h?!1:o===h)},c={},u=0,p=0,d=t.left,g=t.right,v=d.length,_=g.length;v>u&&_>u&&l(d,g,u,u,c);)e=u,s=new n(t.left[e],t.right[e]),t.push(s,e),u++;for(;v>p+u&&_>p+u&&l(d,g,v-1-p,_-1-p,c);)r=v-1-p,i=_-1-p,s=new n(t.left[r],t.right[i]),t.push(s,i),p++;var y;if(u+p===v){if(v===_)return void t.setResult(void 0).exit();for(y=y||{_t:"a"},e=u;_-p>e;e++)y[e]=[g[e]];return void t.setResult(y).exit()}if(u+p===_){for(y=y||{_t:"a"},e=u;v-p>e;e++)y["_"+e]=[d[e],0,0];return void t.setResult(y).exit()}c={};var m=d.slice(u,v-p),x=g.slice(u,_-p),b=f.get(m,x,l,c),w=[];for(y=y||{_t:"a"},e=u;v-p>e;e++)h(b.indices1,e-u)<0&&(y["_"+e]=[d[e],0,0],w.push(e));var M=!0;t.options&&t.options.arrays&&t.options.arrays.detectMove===!1&&(M=!1);var C=!1;t.options&&t.options.arrays&&t.options.arrays.includeValueOnMove&&(C=!0);var A=w.length;for(e=u;_-p>e;e++){var R=h(b.indices2,e-u);if(0>R){var P=!1;if(M&&A>0)for(r=0;A>r;r++)if(l(m,x,w[r]-u,e-u,c)){y["_"+w[r]].splice(1,2,e,o),C||(y["_"+w[r]][0]=""),r=w[r],i=e,s=new n(t.left[r],t.right[i]),t.push(s,i),w.splice(r,1),P=!0;break}P||(y[e]=[g[e]])}else r=b.indices1[R]+u,i=b.indices2[R]+u,s=new n(t.left[r],t.right[i]),t.push(s,i)}t.setResult(y).exit()}};l.filterName="arrays";var c={numerically:function(t,e){return t-e},numericallyBy:function(t){return function(e,r){return e[t]-r[t]}}},u=function(t){if(t.nested&&"a"===t.delta._t){var e,r,n=t.delta,s=t.left,f=[],a=[],h=[];for(e in n)if("_t"!==e)if("_"===e[0]){if(0!==n[e][2]&&n[e][2]!==o)throw new Error("only removal or move can be applied at original array indices, invalid diff type: "+n[e][2]);f.push(parseInt(e.slice(1),10))}else 1===n[e].length?a.push({index:parseInt(e,10),value:n[e][0]}):h.push({index:parseInt(e,10),delta:n[e]});for(f=f.sort(c.numerically),e=f.length-1;e>=0;e--){r=f[e];var l=n["_"+r],u=s.splice(r,1)[0];l[2]===o&&a.push({index:l[1],value:u})}a=a.sort(c.numericallyBy("index"));var p=a.length;for(e=0;p>e;e++){var d=a[e];s.splice(d.index,0,d.value)}var g,v=h.length;if(v>0)for(e=0;v>e;e++){var _=h[e];g=new i(t.left[_.index],_.delta),t.push(g,_.index)}return t.children?void t.exit():void t.setResult(t.left).exit()}};u.filterName="arrays";var p=function(t){if(t&&t.children&&"a"===t.delta._t){for(var e,r=t.children.length,n=0;r>n;n++)e=t.children[n],t.left[e.childName]=e.result;t.setResult(t.left).exit()}};p.filterName="arraysCollectChildren";var d=function(t){if(!t.nested)return void(t.delta[2]===o&&(t.newName="_"+t.delta[1],t.setResult([t.delta[0],parseInt(t.childName.substr(1),10),o]).exit()));if("a"===t.delta._t){var e,r;for(e in t.delta)"_t"!==e&&(r=new s(t.delta[e]),t.push(r,e));t.exit()}};d.filterName="arrays";var g=function(t,e,r){var n=e;if("string"==typeof e&&"_"===e[0])n=parseInt(e.substr(1),10);else{var i="_"+e;if(a(r)&&0===r[2])n=i;else for(var s in t){var f=t[s];a(f)&&f[2]===o&&f[1].toString()===e&&(n=s.substr(1))}}return n},v=function(t){if(t&&t.children&&"a"===t.delta._t){for(var e,r=t.children.length,n={_t:"a"},i=0;r>i;i++){e=t.children[i];var s=e.newName;"undefined"==typeof s&&(s=g(t.delta,e.childName,e.result)),n[s]!==e.result&&(n[s]=e.result)}t.setResult(n).exit()}};v.filterName="arraysCollectChildren",r.diffFilter=l,r.patchFilter=u,r.collectChildrenPatchFilter=p,r.reverseFilter=d,r.collectChildrenReverseFilter=v},{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6,"./lcs":12}],11:[function(t,e,r){var n=function(t){t.left instanceof Date?(t.setResult(t.right instanceof Date?t.left.getTime()!==t.right.getTime()?[t.left,t.right]:void 0:[t.left,t.right]),t.exit()):t.right instanceof Date&&t.setResult([t.left,t.right]).exit()};n.filterName="dates",r.diffFilter=n},{}],12:[function(t,e,r){var n=function(t,e,r,n){return t[r]===e[n]},i=function(t,e,r,n){var i,s,f=t.length,o=e.length,a=[f+1];for(i=0;f+1>i;i++)for(a[i]=[o+1],s=0;o+1>s;s++)a[i][s]=0;for(a.match=r,i=1;f+1>i;i++)for(s=1;o+1>s;s++)a[i][s]=r(t,e,i-1,s-1,n)?a[i-1][s-1]+1:Math.max(a[i-1][s],a[i][s-1]);return a},s=function(t,e,r,n,i,f){if(0===n||0===i)return{sequence:[],indices1:[],indices2:[]};if(t.match(e,r,n-1,i-1,f)){var o=s(t,e,r,n-1,i-1,f);return o.sequence.push(e[n-1]),o.indices1.push(n-1),o.indices2.push(i-1),o}return t[n][i-1]>t[n-1][i]?s(t,e,r,n,i-1,f):s(t,e,r,n-1,i,f)},f=function(t,e,r,f){f=f||{};var o=i(t,e,r||n,f),a=s(o,t,e,t.length,e.length,f);return"string"==typeof t&&"string"==typeof e&&(a.sequence=a.sequence.join("")),a};r.get=f},{}],13:[function(t,e,r){var n=t("../contexts/diff").DiffContext,i=t("../contexts/patch").PatchContext,s=t("../contexts/reverse").ReverseContext,f=function(t){if(t&&t.children){for(var e,r=t.children.length,n=t.result,i=0;r>i;i++)e=t.children[i],"undefined"!=typeof e.result&&(n=n||{},n[e.childName]=e.result);n&&t.leftIsArray&&(n._t="a"),t.setResult(n).exit()}};f.filterName="collectChildren";var o=function(t){if(!t.leftIsArray&&"object"===t.leftType){var e,r;for(e in t.left)r=new n(t.left[e],t.right[e]),t.push(r,e);for(e in t.right)"undefined"==typeof t.left[e]&&(r=new n(void 0,t.right[e]),t.push(r,e));return t.children&&0!==t.children.length?void t.exit():void t.setResult(void 0).exit()}};o.filterName="objects";var a=function(t){if(t.nested&&!t.delta._t){var e,r;for(e in t.delta)r=new i(t.left[e],t.delta[e]),t.push(r,e);t.exit()}};a.filterName="objects";var h=function(t){if(t&&t.children&&!t.delta._t){for(var e,r=t.children.length,n=0;r>n;n++)e=t.children[n],t.left[e.childName]!==e.result&&(t.left[e.childName]=e.result);t.setResult(t.left).exit()}};h.filterName="collectChildren";var l=function(t){if(t.nested&&!t.delta._t){var e,r;for(e in t.delta)r=new s(t.delta[e]),t.push(r,e);t.exit()}};l.filterName="objects";var c=function(t){if(t&&t.children&&!t.delta._t){for(var e,r=t.children.length,n={},i=0;r>i;i++)e=t.children[i],n[e.childName]!==e.result&&(n[e.childName]=e.result);t.setResult(n).exit()}};c.filterName="collectChildren",r.collectChildrenDiffFilter=f,r.objectsDiffFilter=o,r.patchFilter=a,r.collectChildrenPatchFilter=h,r.reverseFilter=l,r.collectChildrenReverseFilter=c},{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6}],14:[function(t,e,r){var n=2,i=60,s=null,f=function(){if(!s){var e;if("undefined"!=typeof diff_match_patch)e=new diff_match_patch;else if("function"==typeof t){var r=t("../../external/diff_match_patch_uncompressed");e=new r.diff_match_patch}if(!e){var n=new Error("text diff_match_patch library not found");throw n.diff_match_patch_not_found=!0,n}s={diff:function(t,r){return e.patch_toText(e.patch_make(t,r))},patch:function(t,r){for(var n=e.patch_apply(e.patch_fromText(r),t),i=0;ie;e++){i=n[e];var c=i.slice(0,1);"@"===c?(h=l.exec(i),f=e,o=null,a=null,n[f]="@@ -"+h[3]+","+h[4]+" +"+h[1]+","+h[2]+" @@"):"+"===c?(o=e,n[e]="-"+n[e].slice(1),"+"===n[e-1].slice(0,1)&&(s=n[e],n[e]=n[e-1],n[e-1]=s)):"-"===c&&(a=e,n[e]="+"+n[e].slice(1))}return n.join("\n")},l=function(t){t.nested||t.delta[2]===n&&t.setResult([h(t.delta[0]),0,n]).exit()};l.filterName="texts",r.diffFilter=o,r.patchFilter=a,r.reverseFilter=l},{"../../external/diff_match_patch_uncompressed":1}],15:[function(t,e,r){var n="function"==typeof Array.isArray?Array.isArray:function(t){return t instanceof Array 2 | },i=function(t){if(t.left===t.right)return void t.setResult(void 0).exit();if("undefined"==typeof t.left){if("function"==typeof t.right)throw new Error("functions are not supported");return void t.setResult([t.right]).exit()}if("undefined"==typeof t.right)return void t.setResult([t.left,0,0]).exit();if("function"==typeof t.left||"function"==typeof t.right)throw new Error("functions are not supported");return t.leftType=null===t.left?"null":typeof t.left,t.rightType=null===t.right?"null":typeof t.right,t.leftType!==t.rightType?void t.setResult([t.left,t.right]).exit():"boolean"===t.leftType||"number"===t.leftType?void t.setResult([t.left,t.right]).exit():("object"===t.leftType&&(t.leftIsArray=n(t.left)),"object"===t.rightType&&(t.rightIsArray=n(t.right)),t.leftIsArray!==t.rightIsArray?void t.setResult([t.left,t.right]).exit():void 0)};i.filterName="trivial";var s=function(t){return"undefined"==typeof t.delta?void t.setResult(t.left).exit():(t.nested=!n(t.delta),t.nested?void 0:1===t.delta.length?void t.setResult(t.delta[0]).exit():2===t.delta.length?void t.setResult(t.delta[1]).exit():3===t.delta.length&&0===t.delta[2]?void t.setResult(void 0).exit():void 0)};s.filterName="trivial";var f=function(t){return"undefined"==typeof t.delta?void t.setResult(t.delta).exit():(t.nested=!n(t.delta),t.nested?void 0:1===t.delta.length?void t.setResult([t.delta[0],0,0]).exit():2===t.delta.length?void t.setResult([t.delta[1],t.delta[0]]).exit():3===t.delta.length&&0===t.delta[2]?void t.setResult([t.delta[0]]).exit():void 0)};f.filterName="trivial",r.diffFilter=i,r.patchFilter=s,r.reverseFilter=f},{}],16:[function(t,e,r){var n=function(t){this.name=t,this.filters=[]};n.prototype.process=function(t){if(!this.processor)throw new Error("add this pipe to a processor before using it");for(var e=this.debug,r=this.filters.length,n=t,i=0;r>i;i++){var s=this.filters[i];if(e&&this.log("filter: "+s.filterName),s(n),"object"==typeof n&&n.exiting){n.exiting=!1;break}}!n.next&&this.resultCheck&&this.resultCheck(n)},n.prototype.log=function(t){console.log("[jsondiffpatch] "+this.name+" pipe, "+t)},n.prototype.append=function(){return this.filters.push.apply(this.filters,arguments),this},n.prototype.prepend=function(){return this.filters.unshift.apply(this.filters,arguments),this},n.prototype.indexOf=function(t){if(!t)throw new Error("a filter name is required");for(var e=0;e 70) { 16 | text = text.substr(0, 30) + ' ... ' + text.substr(len - 30, len - 1); 17 | } 18 | return text; 19 | } 20 | 21 | function jsonify (obj, _time) { 22 | var jsonObj, 23 | type = objType(obj); 24 | 25 | !_time && (_time = 0); 26 | try { 27 | if (type == 'Undefined') { 28 | jsonObj = undefined; 29 | } else if (obj !== obj) { 30 | jsonObj = 'NaN'; 31 | } else if (type == 'String') { 32 | jsonObj = textOmit(obj); 33 | } else if (type == 'Null' || type == 'Number' || type == 'Boolean') { 34 | jsonObj = obj; 35 | } else { 36 | jsonObj = JSON.parse(JSON.stringify(obj)); 37 | } 38 | 39 | } catch (e) { 40 | if (type.match('Element')) { 41 | jsonObj = obj.outerHTML.replace(obj.innerHTML, ''); 42 | } else if (type == 'Text') { 43 | jsonObj = textOmit(obj.textContent); 44 | } else if (type == 'Array' || type == 'NodeList') { 45 | var array = []; 46 | obj = slice.call(obj); 47 | 48 | obj.forEach(function (item) { 49 | if (_time >= 2 ) { 50 | array.push(objType(item)) 51 | } else { 52 | array.push(jsonify(item, _time + 1)); 53 | } 54 | }); 55 | jsonObj = array; 56 | } else if (type == 'Object' || type == 'global' || type == 'HTMLDocument') { 57 | var keys = Object.keys(obj), 58 | object = {}; 59 | keys.forEach(function (key) { 60 | if (!obj.hasOwnProperty(key)) return 61 | if (_time >= 2 ) { 62 | object[key] = (objType(obj[key])) 63 | } else { 64 | object[key] = jsonify(obj[key], _time + 1); 65 | } 66 | }); 67 | jsonObj = object; 68 | } else if (type == 'Function') { 69 | jsonObj = textOmit(obj.toString()); 70 | } else { 71 | jsonObj = toString.call(obj); 72 | } 73 | } 74 | return jsonObj; 75 | } 76 | 77 | exports.jsonify = jsonify; 78 | }(this); -------------------------------------------------------------------------------- /server/asserts/socket.js: -------------------------------------------------------------------------------- 1 | ;function _jsinpector_execute (code) { 2 | 'use strict'; 3 | 4 | var script = document.createElement('script') 5 | script.innerHTML = '!function(){' + code + '}()' 6 | document.head.appendChild(script) 7 | document.head.removeChild(script) 8 | }; 9 | !function _jsinpector_socket() { 10 | 'use strict'; 11 | 12 | var script = document.createElement('script') 13 | var clientId = '<%= clientId %>' 14 | 15 | script.src = '<%= host %>/socket.io/socket.io.js' 16 | script.onload = function () { 17 | 18 | var socket = io.connect('<%= host %>/client') 19 | var readyEvent = new CustomEvent('connectionReady') 20 | 21 | socket.on('client:inject:' + clientId, function (payload) { 22 | switch (payload.type) { 23 | case 'eval': _jsinpector_execute(payload.value); 24 | break; 25 | case 'js': 26 | var s = document.createElement('script') 27 | s.src = payload.value 28 | document.head.appendChild(s) 29 | break; 30 | case 'css': 31 | var l = document.createElement('link') 32 | l.rel = 'stylesheet' 33 | l.href = payload.value 34 | document.head.appendChild(l) 35 | break; 36 | } 37 | }) 38 | window.addEventListener('load', function () { 39 | readyEvent.socket = socket 40 | document.dispatchEvent(readyEvent) 41 | }) 42 | } 43 | document.head.appendChild(script) 44 | }(); -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var ejs = require('ejs') 3 | var url = require('url') 4 | var http = require('http') 5 | var path = require('path') 6 | var express = require('express') 7 | var uglify = require('uglify-js') 8 | var compress = require('compression') 9 | var bodyParser = require('body-parser') 10 | var cookieParser = require('cookie-parser') 11 | var jsondiffpatch = require('jsondiffpatch').create({ 12 | textDiff: { 13 | minLength: 256 14 | } 15 | }) 16 | var config = require('../config') 17 | /** 18 | * JSInspector server and socket server 19 | * @type {express} 20 | */ 21 | var app = new express() 22 | var server = http.Server(app) 23 | var io = require('socket.io')(server, { log: false }) 24 | 25 | var tmpDir = config.tmp_dir 26 | !fs.existsSync(tmpDir) && fs.mkdirSync(tmpDir) 27 | 28 | /** 29 | * Enalbe gzip 30 | */ 31 | app.use(compress()) 32 | 33 | var sourceCaches = {} 34 | var publicDir = path.join(__dirname, '../public') 35 | function loadFile(p, minify) { 36 | var sourceCode = fs.readFileSync(p, 'utf-8') 37 | if (minify) { 38 | sourceCode = uglify.minify( 39 | sourceCode, 40 | { 41 | fromString: true, 42 | mangle: true, 43 | compress: true 44 | } 45 | ).code 46 | } 47 | return sourceCode 48 | } 49 | if (!config.isDev) { 50 | var files = fs.readdirSync(publicDir) 51 | process.stdout.write('\nPreloading files ..') 52 | files.forEach(function (f) { 53 | if (/\.js$/.test(f)) { 54 | var source = loadFile(path.join(publicDir, f), config.enable_mini) 55 | sourceCaches['/' + f] = source 56 | process.stdout.write('.') 57 | } 58 | }) 59 | console.log('') 60 | } 61 | 62 | app.get('/*.js', function (req, res, next) { 63 | var fpath = path.join(publicDir, '.' + req.path) 64 | if (!fs.existsSync(fpath)) return res.status(404).send(req.path + ' not found !') 65 | 66 | var sourceCode = '' 67 | if (sourceCaches[req.path]) 68 | sourceCode = sourceCaches[req.path] 69 | else { 70 | sourceCode = loadFile(fpath, config.enable_mini) 71 | if (!config.isDev) { 72 | sourceCaches[req.path] = sourceCode 73 | } 74 | } 75 | if (req.path == '/inspector.js') sourceCode = ejs.render(sourceCode, { 76 | serverTime: +new Date 77 | }) 78 | res.send(sourceCode) 79 | }) 80 | 81 | app.use(express.static(path.join(__dirname, '../public'))) 82 | 83 | /** 84 | * Global CORS 85 | **/ 86 | app.use(function(req, res, next) { 87 | res.setHeader('Access-Control-Allow-Origin', req.headers.origin ? req.headers.origin : '*') 88 | res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,HEAD,GET,OPTIONS,PATCH') 89 | next() 90 | }) 91 | app.use(cookieParser()) 92 | app.enable('etag') 93 | app.use(require('./routes/inspector')) 94 | app.use(require('./routes/client')) 95 | 96 | app.get('/', function (req, res) { 97 | res.send(fs.readFileSync(path.join(__dirname, '../public/dashboard.html'), 'utf-8')) 98 | }) 99 | var clients = {} 100 | app.get('/clients', function (req, res, next) { 101 | res.send(clients) 102 | }) 103 | app.get('/preview/:cid', function (req, res, next) { 104 | var fpath = path.join(tmpDir, 'client_'+ req.params.cid + '.json') 105 | if (!fs.existsSync(fpath)) return res.status(404).send(req.path + ' not found !') 106 | 107 | var tmpData = fs.readFileSync(fpath, 'utf-8') 108 | tmpData = JSON.parse(tmpData) 109 | res.send(tmpData.html) 110 | }) 111 | 112 | var inspectorSocket = io.of('/inpsector') 113 | var clientSocket = io.of('/client') 114 | var deviceSocket = io.of('/device') 115 | 116 | inspectorSocket.on('connection', function(socket) { 117 | socket.on('inspector:input', function(data) { 118 | clientSocket.emit('client:inject:' + data.id, data.payload) 119 | }) 120 | }) 121 | 122 | clientSocket.on('connection', function(socket) { 123 | var socketId 124 | socket.on('client:init', function (payload) { 125 | 126 | var clientId = socketId = payload.cid 127 | var packetId = payload.pid 128 | var data = payload.data 129 | var file = path.join(tmpDir, 'client_' + clientId + '.json') 130 | 131 | // connect session 132 | clients[clientId] = data.browser 133 | 134 | // save as base document data 135 | fs.writeFileSync(file, JSON.stringify(data), 'utf-8') 136 | // tell inspector 137 | inspectorSocket.emit('server:inspector:' + clientId, data) 138 | 139 | socket.emit('server:answer:init:' + clientId, { 140 | cid: clientId, 141 | pid: packetId 142 | }) 143 | // device connect 144 | deviceSocket.emit('device:update') 145 | }) 146 | 147 | socket.on('disconnect', function () { 148 | if (socketId) delete clients[socketId] 149 | 150 | // device disconnect 151 | deviceSocket.emit('device:update') 152 | }) 153 | 154 | socket.on('client:update', function (payload) { 155 | var clientId = payload.cid 156 | var packetId = payload.pid 157 | var data = payload.data 158 | var file = path.join(tmpDir, 'client_' + clientId + '.json') 159 | // syncData is used to tell inspector what is happening of client 160 | var tmpData = fs.readFileSync(file, 'utf-8') 161 | var syncData 162 | 163 | function fail () { 164 | socket.emit('server:answer:init:' + clientId, { 165 | cid: clientId, 166 | pid: packetId, 167 | error: { code: 4000, message: 'Base HTML not found!' } 168 | }) 169 | } 170 | syncData = JSON.parse(JSON.stringify(data)) 171 | 172 | if (tmpData) { 173 | tmpData = JSON.parse(tmpData) 174 | 175 | if (!data.browser) syncData.browser = data.browser = tmpData.browser 176 | 177 | // patching html text 178 | if (data.delta && !data.html && tmpData.html) { 179 | 180 | // full amount release, currently I can't support delta release 181 | syncData.html = data.html = jsondiffpatch.patch(tmpData.html, data.delta); 182 | delete data.delta; 183 | 184 | } else if (!data.html && tmpData.html) { 185 | // match when receive the data packet only the meta 186 | data.html = tmpData.html; 187 | } else if (!data.html) { 188 | return fail() 189 | } 190 | 191 | // save newest inspect data 192 | fs.writeFileSync(file, JSON.stringify(data), 'utf-8') 193 | 194 | // tell inspector 195 | inspectorSocket.emit('server:inspector:' + clientId, syncData) 196 | socket.emit('server:answer:update:' + clientId, { 197 | cid: clientId, 198 | pid: packetId 199 | }) 200 | 201 | } else { 202 | return fail() 203 | } 204 | 205 | }) 206 | }) 207 | 208 | module.exports = server 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /server/routes/client.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var router = express.Router() 3 | var fs = require('fs') 4 | var path = require('path') 5 | var uglify = require('uglify-js') 6 | var ejs = require('ejs') 7 | var scripts = {} 8 | var uuid = require('node-uuid') 9 | var config = require('../../config') 10 | var ENABLE_MINI = config.enable_mini 11 | 12 | /** 13 | * Client inject script 14 | */ 15 | router.get('/s', function(req, res) { 16 | ;['inject', 'console', 'jsonify', 'socket', 'jsondiffpatch'].forEach(function(s) { 17 | scripts[s] = fs.readFileSync(path.join(__dirname, '../asserts/', s + '.js'), 'utf-8') 18 | }) 19 | res.setHeader('Content-Type', 'application/javascript') 20 | 21 | var clientId = req.cookies['_jsinspector_client_id_'] 22 | if (!clientId) { 23 | clientId = uuid.v4() 24 | res.cookie('_jsinspector_client_id_', clientId, {maxAge: 360 * 24 * 60 * 60 * 1000}) 25 | } 26 | 27 | var clientParams = { 28 | clientId: clientId, 29 | serverTime: +new Date, 30 | host: 'http://' + req.headers.host, 31 | delta: req.query.hasOwnProperty('delta') && req.query.delta != 'false' 32 | } 33 | var inject = ejs.render(scripts.inject, clientParams) 34 | var socket = ejs.render(scripts.socket, clientParams) 35 | var sourceCode = [scripts.jsonify, scripts.console, scripts.jsondiffpatch, socket, inject].join('\n') 36 | if (ENABLE_MINI) { 37 | res.send(uglify.minify( 38 | sourceCode, 39 | { 40 | fromString: true, 41 | mangle: true, 42 | compress: true 43 | } 44 | ).code) 45 | } else { 46 | res.send(sourceCode) 47 | } 48 | }) 49 | 50 | module.exports = router 51 | -------------------------------------------------------------------------------- /server/routes/inspector.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var router = express.Router() 3 | var fs = require('fs') 4 | var path = require('path') 5 | var config = require('../../config') 6 | 7 | /** 8 | * Client inject script 9 | */ 10 | router.get('/inspector/:id', function(req, res) { 11 | var file = fs.readFileSync(path.join(config.tmp_dir, 'client_' + req.params.id + '.json'), 'utf-8'); 12 | if (file) { 13 | res.send(file); 14 | } else { 15 | res.status(404).send('Please inject the script to your code !') 16 | } 17 | }) 18 | 19 | module.exports = router 20 | --------------------------------------------------------------------------------