@subiz/widget-v4 4.1.210 → 4.1.212
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/README.md +21 -2
- package/assets/img/dot_chat.svg +3 -0
- package/assets/less/chatbox.less +9 -0
- package/assets/less/form.less +10 -0
- package/assets/less/message.less +114 -13
- package/assets/less/widget.less +15 -18
- package/compiled_src/call/css.js +1 -1
- package/compiled_src/chatbox/Buttons.js +7 -4
- package/compiled_src/chatbox/ChatButton.js +127 -29
- package/compiled_src/chatbox/Event.js +37 -5
- package/compiled_src/chatbox/Events.js +348 -129
- package/compiled_src/chatbox/FormAttachment.js +1 -1
- package/compiled_src/chatbox/Header.js +7 -2
- package/compiled_src/chatbox/InfoMessage.js +1 -1
- package/compiled_src/chatbox/MessageInput.js +24 -9
- package/compiled_src/chatbox/OrderAttachments.js +13 -13
- package/compiled_src/chatbox/QuoteMessage.js +3 -2
- package/compiled_src/chatbox/TextMessage.js +22 -18
- package/compiled_src/chatbox/Widget.js +200 -129
- package/compiled_src/chatbox/block.js +39 -29
- package/compiled_src/chatbox/conversation-history.js +44 -0
- package/compiled_src/chatbox/event-layout.js +59 -0
- package/compiled_src/chatbox/greeting.js +32 -0
- package/compiled_src/chatbox/main.js +3 -0
- package/compiled_src/common.js +13 -9
- package/compiled_src/form/css.js +1 -1
- package/compiled_src/lib.js +22 -32
- package/compiled_src/notif/css.js +1 -1
- package/compiled_src/plugin.js +11 -18
- package/compiled_src/popup/css.js +1 -1
- package/compiled_src/popup/popup.js +1 -1
- package/compiled_src/store/api.js +0 -2
- package/compiled_src/store/api_fake.js +1 -1
- package/compiled_src/store/attachments.js +28 -0
- package/compiled_src/store/chain.js +24 -10
- package/compiled_src/store/kv.js +9 -13
- package/compiled_src/store/lsdb.js +1 -1
- package/compiled_src/store/store.js +356 -117
- package/compiled_src/util/common.js +48 -41
- package/compiled_src/util/event.js +15 -0
- package/compiled_src/util/pubsub.js +28 -13
- package/compiled_src/util/url.js +18 -0
- package/dist/127.js +13 -13
- package/dist/132.js +1 -1
- package/dist/1a0f6f3ff1a.127.js +1 -0
- package/dist/{1a0d80f6e72.132.js → 1a0f6f3ff1a.132.js} +1 -1
- package/dist/{1a0d80f6e72.27.js → 1a0f6f3ff1a.27.js} +1 -1
- package/dist/1a0f6f3ff1a.278.js +1 -0
- package/dist/{1a0d80f6e72.339.js → 1a0f6f3ff1a.339.js} +1 -1
- package/dist/1a0f6f3ff1a.459.js +1 -0
- package/dist/1a0f6f3ff1a.app.js +1 -0
- package/dist/{1a0d80f6e72.chat.html → 1a0f6f3ff1a.chat.html} +1 -1
- package/dist/1a0f6f3ff1a.chat.js +1 -0
- package/dist/{1a0d80f6e72.form.html → 1a0f6f3ff1a.form.html} +1 -1
- package/dist/1a0f6f3ff1a.form.js +1 -0
- package/dist/{1a0d80f6e72.index.html → 1a0f6f3ff1a.index.html} +1 -1
- package/dist/{1a0d80f6e72.library.html → 1a0f6f3ff1a.library.html} +1 -1
- package/dist/1a0f6f3ff1a.library.js +1 -0
- package/dist/{1a0d80f6e72.preview.html → 1a0f6f3ff1a.preview.html} +1 -1
- package/dist/1a0f6f3ff1a.preview.js +1 -0
- package/dist/1a0f6f3ff1a.webview.js +1 -0
- package/dist/27.js +2 -2
- package/dist/459.js +2 -2
- package/dist/HASH +1 -1
- package/dist/assets/img/dot_chat.335fd8a.svg +3 -0
- package/dist/screenshoot.js +1523 -619
- package/package.json +12 -5
- package/src/chatbox/Buttons.js +5 -4
- package/src/chatbox/ChatButton.js +133 -39
- package/src/chatbox/Event.js +38 -5
- package/src/chatbox/Events.js +306 -134
- package/src/chatbox/FormAttachment.js +1 -1
- package/src/chatbox/Header.js +5 -4
- package/src/chatbox/InfoMessage.js +1 -1
- package/src/chatbox/MessageInput.js +15 -10
- package/src/chatbox/OrderAttachments.js +13 -13
- package/src/chatbox/QuoteMessage.js +3 -2
- package/src/chatbox/TextMessage.js +21 -20
- package/src/chatbox/Widget.js +150 -150
- package/src/chatbox/block.js +29 -27
- package/src/chatbox/conversation-history.js +37 -0
- package/src/chatbox/event-layout.js +72 -0
- package/src/chatbox/greeting.js +13 -0
- package/src/chatbox/main.js +3 -0
- package/src/common.js +15 -9
- package/src/lib.js +22 -35
- package/src/plugin.js +11 -13
- package/src/popup/popup.js +1 -1
- package/src/store/api.js +0 -2
- package/src/store/api_fake.js +1 -1
- package/src/store/attachments.js +21 -0
- package/src/store/chain.js +25 -12
- package/src/store/kv.js +10 -19
- package/src/store/lsdb.js +1 -1
- package/src/store/store.js +313 -130
- package/src/util/common.js +48 -43
- package/src/util/event.js +7 -0
- package/src/util/pubsub.js +27 -8
- package/src/util/url.js +14 -0
- package/test.cjs +59 -0
- package/assets/less/contact.less +0 -126
- package/compiled_src/util/common.test.js +0 -414
- package/compiled_src/util/event.test.js +0 -38
- package/compiled_src/util/extractor.test.js +0 -299
- package/compiled_src/util/extractor.test2.js +0 -61
- package/compiled_src/util/gate.test.js +0 -18
- package/compiled_src/util/pubsub.test.js +0 -35
- package/dist/1a0d80f6e72.127.js +0 -1
- package/dist/1a0d80f6e72.278.js +0 -1
- package/dist/1a0d80f6e72.459.js +0 -1
- package/dist/1a0d80f6e72.74.js +0 -1
- package/dist/1a0d80f6e72.997.js +0 -1
- package/dist/1a0d80f6e72.app.js +0 -1
- package/dist/1a0d80f6e72.chat.js +0 -1
- package/dist/1a0d80f6e72.form.js +0 -1
- package/dist/1a0d80f6e72.library.js +0 -1
- package/dist/1a0d80f6e72.preview.js +0 -1
- package/dist/1a0d80f6e72.webview.js +0 -1
- package/dist/74.js +0 -376
- package/dist/997.js +0 -37
- package/src/contact/contact.js +0 -400
- package/src/contact/css.js +0 -1
- package/src/util/common.test.js +0 -301
- package/src/util/event.test.js +0 -31
- package/src/util/extractor.test.js +0 -240
- package/src/util/extractor.test2.js +0 -56
- package/src/util/gate.test.js +0 -20
- package/src/util/pubsub.test.js +0 -37
- /package/dist/{1a0d80f6e72.185.js → 1a0f6f3ff1a.185.js} +0 -0
- /package/dist/{1a0d80f6e72.490.js → 1a0f6f3ff1a.490.js} +0 -0
package/src/store/store.js
CHANGED
|
@@ -6,6 +6,7 @@ import Chain from './chain.js'
|
|
|
6
6
|
import KV from './kv.js'
|
|
7
7
|
import APIFake from './api_fake.js'
|
|
8
8
|
import RealtimeFake from './realtime_fake.js'
|
|
9
|
+
import {uploadAttachments} from './attachments.js'
|
|
9
10
|
var Realtime = require('@subiz/wsclient')
|
|
10
11
|
var cookie = require('cookie_js')
|
|
11
12
|
cookie.defaults.path = '/'
|
|
@@ -15,8 +16,6 @@ let memlocalStorage = require('./inmem_localstorage.js')
|
|
|
15
16
|
const MAX_CONVO = 20
|
|
16
17
|
const page_loaded = sb.Date.now()
|
|
17
18
|
|
|
18
|
-
const DEBUG_ACCOUNT_IDS = ['acqsulrowbxiugvginhw', 'acryguzgwxmamhzmqlyx'] // acc test and minvoice
|
|
19
|
-
|
|
20
19
|
// available localstorage key mask, user, hasManuallyClosed, isopen, close_preview_at, lastview, bot_run_logs, plugin_run_logs
|
|
21
20
|
export default class Store {
|
|
22
21
|
inmem = false
|
|
@@ -27,7 +26,6 @@ export default class Store {
|
|
|
27
26
|
this.CONNECTION_CHECK_INTERVAL = 3000
|
|
28
27
|
this.reconnectTimestamp = 0
|
|
29
28
|
this.g_sync = {}
|
|
30
|
-
this.agent_rt_message_sent_dict = {}
|
|
31
29
|
this._die = false
|
|
32
30
|
this.forceDevice = null
|
|
33
31
|
this.forcePlugins = {}
|
|
@@ -67,15 +65,22 @@ export default class Store {
|
|
|
67
65
|
// dont dupplicate seen
|
|
68
66
|
this.g_haveSentSeenM = {}
|
|
69
67
|
this.last_focused = this.now() // 0 -> in focus, timestamp -> lost focus at
|
|
70
|
-
|
|
68
|
+
this._onBlur = () => {
|
|
69
|
+
if (this._die) return
|
|
71
70
|
this.last_focused = this.now()
|
|
72
71
|
this.sendPresence()
|
|
73
|
-
}
|
|
74
|
-
|
|
72
|
+
}
|
|
73
|
+
this._onFocus = () => {
|
|
74
|
+
if (this._die) return
|
|
75
75
|
this.last_focused = 0 //
|
|
76
76
|
this.sendPresence()
|
|
77
|
-
}
|
|
78
|
-
|
|
77
|
+
}
|
|
78
|
+
this._onBeforeUnload = () => {
|
|
79
|
+
if (!this._die) this.api.sendLostFocus()
|
|
80
|
+
}
|
|
81
|
+
window.addEventListener('blur', this._onBlur)
|
|
82
|
+
window.addEventListener('focus', this._onFocus)
|
|
83
|
+
window.addEventListener('beforeunload', this._onBeforeUnload)
|
|
79
84
|
this._lastPresenceURL = window.location.href
|
|
80
85
|
this._urlCheckInterval = sb.setInterval(() => {
|
|
81
86
|
if (this._die) return sb.clearInterval(this._urlCheckInterval)
|
|
@@ -110,7 +115,7 @@ export default class Store {
|
|
|
110
115
|
this.sendPresence()
|
|
111
116
|
}, 60000) // dont bother delete this
|
|
112
117
|
|
|
113
|
-
onVisibilityChange((val) => {
|
|
118
|
+
this._visibilityCleanup = onVisibilityChange((val) => {
|
|
114
119
|
if (this._die) return
|
|
115
120
|
this.browserVisibility = val
|
|
116
121
|
this.sendPresence()
|
|
@@ -120,12 +125,13 @@ export default class Store {
|
|
|
120
125
|
this.device = this.getDeviceInfo()
|
|
121
126
|
|
|
122
127
|
// try to get Google Analytics tracking id and client id by periodically checking it after an interval
|
|
123
|
-
|
|
128
|
+
this._gaInterval = sb.setInterval(() => {
|
|
129
|
+
if (this._die) return
|
|
124
130
|
let user = this.cache.match('user') || {}
|
|
125
131
|
if (!user.id) return // store not init
|
|
126
132
|
var dev = this.getDeviceInfo()
|
|
127
133
|
if (!dev.ga_tracking_ids || !dev.ga_client_id) return
|
|
128
|
-
sb.clearInterval(_gaInterval)
|
|
134
|
+
sb.clearInterval(this._gaInterval)
|
|
129
135
|
this.device = dev
|
|
130
136
|
|
|
131
137
|
// update gclient id
|
|
@@ -154,9 +160,30 @@ export default class Store {
|
|
|
154
160
|
console.log('publish connectionStatus', this.connectionStatus)
|
|
155
161
|
this.pubsub.publish('connection')
|
|
156
162
|
}
|
|
163
|
+
if (this.connectionStatus === 'active' && (this._needsMessageSync || currentStatus !== 'active')) {
|
|
164
|
+
this._needsMessageSync = false
|
|
165
|
+
this.syncKnownConvoEvents()
|
|
166
|
+
}
|
|
157
167
|
}
|
|
158
168
|
}
|
|
159
169
|
|
|
170
|
+
syncKnownConvoEvents() {
|
|
171
|
+
if (this._die) return Promise.resolve()
|
|
172
|
+
if (this.knownConvoSyncRequest) return this.knownConvoSyncRequest
|
|
173
|
+
const chain = this.chain
|
|
174
|
+
const request = (async () => {
|
|
175
|
+
const convos = sb.filter(this.cache.all(), (value, key) => key.startsWith('convo_'))
|
|
176
|
+
for (const convo of convos) {
|
|
177
|
+
if (this._die || chain !== this.chain) break
|
|
178
|
+
await this.syncConvoEvents(convo.id)
|
|
179
|
+
}
|
|
180
|
+
})().finally(() => {
|
|
181
|
+
if (this.knownConvoSyncRequest === request) this.knownConvoSyncRequest = null
|
|
182
|
+
})
|
|
183
|
+
this.knownConvoSyncRequest = request
|
|
184
|
+
return request
|
|
185
|
+
}
|
|
186
|
+
|
|
160
187
|
getConnectionStatus = () => this.connectionStatus
|
|
161
188
|
|
|
162
189
|
// inmem
|
|
@@ -342,7 +369,13 @@ export default class Store {
|
|
|
342
369
|
async _touchAcc() {
|
|
343
370
|
if (this.isInsync('account')) return
|
|
344
371
|
await this.readyGate.entry()
|
|
345
|
-
if (!this.isInsync('account')) this.
|
|
372
|
+
if (!this.isInsync('account')) this.refreshAccount()
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
refreshAccount() {
|
|
376
|
+
if (this._die || sb.Date.now() < (this.accountRefreshAfter || 0)) return
|
|
377
|
+
this.accountRefreshAfter = sb.Date.now() + 30_000
|
|
378
|
+
return this.reconnect()
|
|
346
379
|
}
|
|
347
380
|
|
|
348
381
|
// must ensure only call this function after init is done
|
|
@@ -426,13 +459,18 @@ export default class Store {
|
|
|
426
459
|
await this.readyGate.entry()
|
|
427
460
|
await this.reconnectGate.entry(undefined, true)
|
|
428
461
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
462
|
+
try {
|
|
463
|
+
if (this._die || this.isInsync(cid)) return
|
|
464
|
+
this.convoFetchAfter = this.convoFetchAfter || {}
|
|
465
|
+
if (sb.Date.now() < (this.convoFetchAfter[cid] || 0)) return
|
|
466
|
+
this.convoFetchAfter[cid] = sb.Date.now() + 15_000
|
|
467
|
+
let {body: convo, error} = await this.api.getConversation(cid)
|
|
468
|
+
if (convo && !error) this._setConvo(convo)
|
|
469
|
+
} catch (error) {
|
|
470
|
+
return {error}
|
|
471
|
+
} finally {
|
|
472
|
+
this.reconnectGate.open()
|
|
473
|
+
}
|
|
436
474
|
}
|
|
437
475
|
|
|
438
476
|
list_provinces = () => this.api.list_provinces()
|
|
@@ -447,28 +485,111 @@ export default class Store {
|
|
|
447
485
|
return this.chain.listM(id)
|
|
448
486
|
}
|
|
449
487
|
|
|
450
|
-
fetchMoreEvents =
|
|
451
|
-
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
488
|
+
fetchMoreEvents = (cid, limit = 150) => {
|
|
489
|
+
this.historyRequests = this.historyRequests || new Map()
|
|
490
|
+
if (this.historyRequests.has(cid)) return this.historyRequests.get(cid)
|
|
491
|
+
this.historyFailures = this.historyFailures || {}
|
|
492
|
+
const failure = this.historyFailures[cid]
|
|
493
|
+
if (failure && sb.Date.now() < failure.retryAt) return Promise.resolve({error: failure.error})
|
|
494
|
+
const request = this._fetchMoreEvents(cid, limit).then(result => {
|
|
495
|
+
if (result.error) this.historyFailures[cid] = {error: result.error, retryAt: sb.Date.now() + 15_000}
|
|
496
|
+
else delete this.historyFailures[cid]
|
|
497
|
+
return result
|
|
498
|
+
}).finally(() => {
|
|
499
|
+
if (this.historyRequests.get(cid) === request) this.historyRequests.delete(cid)
|
|
500
|
+
})
|
|
501
|
+
this.historyRequests.set(cid, request)
|
|
502
|
+
return request
|
|
503
|
+
}
|
|
461
504
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
this.
|
|
505
|
+
async _fetchMoreEvents(convoid, limit = 150) {
|
|
506
|
+
if (!convoid || convoid.startsWith('cslight')) return {}
|
|
507
|
+
await this.readyGate.entry()
|
|
508
|
+
await this.reconnectGate.entry(undefined, true)
|
|
509
|
+
try {
|
|
510
|
+
if (this._die) return dieErr
|
|
511
|
+
const refresh = !this.isInsync('events' + convoid)
|
|
512
|
+
if (refresh) {
|
|
513
|
+
this.chain.anchorCache[convoid] = ''
|
|
514
|
+
delete this.outofmsgM[convoid]
|
|
515
|
+
}
|
|
516
|
+
const out = await this._doFetchMsg(convoid, limit)
|
|
517
|
+
if (!out.error) this.markSync('events' + convoid)
|
|
468
518
|
return out
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return {error}
|
|
521
|
+
} finally {
|
|
522
|
+
this.reconnectGate.open()
|
|
469
523
|
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Fetch from the newest page independently of the older-history cursor.
|
|
527
|
+
// A realtime arrival must not advance this watermark: it could hide a gap.
|
|
528
|
+
syncConvoEvents = (cid) => {
|
|
529
|
+
if (this._die || !cid || cid.startsWith('cslight')) return Promise.resolve({})
|
|
530
|
+
this.messageSyncRequests = this.messageSyncRequests || new Map()
|
|
531
|
+
if (this.messageSyncRequests.has(cid)) return this.messageSyncRequests.get(cid)
|
|
532
|
+
this.messageSyncNextAt = this.messageSyncNextAt || {}
|
|
533
|
+
if (sb.Date.now() < (this.messageSyncNextAt[cid] || 0)) return Promise.resolve({skipped: true})
|
|
534
|
+
const chain = this.chain
|
|
535
|
+
const request = this._syncConvoEvents(cid, chain).then(result => {
|
|
536
|
+
if (chain === this.chain && !this._die) {
|
|
537
|
+
this.messageSyncFailures = this.messageSyncFailures || {}
|
|
538
|
+
const failures = result.error ? Math.min((this.messageSyncFailures[cid] || 0) + 1, 4) : 0
|
|
539
|
+
this.messageSyncFailures[cid] = failures
|
|
540
|
+
this.messageSyncNextAt[cid] = sb.Date.now() + (failures ? 15_000 * 2 ** (failures - 1) : 15_000)
|
|
541
|
+
}
|
|
542
|
+
return result
|
|
543
|
+
}).finally(() => {
|
|
544
|
+
if (this.messageSyncRequests.get(cid) === request) this.messageSyncRequests.delete(cid)
|
|
545
|
+
})
|
|
546
|
+
this.messageSyncRequests.set(cid, request)
|
|
547
|
+
return request
|
|
548
|
+
}
|
|
470
549
|
|
|
471
|
-
|
|
550
|
+
async _syncConvoEvents(cid, chain) {
|
|
551
|
+
await this.readyGate.entry()
|
|
552
|
+
await this.reconnectGate.entry(undefined, true)
|
|
553
|
+
try {
|
|
554
|
+
if (this._die || chain !== this.chain) return dieErr
|
|
555
|
+
this.messageSyncTimes = this.messageSyncTimes || {}
|
|
556
|
+
this.messageSyncProgress = this.messageSyncProgress || {}
|
|
557
|
+
let progress = this.messageSyncProgress[cid]
|
|
558
|
+
if (!progress) {
|
|
559
|
+
const cached = Object.values(chain.listM(cid))
|
|
560
|
+
const boundary = this.messageSyncTimes[cid] || cached.reduce((min, ev) => Math.min(min, sb.getMs(ev.created)), Infinity)
|
|
561
|
+
progress = this.messageSyncProgress[cid] = {boundary, anchor: '', newest: 0, visited: new Set()}
|
|
562
|
+
}
|
|
563
|
+
// Bound each batch; the next trigger resumes at the saved cursor.
|
|
564
|
+
for (let page = 0; page < 5; page++) {
|
|
565
|
+
const wait = (this.nextMessageSyncRequestAt || 0) - sb.Date.now()
|
|
566
|
+
if (wait > 0) await sb.sleep(wait)
|
|
567
|
+
if (this._die || chain !== this.chain) return dieErr
|
|
568
|
+
this.nextMessageSyncRequestAt = sb.Date.now() + 250
|
|
569
|
+
const [items, next, error, end] = await this.listmessageapi(cid, progress.anchor, 150)
|
|
570
|
+
if (this._die || chain !== this.chain) return dieErr
|
|
571
|
+
if (error) return {error}
|
|
572
|
+
chain.merge(cid, items)
|
|
573
|
+
for (const item of items) progress.newest = Math.max(progress.newest, item.index)
|
|
574
|
+
this.pubsub.publish('account')
|
|
575
|
+
if (end || !items.length || items.some(item => item.index < progress.boundary)) {
|
|
576
|
+
if (progress.newest) this.messageSyncTimes[cid] = progress.newest
|
|
577
|
+
delete this.messageSyncProgress[cid]
|
|
578
|
+
return {}
|
|
579
|
+
}
|
|
580
|
+
progress.visited.add(progress.anchor)
|
|
581
|
+
if (!next || progress.visited.has(next)) {
|
|
582
|
+
delete this.messageSyncProgress[cid]
|
|
583
|
+
return {error: 'message_cursor_stalled'}
|
|
584
|
+
}
|
|
585
|
+
progress.anchor = next
|
|
586
|
+
}
|
|
587
|
+
return {pending: true}
|
|
588
|
+
} catch (error) {
|
|
589
|
+
return {error}
|
|
590
|
+
} finally {
|
|
591
|
+
this.reconnectGate.open()
|
|
592
|
+
}
|
|
472
593
|
}
|
|
473
594
|
|
|
474
595
|
reloadConvoEvents = async (convoid) => {
|
|
@@ -483,8 +604,8 @@ export default class Store {
|
|
|
483
604
|
return {error}
|
|
484
605
|
}
|
|
485
606
|
|
|
486
|
-
_doFetchMsg = async (convoid) => {
|
|
487
|
-
let {error} = await this.chain.fetchMore(convoid,
|
|
607
|
+
_doFetchMsg = async (convoid, limit = 150) => {
|
|
608
|
+
let {error} = await this.chain.fetchMore(convoid, limit, async (type, anchor, limit) => {
|
|
488
609
|
if (this.outofmsgM[convoid]) return [[], '']
|
|
489
610
|
let [events, newanchor, error, outofmsg] = await this.listmessageapi(type, anchor, limit)
|
|
490
611
|
if (outofmsg) this.outofmsgM[convoid] = true
|
|
@@ -621,7 +742,6 @@ export default class Store {
|
|
|
621
742
|
|
|
622
743
|
async _initResource(accid, auth, realtimep) {
|
|
623
744
|
this.g_sync = {}
|
|
624
|
-
this._current_acc_id = accid
|
|
625
745
|
|
|
626
746
|
let user = await this.api.setAuth(auth)
|
|
627
747
|
if (typeof sbz_info != 'undefined') sbz_info.store_7 = true
|
|
@@ -635,6 +755,7 @@ export default class Store {
|
|
|
635
755
|
let lightConvos = this.kv.match('light_convos') || {}
|
|
636
756
|
this.lightConvos = {}
|
|
637
757
|
sb.map(lightConvos, (convo) => {
|
|
758
|
+
if (convo.light_type === 'widget' && (convo.account_id !== accid || convo.initial_by !== user.id)) return
|
|
638
759
|
let in7Day = convo.created > sb.Date.now() - 7 * 24 * 3_600_000
|
|
639
760
|
let events = sb.filter(convo.events, (ev) => ev.type === 'message_sent')
|
|
640
761
|
|
|
@@ -652,8 +773,10 @@ export default class Store {
|
|
|
652
773
|
|
|
653
774
|
this.realtime.onEvent(this.onWs.bind(this))
|
|
654
775
|
this.realtime.onInterrupted(async () => {
|
|
776
|
+
if (this._die) return
|
|
777
|
+
this._needsMessageSync = true
|
|
655
778
|
await this.reconnectWs(accid, user.id)
|
|
656
|
-
this.
|
|
779
|
+
await this.refreshAccount()
|
|
657
780
|
})
|
|
658
781
|
|
|
659
782
|
if (typeof sbz_info != 'undefined') sbz_info.store_8 = true
|
|
@@ -709,7 +832,7 @@ export default class Store {
|
|
|
709
832
|
if (this.g_haveSentSeenM[lastmsgsent.id]) return
|
|
710
833
|
|
|
711
834
|
// ignore sending message
|
|
712
|
-
if (lastmsgsent.id === lastmsgsent.
|
|
835
|
+
if (lastmsgsent.id === lastmsgsent.source_event_id) return
|
|
713
836
|
|
|
714
837
|
// fire own event so guest code can update unread number
|
|
715
838
|
this.pubsub.publish('events', {
|
|
@@ -765,40 +888,53 @@ export default class Store {
|
|
|
765
888
|
await this.readyGate.entry()
|
|
766
889
|
}
|
|
767
890
|
|
|
768
|
-
|
|
891
|
+
reconnect(p) {
|
|
892
|
+
if (this.reconnectRequest) return this.reconnectRequest
|
|
893
|
+
const request = this._reconnect(p).finally(() => {
|
|
894
|
+
if (this.reconnectRequest === request) this.reconnectRequest = null
|
|
895
|
+
})
|
|
896
|
+
this.reconnectRequest = request
|
|
897
|
+
return request
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async _reconnect(p) {
|
|
769
901
|
if (this._die) return
|
|
770
902
|
// make sure we only run reconnect once in a time
|
|
771
903
|
await this.reconnectGate.entry(undefined, true)
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
798
|
-
sb.map(convos, (convo) => this._setConvo(convo))
|
|
799
|
-
}
|
|
904
|
+
try {
|
|
905
|
+
|
|
906
|
+
let fetchuser = p && p.user ? Promise.resolve({body: p.user}) : this.api.getUser()
|
|
907
|
+
let outs = await Promise.all([fetchuser, this.api.getAccountSetting({bot_id: this.forceBotId})])
|
|
908
|
+
|
|
909
|
+
let error = sb.find(outs, (out) => out.error)
|
|
910
|
+
if (error) return error
|
|
911
|
+
|
|
912
|
+
let set = outs[1].body
|
|
913
|
+
set.user = outs[0].body
|
|
914
|
+
await this._setSetting(set)
|
|
915
|
+
// refetch convo
|
|
916
|
+
let convob = await this.api.listConversations()
|
|
917
|
+
|
|
918
|
+
error = convob.error
|
|
919
|
+
let body = convob.body
|
|
920
|
+
if (!error) {
|
|
921
|
+
let convos = sb.get(body, 'conversations', [])
|
|
922
|
+
// clear all old convo from two week
|
|
923
|
+
sb.map(this.cache.all(), (value, k) => {
|
|
924
|
+
if (k.startsWith('convo_')) {
|
|
925
|
+
if (this.now() - sb.getMs(sb.get(value, 'actived')) > 1209600000) this.cache.del(k)
|
|
926
|
+
}
|
|
927
|
+
})
|
|
928
|
+
sb.map(convos, (convo) => this._setConvo(convo))
|
|
929
|
+
}
|
|
800
930
|
|
|
801
|
-
|
|
931
|
+
if (error) return {error}
|
|
932
|
+
this.markSync('account')
|
|
933
|
+
} catch (error) {
|
|
934
|
+
return {error}
|
|
935
|
+
} finally {
|
|
936
|
+
this.reconnectGate.open()
|
|
937
|
+
}
|
|
802
938
|
}
|
|
803
939
|
|
|
804
940
|
async submitForm(formid, data, user) {
|
|
@@ -949,39 +1085,18 @@ export default class Store {
|
|
|
949
1085
|
matchTrafficSource = () => this.kv.match('traffic_source')
|
|
950
1086
|
|
|
951
1087
|
async onWs(ev) {
|
|
952
|
-
await this.wsGate.entry()
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
handleLogCompare(ev) {
|
|
963
|
-
if (!DEBUG_ACCOUNT_IDS.includes(this._current_acc_id)) return
|
|
964
|
-
if (ev.type !== 'message_sent') return
|
|
965
|
-
let byType = sb.get(ev, 'by.type')
|
|
966
|
-
if (byType === 'user') return
|
|
967
|
-
|
|
968
|
-
this.agent_rt_message_sent_dict[ev.id] = ev
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
sbzListConversations = () => {
|
|
972
|
-
return this.api.listConversations()
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
sbzListMessages = (cid) => {
|
|
976
|
-
return this.api.listMessages(cid, 100, '')
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
listAgentRealtimeMessageSent = () => {
|
|
980
|
-
return this.agent_rt_message_sent_dict
|
|
1088
|
+
await this.wsGate.entry(undefined, true)
|
|
1089
|
+
try {
|
|
1090
|
+
await this._onWs(ev)
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
console.error(error)
|
|
1093
|
+
return {error}
|
|
1094
|
+
} finally {
|
|
1095
|
+
this.wsGate.open()
|
|
1096
|
+
}
|
|
981
1097
|
}
|
|
982
1098
|
|
|
983
1099
|
async _onWs(ev) {
|
|
984
|
-
this.handleLogCompare(ev)
|
|
985
1100
|
if (this._die) return
|
|
986
1101
|
if (!ev || !ev.type) return
|
|
987
1102
|
ev.created = sb.getMs(ev.created)
|
|
@@ -1063,7 +1178,7 @@ export default class Store {
|
|
|
1063
1178
|
}
|
|
1064
1179
|
|
|
1065
1180
|
if (sb.startsWith(ev.type, 'message_') || sb.startsWith(ev.type, 'conversation_')) {
|
|
1066
|
-
this.restConvoEvent(ev)
|
|
1181
|
+
await this.restConvoEvent(ev)
|
|
1067
1182
|
if (ev.type === 'message_sent' && sb.get(ev, 'by.id') !== user.id) {
|
|
1068
1183
|
sendEventToThirdParty('SUBIZ Chat', 'Received', 'User')
|
|
1069
1184
|
}
|
|
@@ -1116,8 +1231,8 @@ export default class Store {
|
|
|
1116
1231
|
// dont store pong and typing
|
|
1117
1232
|
if (ev.type !== 'message_pong' && ev.type !== 'conversation_typing') {
|
|
1118
1233
|
if (ev.type === 'message_sent') {
|
|
1119
|
-
let
|
|
1120
|
-
let found = sb.find(this.listEvents(cid), (oldev) => oldev.id ===
|
|
1234
|
+
let sourceEventId = sb.get(ev, 'source_event_id')
|
|
1235
|
+
let found = sb.find(this.listEvents(cid), (oldev) => oldev.id === sourceEventId)
|
|
1121
1236
|
if (found) {
|
|
1122
1237
|
// keeping old created so the ui can be static
|
|
1123
1238
|
this.chain.del(cid, found.id)
|
|
@@ -1149,6 +1264,39 @@ export default class Store {
|
|
|
1149
1264
|
this.kv.put('light_convos', this.lightConvos)
|
|
1150
1265
|
}
|
|
1151
1266
|
|
|
1267
|
+
startWidgetConvo(greeting, locale) {
|
|
1268
|
+
const user = this.matchUser()
|
|
1269
|
+
const existing = sb.find(this.lightConvos, convo => convo.light_type === 'widget' && convo.account_id === user.account_id && convo.initial_by === user.id)
|
|
1270
|
+
if (existing) return {convo: existing}
|
|
1271
|
+
const id = 'cslightwidget_' + sb.randomString(24)
|
|
1272
|
+
const ev = sb.cloneDeep(greeting)
|
|
1273
|
+
ev.data.message.conversation_id = id
|
|
1274
|
+
const convo = {
|
|
1275
|
+
id, account_id: user.account_id, initial_by: user.id, locale,
|
|
1276
|
+
light_type: 'widget', state: 'pending', created: ev.created,
|
|
1277
|
+
members: [{id: user.id, type: 'user', seen_at: ev.created + 1}],
|
|
1278
|
+
events: [ev], last_message_sent: ev,
|
|
1279
|
+
touchpoint: {id, channel: 'subiz', source: 'web'},
|
|
1280
|
+
}
|
|
1281
|
+
this.lightConvos[id] = convo
|
|
1282
|
+
this.kv.put('light_convos', this.lightConvos)
|
|
1283
|
+
this.pubsub.publish('account')
|
|
1284
|
+
return {convo}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
promoteWidgetConvo(localId, cid) {
|
|
1288
|
+
// Keep the mounted timeline and its message keys across draft promotion.
|
|
1289
|
+
this.widgetConvoOrigins = this.widgetConvoOrigins || {}
|
|
1290
|
+
this.widgetConvoOrigins[cid] = localId
|
|
1291
|
+
const greeting = sb.cloneDeep(sb.get(this.lightConvos, [localId, 'events', 0]))
|
|
1292
|
+
if (greeting) {
|
|
1293
|
+
greeting.source_event_id = greeting.id
|
|
1294
|
+
greeting.data.message.conversation_id = cid
|
|
1295
|
+
this.chain.merge(cid, [{id: greeting.id, index: greeting.created, value: greeting}])
|
|
1296
|
+
}
|
|
1297
|
+
this.deleteLightConvo(localId)
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1152
1300
|
startLightConvo(botid, messages, locale, is_testing, botversion) {
|
|
1153
1301
|
let convoid = 'cslight' + botid + '_' + this.now()
|
|
1154
1302
|
let user = this.cache.match('user') || {}
|
|
@@ -1229,11 +1377,11 @@ export default class Store {
|
|
|
1229
1377
|
|
|
1230
1378
|
async sendConvoEvent(cid, type, data) {
|
|
1231
1379
|
await this.readyGate.entry()
|
|
1232
|
-
var
|
|
1380
|
+
var sourceEventId = sb.randomString(36)
|
|
1233
1381
|
let user = this.cache.match('user') || {}
|
|
1234
1382
|
|
|
1235
1383
|
var event = {
|
|
1236
|
-
id:
|
|
1384
|
+
id: sourceEventId,
|
|
1237
1385
|
by: {id: user.id, type: 'user', device: this.device},
|
|
1238
1386
|
data,
|
|
1239
1387
|
type,
|
|
@@ -1242,7 +1390,7 @@ export default class Store {
|
|
|
1242
1390
|
sb.convoToEv(cid, event)
|
|
1243
1391
|
let files = []
|
|
1244
1392
|
if (type === 'message_sent') {
|
|
1245
|
-
event.
|
|
1393
|
+
event.source_event_id = sourceEventId
|
|
1246
1394
|
event.data.message.conversation_id = cid
|
|
1247
1395
|
|
|
1248
1396
|
// pre upload image files
|
|
@@ -1263,14 +1411,9 @@ export default class Store {
|
|
|
1263
1411
|
await this.convoGate.entry()
|
|
1264
1412
|
// upload file first
|
|
1265
1413
|
|
|
1266
|
-
await
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
event._error_message = 'cannot_send_too_big_file'
|
|
1270
|
-
}
|
|
1271
|
-
event._error = error
|
|
1272
|
-
att.url = url
|
|
1273
|
-
})
|
|
1414
|
+
const uploaded = await uploadAttachments(files, (file) => this.api.uploadToS3(file))
|
|
1415
|
+
event._error = uploaded.error
|
|
1416
|
+
if (uploaded.errorMessage) event._error_message = uploaded.errorMessage
|
|
1274
1417
|
|
|
1275
1418
|
if (event._error) return this.onWs(event) // dont send error msg
|
|
1276
1419
|
if (cid && cid.startsWith('cslight')) return
|
|
@@ -1413,9 +1556,12 @@ export default class Store {
|
|
|
1413
1556
|
|
|
1414
1557
|
async listmessageapi(type, anchor, limit) {
|
|
1415
1558
|
let convoid = type
|
|
1416
|
-
if (convoid && convoid.startsWith('cslight'))
|
|
1559
|
+
if (convoid && convoid.startsWith('cslight')) {
|
|
1560
|
+
let events = sb.map(this.listEvents(convoid), (ev) => ({id: ev.id, index: ev.created, value: ev}))
|
|
1561
|
+
return [events, '', undefined, true]
|
|
1562
|
+
}
|
|
1417
1563
|
let {body: events, code, error} = await this.api.listMessages(convoid, -limit, anchor)
|
|
1418
|
-
if (error || code !== 200) return [[], anchor, error]
|
|
1564
|
+
if (error || code !== 200) return [[], anchor, error || 'message_fetch_failed']
|
|
1419
1565
|
anchor = sb.get(sb.last(events), 'id')
|
|
1420
1566
|
|
|
1421
1567
|
events = sb.map(events, (event) => {
|
|
@@ -1450,15 +1596,17 @@ export default class Store {
|
|
|
1450
1596
|
let startms = this.now()
|
|
1451
1597
|
|
|
1452
1598
|
// store not init
|
|
1453
|
-
if (!this.cache || !sb.get(this.cache.match('user'), 'id'))
|
|
1599
|
+
if (!this.cache || !sb.get(this.cache.match('user'), 'id'))
|
|
1600
|
+
return (this._timeTimeout = sb.setTimeout(() => this.loopTime(), 240000))
|
|
1454
1601
|
|
|
1455
1602
|
let {body: t, error} = await this.api.time()
|
|
1456
|
-
if (
|
|
1603
|
+
if (this._die) return
|
|
1604
|
+
if (error) return (this._timeTimeout = sb.setTimeout(() => this.loopTime(), 30000))
|
|
1457
1605
|
var latences = Math.abs(this.now() - startms) / 2
|
|
1458
1606
|
var serverMs = new sb.Date(t).getTime() + latences
|
|
1459
1607
|
var localMs = this.now()
|
|
1460
1608
|
this.timeDiff = Math.floor(serverMs - localMs)
|
|
1461
|
-
sb.setTimeout(() => this.loopTime(), 240000)
|
|
1609
|
+
this._timeTimeout = sb.setTimeout(() => this.loopTime(), 240000)
|
|
1462
1610
|
}
|
|
1463
1611
|
|
|
1464
1612
|
replaceFileUrl(src) {
|
|
@@ -1497,6 +1645,14 @@ export default class Store {
|
|
|
1497
1645
|
this.kv = new KV(new LSDB('sbz2023', this.inmem), 'kv')
|
|
1498
1646
|
if (this.chain) this.chain.destroy()
|
|
1499
1647
|
this.chain = new Chain(new LSDB('cbz2023', true), 'ch')
|
|
1648
|
+
this.outofmsgM = {}
|
|
1649
|
+
this.messageSyncTimes = {}
|
|
1650
|
+
this.messageSyncNextAt = {}
|
|
1651
|
+
this.messageSyncFailures = {}
|
|
1652
|
+
this.messageSyncProgress = {}
|
|
1653
|
+
this.accountRefreshAfter = 0
|
|
1654
|
+
this.historyFailures = {}
|
|
1655
|
+
this.convoFetchAfter = {}
|
|
1500
1656
|
if (this.realtime) this.realtime.stop()
|
|
1501
1657
|
if (this.cache) this.cache.destroy()
|
|
1502
1658
|
this.pubsub.publish('destroy')
|
|
@@ -1506,14 +1662,22 @@ export default class Store {
|
|
|
1506
1662
|
return this._die
|
|
1507
1663
|
}
|
|
1508
1664
|
destroy() {
|
|
1665
|
+
if (this._die) return
|
|
1509
1666
|
this._die = true
|
|
1510
1667
|
this.clearBrowserData()
|
|
1511
1668
|
sb.clearInterval(this._seenInterval)
|
|
1512
1669
|
sb.clearInterval(this._urlCheckInterval)
|
|
1670
|
+
sb.clearInterval(this._presenceInterval1)
|
|
1671
|
+
sb.clearInterval(this._presenceInterval2)
|
|
1672
|
+
sb.clearInterval(this._gaInterval)
|
|
1673
|
+
sb.clearTimeout(this._timeTimeout)
|
|
1513
1674
|
if (this.realtime) this.realtime.stop()
|
|
1514
|
-
this.readyGate.close()
|
|
1515
|
-
window.removeEventListener('blur', this.
|
|
1516
|
-
window.removeEventListener('focus', this.
|
|
1675
|
+
if (this.readyGate) this.readyGate.close()
|
|
1676
|
+
window.removeEventListener('blur', this._onBlur)
|
|
1677
|
+
window.removeEventListener('focus', this._onFocus)
|
|
1678
|
+
window.removeEventListener('beforeunload', this._onBeforeUnload)
|
|
1679
|
+
if (this._visibilityCleanup) this._visibilityCleanup()
|
|
1680
|
+
this.pubsub.clear()
|
|
1517
1681
|
}
|
|
1518
1682
|
}
|
|
1519
1683
|
|
|
@@ -1543,16 +1707,29 @@ const ImageTypes = ['image/gif', 'image/jpeg', 'image/png', 'image/svg+xml']
|
|
|
1543
1707
|
|
|
1544
1708
|
function onVisibilityChange(cb) {
|
|
1545
1709
|
var hidden = 'hidden'
|
|
1710
|
+
let target = document,
|
|
1711
|
+
eventName
|
|
1712
|
+
let previous = {}
|
|
1546
1713
|
|
|
1547
1714
|
// Standards:
|
|
1548
|
-
if (hidden in document)
|
|
1549
|
-
else if ((hidden = 'mozHidden') in document)
|
|
1550
|
-
else if ((hidden = 'webkitHidden') in document)
|
|
1551
|
-
else if ((hidden = 'msHidden') in document)
|
|
1715
|
+
if (hidden in document) eventName = 'visibilitychange'
|
|
1716
|
+
else if ((hidden = 'mozHidden') in document) eventName = 'mozvisibilitychange'
|
|
1717
|
+
else if ((hidden = 'webkitHidden') in document) eventName = 'webkitvisibilitychange'
|
|
1718
|
+
else if ((hidden = 'msHidden') in document) eventName = 'msvisibilitychange'
|
|
1552
1719
|
// IE 9 and lower:
|
|
1553
|
-
else if ('onfocusin' in document)
|
|
1720
|
+
else if ('onfocusin' in document) {
|
|
1721
|
+
previous = {onfocusin: document.onfocusin, onfocusout: document.onfocusout}
|
|
1722
|
+
document.onfocusin = document.onfocusout = onchange
|
|
1723
|
+
}
|
|
1554
1724
|
// All others:
|
|
1555
|
-
else
|
|
1725
|
+
else {
|
|
1726
|
+
target = window
|
|
1727
|
+
for (const key of ['onpageshow', 'onpagehide', 'onfocus', 'onblur']) {
|
|
1728
|
+
previous[key] = window[key]
|
|
1729
|
+
window[key] = onchange
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
if (eventName) target.addEventListener(eventName, onchange)
|
|
1556
1733
|
|
|
1557
1734
|
function onchange(evt) {
|
|
1558
1735
|
var v = 'visible'
|
|
@@ -1566,6 +1743,12 @@ function onVisibilityChange(cb) {
|
|
|
1566
1743
|
|
|
1567
1744
|
// set the initial state (but only if browser supports the Page Visibility API)
|
|
1568
1745
|
if (document[hidden] !== undefined) onchange({type: document[hidden] ? 'blur' : 'focus'})
|
|
1746
|
+
return () => {
|
|
1747
|
+
if (eventName) target.removeEventListener(eventName, onchange)
|
|
1748
|
+
for (const key of Object.keys(previous)) {
|
|
1749
|
+
if (target[key] === onchange) target[key] = previous[key]
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1569
1752
|
}
|
|
1570
1753
|
|
|
1571
1754
|
const inmemCookie = {
|