sunda-tracker 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/.babelrc +0 -23
- package/.npmrc.exclude +0 -1
- package/.prettierrc.json +0 -9
- package/.yarnrc.exclude +0 -1
- package/eslint.config.js +0 -25
- package/src/core/ApiSender.js +0 -185
- package/src/core/Device.js +0 -291
- package/src/core/ElasticsearchSender.js +0 -215
- package/src/core/Queue.js +0 -21
- package/src/core/Storage.js +0 -34
- package/src/core/Track.js +0 -53
- package/src/index.js +0 -45
- package/src/plugins/AutoTrack.js +0 -47
- package/src/plugins/Performance.js +0 -189
- package/src/utils/constants.js +0 -21
- package/src/utils/helpers.js +0 -216
- package/vite.config.js +0 -33
package/package.json
CHANGED
package/.babelrc
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"presets": [
|
|
3
|
-
[
|
|
4
|
-
"@babel/preset-env",
|
|
5
|
-
{
|
|
6
|
-
"targets": {
|
|
7
|
-
"browsers": [
|
|
8
|
-
"> 1%",
|
|
9
|
-
"last 2 versions",
|
|
10
|
-
"not ie <= 8"
|
|
11
|
-
]
|
|
12
|
-
},
|
|
13
|
-
"modules": "commonjs",
|
|
14
|
-
"useBuiltIns": "usage",
|
|
15
|
-
"corejs": 3
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
],
|
|
19
|
-
"plugins": [
|
|
20
|
-
"@babel/plugin-transform-runtime",
|
|
21
|
-
"@babel/plugin-proposal-class-properties"
|
|
22
|
-
]
|
|
23
|
-
}
|
package/.npmrc.exclude
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
registry=http://nexus.sunda.top/repository/npm-sunda-hosted/
|
package/.prettierrc.json
DELETED
package/.yarnrc.exclude
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
registry=http://nexus.sunda.top/repository/npm-sunda-hosted/
|
package/eslint.config.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js'
|
|
2
|
-
import globals from 'globals'
|
|
3
|
-
import { defineConfig } from 'eslint/config'
|
|
4
|
-
import googleConfig from 'eslint-config-google'
|
|
5
|
-
|
|
6
|
-
export default defineConfig([
|
|
7
|
-
{
|
|
8
|
-
files: ['src/**/*.{js,mjs,cjs}'],
|
|
9
|
-
plugins: { js },
|
|
10
|
-
extends: [js.configs.recommended, googleConfig],
|
|
11
|
-
rules: {
|
|
12
|
-
'valid-jsdoc': 'off',
|
|
13
|
-
'require-jsdoc': 'off',
|
|
14
|
-
semi: ['error', 'never'],
|
|
15
|
-
'comma-dangle': ['error', 'never'],
|
|
16
|
-
indent: ['error', 2, { SwitchCase: 1 }],
|
|
17
|
-
'operator-linebreak': 'off',
|
|
18
|
-
'object-curly-spacing': ['error', 'always']
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
files: ['src/**/*.{js,mjs,cjs}'],
|
|
23
|
-
languageOptions: { globals: globals.browser }
|
|
24
|
-
}
|
|
25
|
-
])
|
package/src/core/ApiSender.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import request from 'axios'
|
|
2
|
-
import { moment } from '@/utils/helpers'
|
|
3
|
-
|
|
4
|
-
export default class ApiSender {
|
|
5
|
-
/**
|
|
6
|
-
* @param {Object} config 配置项
|
|
7
|
-
* @param {string} config.node ES服务器地址
|
|
8
|
-
* @param {string} config.index 索引名称
|
|
9
|
-
* @param {string} [config.username] 用户名
|
|
10
|
-
* @param {string} [config.password] 密码
|
|
11
|
-
* @param {number} [config.batchSize=100] 批量发送大小
|
|
12
|
-
* @param {number} [config.flushInterval=5000] 自动刷新间隔(ms)
|
|
13
|
-
*/
|
|
14
|
-
constructor(config) {
|
|
15
|
-
this.config = {
|
|
16
|
-
batchSize: 100,
|
|
17
|
-
flushInterval: 5000,
|
|
18
|
-
...config
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
this.queue = []
|
|
22
|
-
this.timer = null
|
|
23
|
-
this.genApiCall(config)
|
|
24
|
-
this.startAutoFlush()
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async genApiCall({ collectUrl: url, collectMethod: method }) {
|
|
28
|
-
this.batchSave = (data) => {
|
|
29
|
-
return request({
|
|
30
|
-
url,
|
|
31
|
-
method,
|
|
32
|
-
data
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 发送单条数据
|
|
39
|
-
* @param {Object} data
|
|
40
|
-
*/
|
|
41
|
-
async send(data) {
|
|
42
|
-
try {
|
|
43
|
-
const docWithTimestamp = {
|
|
44
|
-
...data,
|
|
45
|
-
'@timestamp': new Date().toISOString()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
this.queue.push(docWithTimestamp)
|
|
49
|
-
|
|
50
|
-
if (this.queue.length >= this.config.batchSize) {
|
|
51
|
-
await this.flush()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return true
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.error('Error sending data to Elasticsearch:', error)
|
|
57
|
-
return false
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 批量发送数据
|
|
63
|
-
* @param {Array} dataArray 数据数组
|
|
64
|
-
*/
|
|
65
|
-
async sendBulk(dataArray) {
|
|
66
|
-
try {
|
|
67
|
-
const timestamp = new Date().toISOString()
|
|
68
|
-
const docsWithTimestamp = dataArray.map((data) => ({
|
|
69
|
-
...data,
|
|
70
|
-
'@timestamp': timestamp
|
|
71
|
-
}))
|
|
72
|
-
|
|
73
|
-
this.queue.push(...docsWithTimestamp)
|
|
74
|
-
|
|
75
|
-
if (this.queue.length >= this.config.batchSize) {
|
|
76
|
-
await this.flush()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return true
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error('Error sending bulk data to Elasticsearch:', error)
|
|
82
|
-
return false
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 构建批量请求体
|
|
88
|
-
* @param {Array} documents 文档数组
|
|
89
|
-
* @returns {string} NDJSON格式的请求体
|
|
90
|
-
*/
|
|
91
|
-
buildBulkRequestBody(documents) {
|
|
92
|
-
return (
|
|
93
|
-
documents
|
|
94
|
-
.flatMap((doc) => [
|
|
95
|
-
JSON.stringify({
|
|
96
|
-
index: {
|
|
97
|
-
_index: `${this.config.index}-${moment().format('YYYY.MM.DD')}`
|
|
98
|
-
}
|
|
99
|
-
}),
|
|
100
|
-
JSON.stringify(doc)
|
|
101
|
-
])
|
|
102
|
-
.join('\n') + '\n'
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async flush() {
|
|
107
|
-
if (this.queue.length === 0) return
|
|
108
|
-
|
|
109
|
-
const documents = [...this.queue]
|
|
110
|
-
this.queue = []
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
await this.batchSave(documents)
|
|
114
|
-
|
|
115
|
-
return true
|
|
116
|
-
} catch (error) {
|
|
117
|
-
this.stopAutoFlush()
|
|
118
|
-
console.error('Error flushing data to Elasticsearch:', error)
|
|
119
|
-
this.queue.unshift(...documents)
|
|
120
|
-
return false
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
handleFailedDocuments(failedItems, originalDocuments) {
|
|
125
|
-
failedItems.forEach((item) => {
|
|
126
|
-
const failedDoc = originalDocuments[item.index._id]
|
|
127
|
-
this.storeFailedDocument(failedDoc)
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
storeFailedDocument(document) {
|
|
132
|
-
try {
|
|
133
|
-
const failedDocs = JSON.parse(
|
|
134
|
-
localStorage.getItem('es_failed_docs') || '[]'
|
|
135
|
-
)
|
|
136
|
-
failedDocs.push({
|
|
137
|
-
document,
|
|
138
|
-
timestamp: new Date().toISOString(),
|
|
139
|
-
retryCount: 0
|
|
140
|
-
})
|
|
141
|
-
localStorage.setItem('es_failed_docs', JSON.stringify(failedDocs))
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.error('Error storing failed document:', error)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async retryFailedDocuments() {
|
|
148
|
-
try {
|
|
149
|
-
const failedDocs = JSON.parse(
|
|
150
|
-
localStorage.getItem('es_failed_docs') || '[]'
|
|
151
|
-
)
|
|
152
|
-
if (failedDocs.length === 0) return
|
|
153
|
-
|
|
154
|
-
const docsToRetry = failedDocs.filter((doc) => doc.retryCount < 3)
|
|
155
|
-
const remainingDocs = failedDocs.filter((doc) => doc.retryCount >= 3)
|
|
156
|
-
docsToRetry.forEach((doc) => doc.retryCount++)
|
|
157
|
-
|
|
158
|
-
await this.sendBulk(docsToRetry.map((doc) => doc.document))
|
|
159
|
-
localStorage.setItem('es_failed_docs', JSON.stringify(remainingDocs))
|
|
160
|
-
} catch (error) {
|
|
161
|
-
console.error('Error retrying failed documents:', error)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
startAutoFlush() {
|
|
166
|
-
this.timer = setInterval(() => {
|
|
167
|
-
if (this.queue.length > 0) {
|
|
168
|
-
this.flush()
|
|
169
|
-
}
|
|
170
|
-
this.retryFailedDocuments()
|
|
171
|
-
}, this.config.flushInterval)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
stopAutoFlush() {
|
|
175
|
-
if (this.timer) {
|
|
176
|
-
clearInterval(this.timer)
|
|
177
|
-
this.timer = null
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
destroy() {
|
|
182
|
-
this.stopAutoFlush()
|
|
183
|
-
return this.flush()
|
|
184
|
-
}
|
|
185
|
-
}
|
package/src/core/Device.js
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { uuidv4 } from '@/utils/helpers'
|
|
2
|
-
|
|
3
|
-
export default class Device {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.parser = this.initUAParser()
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
initUAParser() {
|
|
9
|
-
const ua = navigator.userAgent
|
|
10
|
-
return {
|
|
11
|
-
os: this.getOS(ua),
|
|
12
|
-
browser: this.getBrowser(ua),
|
|
13
|
-
device: this.getDevice(ua)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
generateUUID() {
|
|
18
|
-
return uuidv4()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getOS(ua) {
|
|
22
|
-
let os = 'unknown'
|
|
23
|
-
let version = 'unknown'
|
|
24
|
-
|
|
25
|
-
// Windows
|
|
26
|
-
if (ua.includes('Windows')) {
|
|
27
|
-
os = 'Windows'
|
|
28
|
-
if (ua.includes('Windows NT 10.0')) version = '10'
|
|
29
|
-
else if (ua.includes('Windows NT 6.3')) version = '8.1'
|
|
30
|
-
else if (ua.includes('Windows NT 6.2')) version = '8'
|
|
31
|
-
else if (ua.includes('Windows NT 6.1')) version = '7'
|
|
32
|
-
} else if (ua.includes('Macintosh')) {
|
|
33
|
-
os = 'macOS'
|
|
34
|
-
const match = ua.match(/Mac OS X ([0-9_]+)/)
|
|
35
|
-
if (match) version = match[1].replace(/_/g, '.')
|
|
36
|
-
} else if (ua.includes('iPhone') || ua.includes('iPad')) {
|
|
37
|
-
os = 'iOS'
|
|
38
|
-
const match = ua.match(/OS ([0-9_]+)/)
|
|
39
|
-
if (match) version = match[1].replace(/_/g, '.')
|
|
40
|
-
} else if (ua.includes('Android')) {
|
|
41
|
-
os = 'Android'
|
|
42
|
-
const match = ua.match(/Android ([0-9.]+)/)
|
|
43
|
-
if (match) version = match[1]
|
|
44
|
-
} else if (ua.includes('Linux')) {
|
|
45
|
-
os = 'Linux'
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return { name: os, version }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getBrowser(ua) {
|
|
52
|
-
let name = 'unknown'
|
|
53
|
-
let version = 'unknown'
|
|
54
|
-
let engine = 'unknown'
|
|
55
|
-
const engineVersion = 'unknown'
|
|
56
|
-
|
|
57
|
-
// Chrome
|
|
58
|
-
if (ua.includes('Chrome/')) {
|
|
59
|
-
name = 'Chrome'
|
|
60
|
-
const match = ua.match(/Chrome\/([0-9.]+)/)
|
|
61
|
-
if (match) version = match[1]
|
|
62
|
-
engine = 'Blink'
|
|
63
|
-
} else if (ua.includes('Firefox/')) {
|
|
64
|
-
name = 'Firefox'
|
|
65
|
-
const match = ua.match(/Firefox\/([0-9.]+)/)
|
|
66
|
-
if (match) version = match[1]
|
|
67
|
-
engine = 'Gecko'
|
|
68
|
-
} else if (ua.includes('Safari/') && !ua.includes('Chrome/')) {
|
|
69
|
-
name = 'Safari'
|
|
70
|
-
const match = ua.match(/Version\/([0-9.]+)/)
|
|
71
|
-
if (match) version = match[1]
|
|
72
|
-
engine = 'WebKit'
|
|
73
|
-
} else if (ua.includes('Edg/')) {
|
|
74
|
-
name = 'Edge'
|
|
75
|
-
const match = ua.match(/Edg\/([0-9.]+)/)
|
|
76
|
-
if (match) version = match[1]
|
|
77
|
-
engine = 'Blink'
|
|
78
|
-
} else if (ua.includes('Trident/')) {
|
|
79
|
-
name = 'Internet Explorer'
|
|
80
|
-
const match = ua.match(/rv:([0-9.]+)/)
|
|
81
|
-
if (match) version = match[1]
|
|
82
|
-
engine = 'Trident'
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return { name, version, engine, engineVersion }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
getDevice(ua) {
|
|
89
|
-
let type = 'desktop'
|
|
90
|
-
let brand = 'unknown'
|
|
91
|
-
let model = 'unknown'
|
|
92
|
-
|
|
93
|
-
// 转换为小写以便统一处理
|
|
94
|
-
const uaLower = ua.toLowerCase()
|
|
95
|
-
|
|
96
|
-
// 操作系统/平台检测
|
|
97
|
-
if (uaLower.includes('macintosh') || uaLower.includes('mac os')) {
|
|
98
|
-
brand = 'Mac'
|
|
99
|
-
// return { type, brand, model }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (uaLower.includes('windows')) {
|
|
103
|
-
brand = 'PC'
|
|
104
|
-
return { type, brand, model }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// 移动设备检测
|
|
108
|
-
if (
|
|
109
|
-
ua.includes('Mobile') ||
|
|
110
|
-
ua.includes('Android') ||
|
|
111
|
-
ua.includes('iPhone')
|
|
112
|
-
) {
|
|
113
|
-
type = 'mobile'
|
|
114
|
-
|
|
115
|
-
// Apple 设备
|
|
116
|
-
if (ua.includes('iPhone')) {
|
|
117
|
-
brand = 'Apple'
|
|
118
|
-
model = 'iPhone'
|
|
119
|
-
} else if (ua.includes('iPad')) {
|
|
120
|
-
brand = 'Apple'
|
|
121
|
-
model = 'iPad'
|
|
122
|
-
type = 'tablet'
|
|
123
|
-
} else if (ua.includes('Android')) {
|
|
124
|
-
brand = 'Android'
|
|
125
|
-
// 常见安卓品牌关键词映射
|
|
126
|
-
const brandMap = {
|
|
127
|
-
// 中国品牌
|
|
128
|
-
'huawei': 'Huawei',
|
|
129
|
-
'honor': 'Honor',
|
|
130
|
-
'xiaomi': 'Xiaomi',
|
|
131
|
-
'redmi': 'Xiaomi',
|
|
132
|
-
'poco': 'Xiaomi',
|
|
133
|
-
'mi': 'Xiaomi',
|
|
134
|
-
'oppo': 'OPPO',
|
|
135
|
-
'oneplus': 'OnePlus',
|
|
136
|
-
'vivo': 'vivo',
|
|
137
|
-
'iqoo': 'vivo',
|
|
138
|
-
'realme': 'Realme',
|
|
139
|
-
'meizu': 'Meizu',
|
|
140
|
-
'zte': 'ZTE',
|
|
141
|
-
'lenovo': 'Lenovo',
|
|
142
|
-
|
|
143
|
-
// 三星系列
|
|
144
|
-
'samsung': 'Samsung',
|
|
145
|
-
'sm-': 'Samsung',
|
|
146
|
-
'gt-': 'Samsung',
|
|
147
|
-
'sgh-': 'Samsung',
|
|
148
|
-
'galaxy': 'Samsung',
|
|
149
|
-
|
|
150
|
-
// 传音系列
|
|
151
|
-
'tecno': 'TECNO',
|
|
152
|
-
'infinix': 'Infinix',
|
|
153
|
-
'itel': 'itel',
|
|
154
|
-
|
|
155
|
-
// 其他国际品牌
|
|
156
|
-
'sony': 'Sony',
|
|
157
|
-
'sony ericsson': 'Sony',
|
|
158
|
-
'lg': 'LG',
|
|
159
|
-
'motorola': 'Motorola',
|
|
160
|
-
'moto': 'Motorola',
|
|
161
|
-
'nokia': 'Nokia',
|
|
162
|
-
'htc': 'HTC',
|
|
163
|
-
'asus': 'ASUS',
|
|
164
|
-
'zenfone': 'ASUS',
|
|
165
|
-
'blackberry': 'BlackBerry',
|
|
166
|
-
'google': 'Google',
|
|
167
|
-
'pixel': 'Google',
|
|
168
|
-
'surface': 'Surface',
|
|
169
|
-
|
|
170
|
-
// 其他中国品牌
|
|
171
|
-
'nubia': 'Nubia',
|
|
172
|
-
'360': '360',
|
|
173
|
-
'smartisan': 'Smartisan',
|
|
174
|
-
'gione': 'Gionee',
|
|
175
|
-
'k-touch': 'K-Touch',
|
|
176
|
-
|
|
177
|
-
// 印度品牌
|
|
178
|
-
'micromax': 'Micromax',
|
|
179
|
-
'lava': 'Lava',
|
|
180
|
-
'karbonn': 'Karbonn',
|
|
181
|
-
|
|
182
|
-
// 其他亚洲品牌
|
|
183
|
-
'sharp': 'Sharp',
|
|
184
|
-
'panasonic': 'Panasonic'
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 尝试从 UA 中提取品牌信息
|
|
188
|
-
for (const key in brandMap) {
|
|
189
|
-
if (uaLower.includes(key)) {
|
|
190
|
-
brand = brandMap[key]
|
|
191
|
-
break
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// 如果上面没匹配到,尝试从括号中提取信息
|
|
196
|
-
if (brand === 'Android') {
|
|
197
|
-
const match = ua.match(/\((.+?)\)/)
|
|
198
|
-
if (match) {
|
|
199
|
-
const details = match[1].split(';')
|
|
200
|
-
// 通常品牌信息在第3个或第4个位置
|
|
201
|
-
for (let i = 2; i < details.length; i++) {
|
|
202
|
-
const detail = details[i] && details[i].trim().toLowerCase()
|
|
203
|
-
if (detail) {
|
|
204
|
-
for (const key in brandMap) {
|
|
205
|
-
if (detail.includes(key)) {
|
|
206
|
-
brand = brandMap[key]
|
|
207
|
-
break
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (brand !== 'Android') break
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 尝试提取型号信息
|
|
217
|
-
const modelMatch = ua.match(/Build\/(.*?)[)\s]/)
|
|
218
|
-
if (modelMatch) {
|
|
219
|
-
model = modelMatch[1].trim()
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return { type, brand, model }
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
getInfo() {
|
|
228
|
-
const connection =
|
|
229
|
-
navigator.connection ||
|
|
230
|
-
navigator.mozConnection ||
|
|
231
|
-
navigator.webkitConnection
|
|
232
|
-
|
|
233
|
-
return {
|
|
234
|
-
// 基础信息
|
|
235
|
-
userAgent: navigator.userAgent,
|
|
236
|
-
platform: navigator.platform,
|
|
237
|
-
language: navigator.language,
|
|
238
|
-
languages: navigator.languages,
|
|
239
|
-
|
|
240
|
-
// 操作系统信息
|
|
241
|
-
os: this.parser.os,
|
|
242
|
-
|
|
243
|
-
// 浏览器信息
|
|
244
|
-
browser: this.parser.browser,
|
|
245
|
-
|
|
246
|
-
// 设备信息
|
|
247
|
-
device: this.parser.device,
|
|
248
|
-
|
|
249
|
-
// 屏幕信息
|
|
250
|
-
screen: {
|
|
251
|
-
width: window.screen.width,
|
|
252
|
-
height: window.screen.height,
|
|
253
|
-
availWidth: window.screen.availWidth,
|
|
254
|
-
availHeight: window.screen.availHeight,
|
|
255
|
-
colorDepth: window.screen.colorDepth,
|
|
256
|
-
pixelDepth: window.screen.pixelDepth,
|
|
257
|
-
orientation: window.screen.orientation ?
|
|
258
|
-
window.screen.orientation.type :
|
|
259
|
-
'unknown'
|
|
260
|
-
},
|
|
261
|
-
|
|
262
|
-
// 窗口信息
|
|
263
|
-
window: {
|
|
264
|
-
innerWidth: window.innerWidth,
|
|
265
|
-
innerHeight: window.innerHeight,
|
|
266
|
-
outerWidth: window.outerWidth,
|
|
267
|
-
outerHeight: window.outerHeight,
|
|
268
|
-
devicePixelRatio: window.devicePixelRatio
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
// 网络信息
|
|
272
|
-
network: connection ?
|
|
273
|
-
{
|
|
274
|
-
type: connection.type,
|
|
275
|
-
effectiveType: connection.effectiveType,
|
|
276
|
-
downlink: connection.downlink,
|
|
277
|
-
downlinkMax: connection.downlinkMax,
|
|
278
|
-
rtt: connection.rtt,
|
|
279
|
-
saveData: connection.saveData
|
|
280
|
-
} :
|
|
281
|
-
undefined,
|
|
282
|
-
|
|
283
|
-
// 硬件信息
|
|
284
|
-
hardware: {
|
|
285
|
-
maxTouchPoints: navigator.maxTouchPoints,
|
|
286
|
-
hardwareConcurrency: navigator.hardwareConcurrency,
|
|
287
|
-
deviceMemory: navigator.deviceMemory
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|