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 CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "sunda-tracker",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
+ "main": "dist/sunda-tracker.es.js",
4
5
  "module": "dist/sunda-tracker.es.js",
6
+ "files": [
7
+ "dist"
8
+ ],
5
9
  "type": "module",
6
10
  "scripts": {
7
11
  "dev": "vite",
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
@@ -1,9 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/prettierrc",
3
- "semi": false,
4
- "tabWidth": 2,
5
- "singleQuote": true,
6
- "printWidth": 80,
7
- "trailingComma": "none",
8
- "quoteProps": "as-needed"
9
- }
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
- ])
@@ -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
- }
@@ -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
- }