@aochuang/client-sdk 1.0.368 → 1.0.370

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@aochuang/client-sdk",
3
- "version": "1.0.368",
3
+ "version": "1.0.370",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -228,8 +228,12 @@ export default class Websocket {
228
228
  exportLogData () {
229
229
  return this.socket.exportLogData()
230
230
  }
231
+
232
+ checkAndSync () {
233
+ return this.socket.checkAndSync()
234
+ }
231
235
 
232
236
  clearLogData () {
233
237
  return this.socket.clearLogData()
234
238
  }
235
- }
239
+ }
@@ -45,10 +45,10 @@ export default class Socket {
45
45
  constructor (options) {
46
46
  options = Object.assign({
47
47
  url: null,
48
- client: null,
49
- logEnable: false,
50
- logName: 'socket',
51
- logMaxSize: 5 * 1024 * 1024, // 最多记5M日志
48
+ client: null,
49
+ logEnable: false,
50
+ logName: 'socket',
51
+ logMaxSize: 3 * 1024 * 1024, // 非网页版本最多记3M日志
52
52
  socketAdapter: null, // 支持从外面传一个socket构造函数,但是要实现socket标准的使用方式
53
53
  onExceedMaxReconnectLimit: null, // 超过最大重连次数回调
54
54
  onBeforeSendMessage: null,
@@ -74,7 +74,7 @@ export default class Socket {
74
74
  this.url = options.url
75
75
  this.event = new Event()
76
76
  this.log = new Log({
77
- maxSize: options.logMaxSize, // 最多存5M的日志数据
77
+ maxSize: options.logMaxSize, // 非网页版本日志大小限制
78
78
  name: options.logName || 'socket'
79
79
  })
80
80
  // 会话失效
@@ -790,6 +790,10 @@ export default class Socket {
790
790
  exportLogData () {
791
791
  return this.log.exportLogData()
792
792
  }
793
+
794
+ checkAndSync () {
795
+ return this.log.checkAndSync()
796
+ }
793
797
 
794
798
  clearLogData () {
795
799
  return this.log.clearLogData()
package/utils/log.js CHANGED
@@ -1,243 +1,626 @@
1
- import { unique } from './string'
2
- import { parse } from './json'
3
1
  import { formatDate } from './date'
4
2
 
5
- function requestAnimationFrame (callback) {
6
- if (typeof uni === 'object') {
7
- return setTimeout(() => {
8
- callback && callback()
9
- }, 10)
10
- } else {
11
- return window.requestAnimationFrame(() => {
12
- callback && callback()
13
- })
3
+ let uniqueId = Date.now()
4
+
5
+ function unique () {
6
+ uniqueId++
7
+ return uniqueId
8
+ }
9
+
10
+ function parse (value) {
11
+ try {
12
+ return typeof value === 'string' ? JSON.parse(value) : value
13
+ } catch (e) {
14
+ return value
14
15
  }
15
16
  }
16
17
 
17
- function cancelAnimationFrame (rafId) {
18
- if (typeof uni === 'object') {
19
- clearTimeout(rafId)
20
- } else {
21
- window.cancelAnimationFrame(rafId)
22
- }
23
- }
24
-
25
- const LogManage = {
26
- list: [],
27
- add: (logInstance) => {
28
- if (!(logInstance instanceof Log)) {
29
- throw new Error('不是有效日志实例')
30
- }
31
- if (!this.list.some(v => v === logInstance)) {
32
- throw new Error('日志实例已存在')
33
- }
34
- this.list.push(logInstance)
35
- },
36
- remove: (logInstance) => {
37
- let index = -1
38
- this.list.some((v, i) => {
39
- if (v === logInstance) {
40
- index = i
41
- return true
42
- }
43
- })
44
- if (index < 0) {
45
- return
46
- }
47
- this.list.splice(index, 1)
48
- }
18
+ const INDEXED_DB_VERSION = 2
19
+ const INDEXED_DB_STORE_NAME = 'logs'
20
+ const INDEXED_DB_META_STORE_NAME = 'meta'
21
+ const INDEXED_DB_ORDER_INDEX_NAME = 'order'
22
+ const INDEXED_DB_MAX_SIZE = 50 * 1024 * 1024
23
+ const INDEXED_DB_STATS_KEY = 'stats'
24
+ const AUTO_PUBLISH_DELAY = 1000
25
+
26
+ function isUniRuntime () {
27
+ try {
28
+ return !!uni
29
+ } catch (e) {
30
+ return false
31
+ }
32
+ }
33
+
34
+ function isIndexedDbSupported () {
35
+ return typeof window !== 'undefined' && !isUniRuntime() && !!window.indexedDB
36
+ }
37
+
38
+ function getLogSize (item) {
39
+ return item && item.size ? item.size : JSON.stringify(item || {}).length
40
+ }
41
+
42
+ function getLogByteSize (item) {
43
+ const value = JSON.stringify(item || {})
44
+ if (typeof Blob !== 'undefined') {
45
+ return new Blob([value]).size
46
+ }
47
+ return unescape(encodeURIComponent(value)).length
48
+ }
49
+
50
+ function normalizeLogStore (store) {
51
+ let nextStore = store
52
+ if (!nextStore) {
53
+ nextStore = {
54
+ size: 0,
55
+ data: []
56
+ }
57
+ }
58
+ if (Array.isArray(nextStore)) {
59
+ let totalSize = 0
60
+ nextStore = {
61
+ data: nextStore.map(item => {
62
+ if (item && !item.size) {
63
+ item.size = JSON.stringify(item).length
64
+ }
65
+ totalSize += getLogSize(item)
66
+ return item
67
+ })
68
+ }
69
+ nextStore.size = totalSize
70
+ }
71
+ if (!Array.isArray(nextStore.data)) {
72
+ nextStore.data = []
73
+ }
74
+ if (!nextStore.size) {
75
+ nextStore.size = nextStore.data.reduce((totalSize, item) => {
76
+ return totalSize + getLogSize(item)
77
+ }, 0)
78
+ }
79
+ return nextStore
80
+ }
81
+
82
+ function parseLogStoreValue (value) {
83
+ if (!value) {
84
+ return null
85
+ }
86
+ if (typeof value === 'string') {
87
+ value = parse(value)
88
+ }
89
+ return normalizeLogStore(value)
90
+ }
91
+
92
+ function readLegacyStorage (storageKey) {
93
+ if (isUniRuntime()) {
94
+ return uni.getStorageSync(storageKey)
95
+ }
96
+ if (typeof window !== 'undefined' && window.localStorage) {
97
+ return window.localStorage.getItem(storageKey)
98
+ }
99
+ return null
100
+ }
101
+
102
+ function writeLegacyStorage (storageKey, value) {
103
+ if (isUniRuntime()) {
104
+ uni.setStorageSync(storageKey, value)
105
+ return
106
+ }
107
+ if (typeof window !== 'undefined' && window.localStorage) {
108
+ window.localStorage.setItem(storageKey, typeof value === 'string' ? value : JSON.stringify(value))
109
+ }
110
+ }
111
+
112
+ function removeLegacyStorage (storageKey) {
113
+ if (isUniRuntime()) {
114
+ uni.removeStorageSync(storageKey)
115
+ return
116
+ }
117
+ if (typeof window !== 'undefined' && window.localStorage) {
118
+ window.localStorage.removeItem(storageKey)
119
+ }
120
+ }
121
+
122
+ function hashString (value) {
123
+ let hash = 2166136261
124
+ for (let index = 0; index < value.length; index++) {
125
+ hash ^= value.charCodeAt(index)
126
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)
127
+ }
128
+ return (hash >>> 0).toString(16)
129
+ }
130
+
131
+ function getLogRecordId (log) {
132
+ if (log && log.id !== undefined && log.id !== null) {
133
+ return `id:${log.id}`
134
+ }
135
+ const value = JSON.stringify(log || {})
136
+ return `legacy:${hashString(value)}:${value.length}`
137
+ }
138
+
139
+ function createIndexedDbStores (db) {
140
+ const logStore = db.createObjectStore(INDEXED_DB_STORE_NAME, { keyPath: 'recordId' })
141
+ logStore.createIndex(INDEXED_DB_ORDER_INDEX_NAME, 'order', { unique: false })
142
+ if (!db.objectStoreNames.contains(INDEXED_DB_META_STORE_NAME)) {
143
+ db.createObjectStore(INDEXED_DB_META_STORE_NAME, { keyPath: 'key' })
144
+ }
145
+ return logStore
146
+ }
147
+
148
+ function createIndexedDbRecord (log, order) {
149
+ return {
150
+ recordId: getLogRecordId(log),
151
+ order,
152
+ byteSize: getLogByteSize(log),
153
+ log
154
+ }
155
+ }
156
+
157
+ function uniqueLogs (logs) {
158
+ const recordIds = Object.create(null)
159
+ return logs.filter(log => {
160
+ const recordId = getLogRecordId(log)
161
+ if (recordIds[recordId]) {
162
+ return false
163
+ }
164
+ recordIds[recordId] = true
165
+ return true
166
+ })
167
+ }
168
+
169
+ function trimRecordsToIndexedDbLimit (records) {
170
+ let totalBytes = 0
171
+ const retainedRecords = []
172
+ records.some(record => {
173
+ if (record.byteSize + totalBytes > INDEXED_DB_MAX_SIZE) {
174
+ return true
175
+ }
176
+ retainedRecords.push(record)
177
+ totalBytes += record.byteSize
178
+ return false
179
+ })
180
+ return {
181
+ records: retainedRecords,
182
+ totalBytes,
183
+ removedCount: records.length - retainedRecords.length
184
+ }
49
185
  }
50
186
 
51
187
  export default class Log {
52
188
 
53
- prefix = 'log'
54
-
55
- name = null
56
-
57
- tempStore = []
58
-
59
- beforeAddCallback = null
60
-
61
- constructor (options) {
62
- options = Object.assign({
63
- name: null,
64
- console: false, // 是否控制台打印
65
- maxSize: 1024 * 1024 * 3 // 最多存3M的日志数据
66
- }, options)
67
- if (!options.name) {
68
- throw new Error('必须指定日志的name名称')
69
- }
70
- if (LogManage.list.some(v => v.name === options.name)) {
71
- throw new Error('已存在同名的日志实例')
72
- }
73
- this.options = options
74
- this.maxSize = options.maxSize
75
- this.name = options.name
76
- this.validTime = options.validTime
77
- }
78
-
79
- get storageKey () {
80
- return this.prefix + '_' + this.name
81
- }
82
-
83
- onBeforeAdd (callback) {
84
- this.beforeAddCallback = callback
85
- }
86
-
87
- /**
88
- * 新增日志到内存
89
- */
90
- addLog (data) {
91
- data = Object.assign({
92
- group: this.defautGroup,
93
- refererId: undefined, // 可以关联一个日志
94
- content: null
95
- }, data, {
96
- createTime: formatDate(new Date())
97
- })
98
- data.id = unique(data.group)
99
- if (this.beforeAddCallback && this.beforeAddCallback({
100
- data
101
- }) === false) {
102
- return
103
- }
104
- const size = JSON.stringify(data).length
105
- data.size = size
106
- this.tempStore.push(data)
107
- if (this.tempStore.length > 5) {
108
- this.publishLogData().then(null, err => {
109
- console.log(err)
189
+ static checkAndSync (storageKeys) {
190
+ const keys = [].concat(storageKeys || []).filter((storageKey, index, list) => {
191
+ return typeof storageKey === 'string' && storageKey && list.indexOf(storageKey) === index
192
+ })
193
+ return Promise.all(keys.map(storageKey => {
194
+ const log = new Log({ name: storageKey })
195
+ log._customStorageKey = storageKey
196
+ return log.checkAndSync().then(result => {
197
+ return log._closeIndexedDb().then(() => {
198
+ return Object.assign({ storageKey }, result)
199
+ })
110
200
  })
111
- }
112
- if (this.options.console) {
113
- console.log(`【${this.name || 'operateLog'}】${JSON.stringify(data) || '--'}`)
201
+ }))
202
+ }
203
+
204
+ constructor (options) {
205
+ this.prefix = 'log'
206
+ this.name = null
207
+ this.tempStore = []
208
+ this.beforeAddCallback = null
209
+ this._writePromise = Promise.resolve()
210
+ this._indexedDbPromise = null
211
+ this._syncPromise = null
212
+ this._syncCompleted = false
213
+ this._customStorageKey = null
214
+ this._publishTimer = null
215
+ options = Object.assign({
216
+ name: null,
217
+ console: false, // 是否控制台打印
218
+ maxSize: 1024 * 1024 * 3 // 非网页版本默认最多存3M日志数据
219
+ }, options)
220
+ if (!options.name) {
221
+ throw new Error('必须指定日志的name名称')
114
222
  }
115
- return data
116
- }
117
-
118
- /**
119
- * 清空本地日志
120
- */
121
- clearLogData () {
122
- return new Promise((resolve) => {
123
- this.tempStore = []
124
- if (typeof uni !== 'undefined') {
125
- uni.removeStorageSync(this.storageKey)
126
- } else {
127
- window.localStorage.removeItem(this.storageKey)
128
- }
129
- resolve()
130
- })
131
- }
223
+ this.options = options
224
+ this.maxSize = options.maxSize
225
+ this.name = options.name
226
+ this.validTime = options.validTime
227
+ }
228
+
229
+ get storageKey () {
230
+ return this._customStorageKey || this.prefix + '_' + this.name
231
+ }
232
+
233
+ get useIndexedDb () {
234
+ return isIndexedDbSupported()
235
+ }
132
236
 
133
- loadLogData () {
237
+ get indexedDbName () {
238
+ return this.storageKey
239
+ }
240
+
241
+ onBeforeAdd (callback) {
242
+ this.beforeAddCallback = callback
243
+ }
244
+
245
+ _enqueueWrite (callback) {
246
+ const task = this._writePromise.then(callback, callback)
247
+ this._writePromise = task.catch(() => {})
248
+ return task
249
+ }
250
+
251
+ _clearPublishTimer () {
252
+ if (!this._publishTimer) {
253
+ return
254
+ }
255
+ clearTimeout(this._publishTimer)
256
+ this._publishTimer = null
257
+ }
258
+
259
+ _schedulePublish () {
260
+ this._clearPublishTimer()
261
+ this._publishTimer = setTimeout(() => {
262
+ this._publishTimer = null
263
+ this.publishLogData().catch(() => {})
264
+ }, AUTO_PUBLISH_DELAY)
265
+ }
266
+
267
+ _closeIndexedDb () {
268
+ const indexedDbPromise = this._indexedDbPromise
269
+ this._indexedDbPromise = null
270
+ if (!indexedDbPromise) {
271
+ return Promise.resolve()
272
+ }
273
+ return indexedDbPromise.then(db => {
274
+ db.close()
275
+ }, () => {})
276
+ }
277
+
278
+ _upgradeIndexedDb (request) {
279
+ const db = request.result
280
+ const transaction = request.transaction
281
+ if (!db.objectStoreNames.contains(INDEXED_DB_STORE_NAME)) {
282
+ createIndexedDbStores(db)
283
+ transaction.objectStore(INDEXED_DB_META_STORE_NAME).put({
284
+ key: INDEXED_DB_STATS_KEY,
285
+ totalBytes: 0,
286
+ nextOrder: 0
287
+ })
288
+ return
289
+ }
290
+
291
+ const legacyStore = transaction.objectStore(INDEXED_DB_STORE_NAME)
292
+ const legacyRequest = legacyStore.get(this.storageKey)
293
+ legacyRequest.onerror = () => {
294
+ transaction.abort()
295
+ }
296
+ legacyRequest.onsuccess = () => {
297
+ let legacyLogStore = null
298
+ try {
299
+ const result = legacyRequest.result
300
+ legacyLogStore = result && result.value ? parseLogStoreValue(result.value) : null
301
+ } catch (e) {
302
+ legacyLogStore = null
303
+ }
304
+ db.deleteObjectStore(INDEXED_DB_STORE_NAME)
305
+ if (db.objectStoreNames.contains(INDEXED_DB_META_STORE_NAME)) {
306
+ db.deleteObjectStore(INDEXED_DB_META_STORE_NAME)
307
+ }
308
+ const logStore = createIndexedDbStores(db)
309
+ const logs = uniqueLogs(legacyLogStore ? legacyLogStore.data : [])
310
+ const records = logs.map((log, index) => createIndexedDbRecord(log, logs.length - index))
311
+ const result = trimRecordsToIndexedDbLimit(records)
312
+ result.records.forEach(record => logStore.put(record))
313
+ transaction.objectStore(INDEXED_DB_META_STORE_NAME).put({
314
+ key: INDEXED_DB_STATS_KEY,
315
+ totalBytes: result.totalBytes,
316
+ nextOrder: logs.length
317
+ })
318
+ }
319
+ }
320
+
321
+ openIndexedDb () {
322
+ if (!this.useIndexedDb) {
323
+ return Promise.reject(new Error('indexedDB不可用'))
324
+ }
325
+ if (this._indexedDbPromise) {
326
+ return this._indexedDbPromise
327
+ }
328
+ this._indexedDbPromise = new Promise((resolve, reject) => {
329
+ const request = window.indexedDB.open(this.indexedDbName, INDEXED_DB_VERSION)
330
+ request.onerror = () => {
331
+ this._indexedDbPromise = null
332
+ reject(request.error || new Error('打开日志数据库失败'))
333
+ }
334
+ request.onupgradeneeded = () => {
335
+ this._upgradeIndexedDb(request)
336
+ }
337
+ request.onsuccess = () => {
338
+ const db = request.result
339
+ db.onversionchange = () => {
340
+ db.close()
341
+ this._indexedDbPromise = null
342
+ }
343
+ resolve(db)
344
+ }
345
+ })
346
+ return this._indexedDbPromise
347
+ }
348
+
349
+ _readIndexedDbLogData () {
350
+ return this.openIndexedDb().then(db => {
134
351
  return new Promise((resolve, reject) => {
135
- if (this._lastLoadLogDataRaf) {
136
- this._lastLoadLogDataRaf.cancelFn()
352
+ const transaction = db.transaction([INDEXED_DB_STORE_NAME], 'readonly')
353
+ const store = transaction.objectStore(INDEXED_DB_STORE_NAME)
354
+ const request = store.index(INDEXED_DB_ORDER_INDEX_NAME).openCursor(null, 'prev')
355
+ const data = []
356
+ let size = 0
357
+ request.onerror = () => {
358
+ reject(request.error || new Error('读取日志失败'))
137
359
  }
138
- let rafId = null
139
- this._lastLoadLogDataRaf = {
140
- cancelFn: () => {
141
- cancelAnimationFrame(rafId)
142
- this._lastLoadLogDataRaf = null
143
- reject('cancel')
144
- },
145
- execFn: () => {
146
- rafId = requestAnimationFrame(() => {
147
- let localTempStore
148
- if (typeof uni !== 'undefined') {
149
- localTempStore = uni.getStorageSync(this.storageKey)
150
- } else {
151
- localTempStore = window.localStorage.getItem(this.storageKey)
152
- }
153
- if (localTempStore) {
154
- localTempStore = parse(localTempStore)
155
- if (Array.isArray(localTempStore)) {
156
- let totalSize = 0
157
- localTempStore = {
158
- data: localTempStore.map(v => {
159
- if (!v.size) {
160
- v.size = JSON.stringify(v).length
161
- }
162
- totalSize += v.size
163
- return v
164
- })
165
- }
166
- localTempStore.size = totalSize
167
- }
168
- }
169
- if (!localTempStore) {
170
- localTempStore = {
171
- size: 0,
172
- data: []
173
- }
174
- }
175
- // 自清理一下
176
- if (this._lastLoadLogDataRaf) {
177
- cancelAnimationFrame(this._lastLoadLogDataRaf)
178
- this._lastLoadLogDataRaf = null
179
- }
180
- resolve(localTempStore)
181
- })
360
+ request.onsuccess = event => {
361
+ const cursor = event.target.result
362
+ if (!cursor) {
363
+ resolve({ size, data })
364
+ return
182
365
  }
366
+ data.push(cursor.value.log)
367
+ size += getLogSize(cursor.value.log)
368
+ cursor.continue()
183
369
  }
184
- this._lastLoadLogDataRaf.execFn()
185
370
  })
186
- }
371
+ })
372
+ }
187
373
 
188
- publishLogData () {
189
- return new Promise((resolve, reject) => {
190
- if (this._lastPublishLogDataRaf) {
191
- this._lastPublishLogDataRaf.cancelFn()
374
+ _writeIndexedDbLogs (logs) {
375
+ if (!logs.length) {
376
+ return Promise.resolve({ migratedCount: 0, duplicateCount: 0, removedCount: 0 })
377
+ }
378
+ const uniqueLogList = uniqueLogs(logs)
379
+ const repeatedInputCount = logs.length - uniqueLogList.length
380
+ return this.openIndexedDb().then(db => {
381
+ return new Promise((resolve, reject) => {
382
+ const transaction = db.transaction(
383
+ [INDEXED_DB_STORE_NAME, INDEXED_DB_META_STORE_NAME],
384
+ 'readwrite'
385
+ )
386
+ const store = transaction.objectStore(INDEXED_DB_STORE_NAME)
387
+ const metaStore = transaction.objectStore(INDEXED_DB_META_STORE_NAME)
388
+ const metaRequest = metaStore.get(INDEXED_DB_STATS_KEY)
389
+ let result = null
390
+ transaction.oncomplete = () => {
391
+ resolve(result || { migratedCount: 0, duplicateCount: 0, removedCount: 0 })
392
+ }
393
+ transaction.onerror = () => {
394
+ reject(transaction.error || new Error('写入日志失败'))
395
+ }
396
+ transaction.onabort = () => {
397
+ reject(transaction.error || new Error('写入日志失败'))
398
+ }
399
+ metaRequest.onsuccess = () => {
400
+ const stats = metaRequest.result || {
401
+ key: INDEXED_DB_STATS_KEY,
402
+ totalBytes: 0,
403
+ nextOrder: 0
192
404
  }
193
- let rafId = null
194
- this._lastPublishLogDataRaf = {
195
- cancelFn: () => {
196
- cancelAnimationFrame(rafId)
197
- this._lastPublishLogDataRaf = null
198
- reject('cancel')
199
- },
200
- execFn: () => {
201
- if (!this.tempStore || !this.tempStore.length) {
202
- return resolve()
405
+ const records = uniqueLogList.map((log, index) => {
406
+ return createIndexedDbRecord(log, stats.nextOrder + uniqueLogList.length - index)
407
+ })
408
+ let pendingCount = records.length
409
+ let duplicateCount = repeatedInputCount
410
+ records.forEach(record => {
411
+ const getRequest = store.get(record.recordId)
412
+ getRequest.onsuccess = () => {
413
+ if (getRequest.result) {
414
+ stats.totalBytes -= getRequest.result.byteSize || 0
415
+ duplicateCount++
416
+ }
417
+ stats.totalBytes += record.byteSize
418
+ store.put(record)
419
+ pendingCount--
420
+ if (!pendingCount) {
421
+ stats.nextOrder += uniqueLogList.length
422
+ trimOldestLogs(stats, duplicateCount)
203
423
  }
204
- rafId = requestAnimationFrame(() => {
205
- this.loadLogData().then((localTempStore) => {
206
- const allData = [].concat(this.tempStore, localTempStore.data)
207
- const nextLocalStore = {
208
- size: 0,
209
- data: []
210
- }
211
- allData.some((item, index) => {
212
- if (item.size + nextLocalStore.size >= this.maxSize) {
213
- return true
214
- }
215
- nextLocalStore.data.push(item)
216
- nextLocalStore.size = nextLocalStore.size + item.size
217
- })
218
- if (typeof uni !== 'undefined') {
219
- uni.setStorageSync(this.storageKey, nextLocalStore)
220
- } else {
221
- window.localStorage.setItem(this.storageKey, JSON.stringify(nextLocalStore))
222
- }
223
- this.tempStore = []
224
- // 自清理一下
225
- if (this._lastPublishLogDataRaf) {
226
- cancelAnimationFrame(this._lastPublishLogDataRaf)
227
- this._lastPublishLogDataRaf = null
228
- }
229
- resolve()
230
- }, reject)
231
- })
424
+ }
425
+ })
426
+
427
+ function trimOldestLogs (nextStats, nextDuplicateCount) {
428
+ let removedCount = 0
429
+ const cursorRequest = store.index(INDEXED_DB_ORDER_INDEX_NAME).openCursor(null, 'next')
430
+ cursorRequest.onsuccess = event => {
431
+ const cursor = event.target.result
432
+ if (nextStats.totalBytes <= INDEXED_DB_MAX_SIZE || !cursor) {
433
+ metaStore.put(nextStats)
434
+ result = {
435
+ migratedCount: logs.length - nextDuplicateCount,
436
+ duplicateCount: nextDuplicateCount,
437
+ removedCount
438
+ }
439
+ return
440
+ }
441
+ nextStats.totalBytes -= cursor.value.byteSize || 0
442
+ cursor.delete()
443
+ removedCount++
444
+ cursor.continue()
232
445
  }
233
446
  }
234
- this._lastPublishLogDataRaf.execFn()
447
+ }
448
+ })
449
+ })
450
+ }
451
+
452
+ _writeLegacyLogs (logs) {
453
+ const localLogStore = parseLogStoreValue(readLegacyStorage(this.storageKey)) || {
454
+ size: 0,
455
+ data: []
456
+ }
457
+ const allData = [].concat(logs, localLogStore.data)
458
+ const nextLocalStore = {
459
+ size: 0,
460
+ data: []
461
+ }
462
+ allData.some(item => {
463
+ const itemSize = getLogSize(item)
464
+ if (itemSize + nextLocalStore.size >= this.maxSize) {
465
+ return true
466
+ }
467
+ nextLocalStore.data.push(item)
468
+ nextLocalStore.size += itemSize
469
+ return false
470
+ })
471
+ writeLegacyStorage(this.storageKey, nextLocalStore)
472
+ }
473
+
474
+ checkAndSync () {
475
+ if (!this.useIndexedDb || this._syncCompleted) {
476
+ return Promise.resolve({
477
+ synced: false,
478
+ migratedCount: 0,
479
+ duplicateCount: 0,
480
+ removedCount: 0
481
+ })
482
+ }
483
+ if (this._syncPromise) {
484
+ return this._syncPromise
485
+ }
486
+ const task = this._enqueueWrite(() => {
487
+ const legacyLogStore = parseLogStoreValue(readLegacyStorage(this.storageKey))
488
+ if (!legacyLogStore || !legacyLogStore.data.length) {
489
+ return {
490
+ synced: false,
491
+ migratedCount: 0,
492
+ duplicateCount: 0,
493
+ removedCount: 0
494
+ }
495
+ }
496
+ return this._writeIndexedDbLogs(legacyLogStore.data).then(result => {
497
+ removeLegacyStorage(this.storageKey)
498
+ return Object.assign({ synced: true }, result)
499
+ })
500
+ })
501
+ this._syncPromise = task.then(result => {
502
+ this._syncPromise = null
503
+ this._syncCompleted = true
504
+ return result
505
+ }, () => {
506
+ this._syncPromise = null
507
+ this._syncCompleted = true
508
+ return {
509
+ synced: false,
510
+ migratedCount: 0,
511
+ duplicateCount: 0,
512
+ removedCount: 0
513
+ }
514
+ })
515
+ return this._syncPromise
516
+ }
517
+
518
+ /**
519
+ * 新增日志到内存
520
+ */
521
+ addLog (data) {
522
+ data = Object.assign({
523
+ group: this.defautGroup,
524
+ refererId: undefined, // 可以关联一个日志
525
+ content: null
526
+ }, data, {
527
+ createTime: formatDate(new Date())
528
+ })
529
+ data.id = unique(data.group)
530
+ if (this.beforeAddCallback && this.beforeAddCallback({ data }) === false) {
531
+ return
532
+ }
533
+ data.size = JSON.stringify(data).length
534
+ this.tempStore.push(data)
535
+ this.checkAndSync().catch(() => {})
536
+ this._schedulePublish()
537
+ if (this.tempStore.length > 5) {
538
+ this.publishLogData().then(null, err => {
539
+ console.log(err)
540
+ })
541
+ }
542
+ if (this.options.console) {
543
+ console.log(`【${this.name || 'operateLog'}】${JSON.stringify(data) || '--'}`)
544
+ }
545
+ return data
546
+ }
547
+
548
+ /**
549
+ * 清空本地日志
550
+ */
551
+ clearLogData () {
552
+ this._clearPublishTimer()
553
+ this.tempStore = []
554
+ const task = this._enqueueWrite(() => {
555
+ if (!this.useIndexedDb) {
556
+ removeLegacyStorage(this.storageKey)
557
+ return
558
+ }
559
+ return this.openIndexedDb().then(db => {
560
+ return new Promise((resolve, reject) => {
561
+ const transaction = db.transaction(
562
+ [INDEXED_DB_STORE_NAME, INDEXED_DB_META_STORE_NAME],
563
+ 'readwrite'
564
+ )
565
+ transaction.objectStore(INDEXED_DB_STORE_NAME).clear()
566
+ transaction.objectStore(INDEXED_DB_META_STORE_NAME).put({
567
+ key: INDEXED_DB_STATS_KEY,
568
+ totalBytes: 0,
569
+ nextOrder: 0
570
+ })
571
+ transaction.oncomplete = resolve
572
+ transaction.onerror = () => reject(transaction.error || new Error('清理日志失败'))
573
+ transaction.onabort = () => reject(transaction.error || new Error('清理日志失败'))
574
+ })
575
+ }).then(() => {
576
+ removeLegacyStorage(this.storageKey)
577
+ })
578
+ })
579
+ return task.catch(() => {
580
+ removeLegacyStorage(this.storageKey)
581
+ })
582
+ }
583
+
584
+ loadLogData () {
585
+ return this.checkAndSync().then(() => this._writePromise).then(() => {
586
+ if (this.useIndexedDb) {
587
+ return this._readIndexedDbLogData().catch(() => ({ size: 0, data: [] }))
588
+ }
589
+ return parseLogStoreValue(readLegacyStorage(this.storageKey)) || {
590
+ size: 0,
591
+ data: []
592
+ }
593
+ })
594
+ }
595
+
596
+ publishLogData () {
597
+ this._clearPublishTimer()
598
+ return this.checkAndSync().then(() => {
599
+ return this._enqueueWrite(() => {
600
+ if (!this.tempStore.length) {
601
+ return
602
+ }
603
+ const flushingTempStore = this.tempStore.slice()
604
+ this.tempStore = []
605
+ let writeTask
606
+ if (this.useIndexedDb) {
607
+ writeTask = this._writeIndexedDbLogs(flushingTempStore).catch(() => {})
608
+ } else {
609
+ writeTask = Promise.resolve().then(() => this._writeLegacyLogs(flushingTempStore))
610
+ }
611
+ return writeTask.catch(error => {
612
+ this.tempStore = flushingTempStore.concat(this.tempStore)
613
+ throw error
235
614
  })
236
- }
237
-
238
- exportLogData () {
239
- return this.publishLogData().then(() => {
240
- return this.loadLogData()
241
- })
242
- }
243
- }
615
+ })
616
+ })
617
+ }
618
+
619
+ exportLogData () {
620
+ return this.checkAndSync().then(() => {
621
+ return this.publishLogData()
622
+ }).then(() => {
623
+ return this.loadLogData()
624
+ })
625
+ }
626
+ }