├── .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 | 
2 | ===================================================================
3 | [](https://badge.fury.io/js/jsinspector)
4 |
5 | Mobile debug toolkit, include **console.log**, **HTML Inspector** and **script inject api**.
6 |
7 |

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 | 
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 |
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(/