@subiz/widget-v4 4.1.155 → 4.1.157

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.
Files changed (42) hide show
  1. package/compiled_src/bot.js +9 -0
  2. package/compiled_src/chatbox/AgentsAvatar.js +7 -6
  3. package/compiled_src/chatbox/ChatButton.js +26 -15
  4. package/compiled_src/chatbox/Widget.js +41 -0
  5. package/compiled_src/store/kv.js +0 -2
  6. package/compiled_src/store/store.js +101 -13
  7. package/dist/19888eff7f5.app.js +1 -0
  8. package/dist/{19816b2e8ca.chat.html → 19888eff7f5.chat.html} +1 -1
  9. package/dist/19888eff7f5.chat.js +1 -0
  10. package/dist/{19816b2e8ca.form.html → 19888eff7f5.form.html} +1 -1
  11. package/dist/19888eff7f5.form.js +1 -0
  12. package/dist/{19816b2e8ca.index.html → 19888eff7f5.index.html} +1 -1
  13. package/dist/{19816b2e8ca.library.html → 19888eff7f5.library.html} +1 -1
  14. package/dist/19888eff7f5.library.js +1 -0
  15. package/dist/{19816b2e8ca.preview.html → 19888eff7f5.preview.html} +1 -1
  16. package/dist/19888eff7f5.preview.js +1 -0
  17. package/dist/19888eff7f5.webview.js +1 -0
  18. package/dist/HASH +1 -1
  19. package/dist/screenshoot.js +226 -34
  20. package/package.json +3 -3
  21. package/src/bot.js +8 -1
  22. package/src/chatbox/AgentsAvatar.js +30 -12
  23. package/src/chatbox/ChatButton.js +31 -18
  24. package/src/chatbox/Widget.js +27 -0
  25. package/src/store/kv.js +11 -14
  26. package/src/store/store.js +92 -17
  27. package/dist/19816b2e8ca.app.js +0 -1
  28. package/dist/19816b2e8ca.chat.js +0 -1
  29. package/dist/19816b2e8ca.form.js +0 -1
  30. package/dist/19816b2e8ca.library.js +0 -1
  31. package/dist/19816b2e8ca.preview.js +0 -1
  32. package/dist/19816b2e8ca.webview.js +0 -1
  33. /package/dist/{19816b2e8ca.127.js → 19888eff7f5.127.js} +0 -0
  34. /package/dist/{19816b2e8ca.132.js → 19888eff7f5.132.js} +0 -0
  35. /package/dist/{19816b2e8ca.185.js → 19888eff7f5.185.js} +0 -0
  36. /package/dist/{19816b2e8ca.27.js → 19888eff7f5.27.js} +0 -0
  37. /package/dist/{19816b2e8ca.278.js → 19888eff7f5.278.js} +0 -0
  38. /package/dist/{19816b2e8ca.339.js → 19888eff7f5.339.js} +0 -0
  39. /package/dist/{19816b2e8ca.459.js → 19888eff7f5.459.js} +0 -0
  40. /package/dist/{19816b2e8ca.490.js → 19888eff7f5.490.js} +0 -0
  41. /package/dist/{19816b2e8ca.74.js → 19888eff7f5.74.js} +0 -0
  42. /package/dist/{19816b2e8ca.997.js → 19888eff7f5.997.js} +0 -0
@@ -22,7 +22,7 @@ export default class ChatButton extends Com {
22
22
  if (sb.get(ev, 'type') !== 'message_sent') return
23
23
 
24
24
  let bytype = sb.get(ev, 'by.type')
25
- if (bytype !== 'system' && bytype !== 'agent') return
25
+ if (bytype !== 'system' && bytype !== 'agent' && bytype !== 'bot') return
26
26
 
27
27
  let isquickreplies = false
28
28
  sb.map(sb.get(ev, 'data.message.attachments', []), (att) => {
@@ -106,25 +106,38 @@ export default class ChatButton extends Com {
106
106
  this.setState({displayPreview: false})
107
107
  }
108
108
 
109
- // this method could be call from the parent
110
- ShowGreeting() {
111
- // workaround for bao chau elect dont want show greeting in mobile
112
- if (this.props.store.isMobile()) {
113
- let accid = sb.get(this.props.plugin, 'chatbox.account_id', {})
114
- if (accid === 'acqjeefqeaoiagyfprap') return
115
- }
116
- let greeting = sb.get(this.props.plugin, 'chatbox.greeting', {})
117
- let agentM = this.props.store.matchAgent('*')
118
- let byid = greeting.agent_id
119
- if (!byid) byid = randomAgent(agentM).id
120
-
121
- let text = this.props.t.i18n(sb.get(greeting, 'greeting', {})) || greeting.message
109
+ pickBestGreeting() {
110
+ let convoM = this.props.store.matchConvo('*')
111
+ let unseenConvo = sb
112
+ .filter(convoM, (convo) => {
113
+ let seen = sb.isConvoSeen(convo, this.props.store.matchUser().id)
114
+ return !seen
115
+ })
116
+ .sort((a, b) => sb.get(b, 'last_message_sent.created', 0) - sb.get(a, 'last_message_sent.created', 0))
117
+ if (sb.size(unseenConvo) == 0) {
118
+ // workaround for bao chau elect dont want show greeting in mobile
119
+ if (this.props.store.isMobile()) {
120
+ let accid = sb.get(this.props.plugin, 'chatbox.account_id', {})
121
+ if (accid === 'acqjeefqeaoiagyfprap') return
122
+ }
123
+ let greeting = sb.get(this.props.plugin, 'chatbox.greeting', {})
124
+ let agentM = this.props.store.matchAgent('*')
125
+ let byid = greeting.agent_id
126
+ if (!byid) byid = randomAgent(agentM).id
122
127
 
123
- this.setState({
124
- previewEvent: {
128
+ let text = this.props.t.i18n(sb.get(greeting, 'greeting', {})) || greeting.message
129
+ return {
125
130
  by: {id: byid, type: 'agent'},
126
131
  data: {message: {text, format: 'plaintext'}},
127
- },
132
+ }
133
+ }
134
+ return unseenConvo[0].last_message_sent
135
+ }
136
+
137
+ // this method could be call from the parent
138
+ ShowGreeting() {
139
+ this.setState({
140
+ previewEvent: this.pickBestGreeting(),
128
141
  displayPreview: true,
129
142
  })
130
143
  }
@@ -195,7 +208,7 @@ export default class ChatButton extends Com {
195
208
  if (!agent) agent = this.props.store.matchAgent('*')[byid] || {}
196
209
  agent = sb.cloneDeep(agent)
197
210
  agent.fullname = agent.fullname || sb.get(account, 'name') || this.props.t('agent')
198
- agent.avatar_url = agent.avatar_url || defaultAvatar
211
+ agent.avatar_url = agent.avatar_url || account.logo_url || require('../../assets/img/image_default.png')
199
212
  let position = sb.get(accountSetting, 'desktop_button.position') || 'right'
200
213
  if (this.props.store.isMobile()) {
201
214
  position = sb.get(accountSetting, 'mobile_button.position') || position
@@ -776,6 +776,33 @@ export default class Widget extends Com {
776
776
  }
777
777
 
778
778
  let text = message.text
779
+
780
+ if (this.props.cid && this.props.cid.startsWith('cslight')) {
781
+ let cid = this.props.cid
782
+ this._creating = (this._creating || 0) + 1
783
+ let _creating = this._creating
784
+ this.setState({creating: true})
785
+ let botid = cid.split('_')[0].slice(7) // 6 = len(cslight)
786
+ const {convo, error} = await this.props.store.startConvo({
787
+ locale: this.props.t.locale(),
788
+ message: text,
789
+ members: [{type: 'bot', id: botid}],
790
+ initial_by: this.props.store.matchUser().id,
791
+ })
792
+ if (_creating != this._creating) return // outdated
793
+ if (error) {
794
+ this.setState({creating: false})
795
+ return
796
+ // show error and try again
797
+ }
798
+ this.props.store.deleteLightConvo(cid)
799
+ await this.props.store.sendConvoEvent(convo.id, 'message_sent', {message})
800
+ if (_creating != this._creating) return // outdated
801
+ this.props.onGo({view: 'chat', cid: convo.id})
802
+ this.setState({creating: false})
803
+ return
804
+ }
805
+
779
806
  if (!this.props.cid) {
780
807
  this._creating = (this._creating || 0) + 1
781
808
  let _creating = this._creating
package/src/store/kv.js CHANGED
@@ -1,7 +1,7 @@
1
1
  let KvMaxSize = 500
2
2
  // A key-value database with a cache layer
3
3
  export default class {
4
- constructor (db, schema) {
4
+ constructor(db, schema) {
5
5
  this.name = schema
6
6
  schema += '_kv'
7
7
  this.dead = false
@@ -12,23 +12,22 @@ export default class {
12
12
  this.init()
13
13
  }
14
14
 
15
- refreshCache () {
15
+ refreshCache() {
16
16
  if (this.dead) return
17
17
  let inmemItemM = {}
18
18
  let allitems = this.db.all('items')
19
- allitems.forEach(function(item) {
19
+ allitems.forEach(function (item) {
20
20
  inmemItemM[item.key] = item.value
21
21
  })
22
22
  this.cache = inmemItemM
23
23
  }
24
24
 
25
- init () {
25
+ init() {
26
26
  if (this.initialized) return
27
27
  let inmemItemM = {}
28
28
 
29
- console.time('KV' + this.name)
30
29
  let allitems = this.db.all('items')
31
- allitems.forEach(function(item) {
30
+ allitems.forEach(function (item) {
32
31
  inmemItemM[item.key] = item.value
33
32
  })
34
33
  this.cache = inmemItemM
@@ -51,20 +50,18 @@ export default class {
51
50
  }
52
51
 
53
52
  this.db.removes('items', [removekeys])
54
-
55
53
  }
56
54
 
57
55
  this.initialized = true
58
- console.timeEnd('KV' + this.name)
59
56
  }
60
57
 
61
- all () {
58
+ all() {
62
59
  if (this.dead) return []
63
60
  if (!this.initialized) return []
64
61
  return this.cache
65
62
  }
66
63
 
67
- match (key) {
64
+ match(key) {
68
65
  if (this.dead || !this.initialized) return
69
66
  if (!key) return
70
67
  return this.cache[key]
@@ -74,24 +71,24 @@ export default class {
74
71
  // put('1', {id: '1', name: 'thanh'})
75
72
  // put({id: '1', name: 'thanh'}, 'id')
76
73
  // put([{id: '1', name: 'thanh'},{id: '2', name: 'van'}], 'id')
77
- put (key, value) {
74
+ put(key, value) {
78
75
  if (!key) return
79
76
  if (this.dead) return []
80
77
  if (!this.initialized) throw new Error('uninitialized')
81
78
  // if multiple key
82
79
  this.cache[key] = value
83
- this.db.put('items', key, { key, value })
80
+ this.db.put('items', key, {key, value})
84
81
  }
85
82
 
86
83
  // delete an record in a schema
87
- del (key) {
84
+ del(key) {
88
85
  if (this.dead) return
89
86
  if (!this.initialized) throw new Error('uninitialized')
90
87
  delete this.cache[key]
91
88
  this.db.removes('items', [key])
92
89
  }
93
90
 
94
- destroy () {
91
+ destroy() {
95
92
  // clear all data
96
93
  this.db.clear('items')
97
94
 
@@ -35,6 +35,7 @@ export default class Store {
35
35
  this.FileURL = FileURL
36
36
  this._track_page_view = true
37
37
  this.device_type = 'desktop'
38
+ this.lightConvos = {} // convo that have not been created, for faking bot conversation
38
39
  if (this.fake) this.api = new APIFake(ApiURL, FileURL)
39
40
  else this.api = new API(ApiURL, FileURL)
40
41
 
@@ -63,9 +64,9 @@ export default class Store {
63
64
 
64
65
  // dont dupplicate seen
65
66
  this.g_haveSentSeenM = {}
66
- this.last_focused = sb.Date.now() // 0 -> in focus, timestamp -> lost focus at
67
+ this.last_focused = this.now() // 0 -> in focus, timestamp -> lost focus at
67
68
  window.addEventListener('blur', () => {
68
- this.last_focused = sb.Date.now()
69
+ this.last_focused = this.now()
69
70
  this.sendPresence()
70
71
  })
71
72
  window.addEventListener('focus', () => {
@@ -93,7 +94,7 @@ export default class Store {
93
94
  this._presenceInterval2 = sb.setInterval(() => {
94
95
  if (this._die) return sb.clearInterval(this._presenceInterval2)
95
96
  if (this.browserVisibility === 'visible' && document.hasFocus()) return
96
- if (this.last_focused && sb.Date.now() - this.last_focused > 30 * 60000) {
97
+ if (this.last_focused && this.now() - this.last_focused > 30 * 60000) {
97
98
  // lost focus for more than 30 min
98
99
  return // dont send presence
99
100
  }
@@ -347,20 +348,23 @@ export default class Store {
347
348
  return agentM[id]
348
349
  }
349
350
 
350
- // must ensure only call this function after init is done
351
351
  matchConvo = (id) => {
352
352
  if (!id) return
353
353
 
354
354
  if (id === '*') {
355
355
  this._touchAcc()
356
- return sb.filter(this.cache.all(), (value, k) => k.startsWith('convo_'))
356
+ // combind with
357
+ return sb.filter(this.cache.all(), (value, k) => k.startsWith('convo_')).concat(sb.map(this.lightConvos))
357
358
  }
358
359
 
360
+ if (id.startsWith('cslight')) return this.lightConvos[id]
359
361
  if (!this.isInsync(id)) this.fetchConvo(id)
360
362
  return this.cache.match('convo_' + id)
361
363
  }
362
364
 
363
365
  fetchConvo = async (cid) => {
366
+ if (!cid) return
367
+ if (cid.startsWith('cslight')) return
364
368
  if (this._die) return dieErr
365
369
  await this.readyGate.entry()
366
370
  await this.reconnectGate.entry(undefined, true)
@@ -382,10 +386,13 @@ export default class Store {
382
386
 
383
387
  listEvents = (id) => {
384
388
  if (!id || id === '*' || typeof id !== 'string') return
389
+ if (id.startsWith('cslight')) return sb.get(this.lightConvos, [id, 'events'], [])
385
390
  return this.chain.listM(id)
386
391
  }
387
392
 
388
393
  fetchMoreEvents = async (convoid) => {
394
+ if (!convoid) return
395
+ if (convoid.startsWith('cslight')) return [[], '']
389
396
  if (!this.isInsync('events' + convoid)) {
390
397
  await this.readyGate.entry()
391
398
  await this.reconnectGate.entry(undefined, true)
@@ -535,6 +542,7 @@ export default class Store {
535
542
  this.reconnect()
536
543
  })
537
544
 
545
+ this.lightConvos = this.kv.match('light_convos') || {}
538
546
  this.seenLoop()
539
547
  await this.reconnect({user: user}) // already fetched
540
548
  this.readyGate.open()
@@ -603,26 +611,36 @@ export default class Store {
603
611
  // ignore sending message
604
612
  if (lastmsgsent.id === lastmsgsent.data.message.idempotency_key) return
605
613
 
606
- // send seen
607
- this.api.markAsSeen(id, lastmsgsent.id)
608
-
609
614
  // fire own event so guest code can update unread number
610
615
  this.pubsub.publish('events', {
611
- created: sb.Date.now(),
616
+ created: this.now(),
612
617
  type: 'message_pong',
613
618
  by: {type: 'user'},
614
619
  data: {
615
620
  message: {
616
621
  conversation_id: id,
617
622
  id: '*',
618
- pongs: [{type: 'seen', created: sb.Date.now()}],
623
+ pongs: [{type: 'seen', created: this.now()}],
619
624
  },
620
625
  },
621
626
  })
627
+ if (id.startsWith('cslight')) {
628
+ let old = this.lightConvos[id]
629
+ let user = this.matchUser()
630
+ if (!old) return
631
+ let mem = sb.find(old.members, (mem) => mem.type == 'user' && mem.id == user.id)
632
+ mem.seen_at = this.now()
633
+ this.kv.put('light_convos', this.lightConvos)
634
+ this.g_haveSentSeenM[lastmsgsent.id] = true
635
+ return
636
+ }
637
+
638
+ // send seen
639
+ this.api.markAsSeen(id, lastmsgsent.id)
622
640
 
623
641
  let convo = this.cache.match('convo_' + id) || {}
624
642
  let mem = sb.find(convo.members, (m) => m.id === user.id)
625
- if (mem) mem.seen_at = sb.Date.now()
643
+ if (mem) mem.seen_at = this.now()
626
644
  this._setConvo(convo)
627
645
  this.g_haveSentSeenM[lastmsgsent.id] = true
628
646
  })
@@ -838,7 +856,7 @@ export default class Store {
838
856
  let convo
839
857
 
840
858
  let alreadyFetched = false
841
- if (cid) {
859
+ if (cid && !cid.startsWith('cslight')) {
842
860
  convo = this.cache.match('convo_' + cid)
843
861
  if (!convo || !convo.id) {
844
862
  let {body: apiconvo, error} = await this.api.getConversation(cid)
@@ -849,6 +867,16 @@ export default class Store {
849
867
  }
850
868
  }
851
869
 
870
+ if (cid.startsWith('cslight')) {
871
+ convo = this.lightConvos[cid]
872
+ if (!convo) return
873
+ convo.events.push(ev)
874
+ convo.last_message_sent = ev
875
+ this.kv.put('light_convos', this.lightConvos)
876
+ this.pubsub.publish('events', ev)
877
+ return
878
+ }
879
+
852
880
  if (!convo) return
853
881
 
854
882
  // ignore wrong channel
@@ -962,11 +990,54 @@ export default class Store {
962
990
 
963
991
  typing(cid) {
964
992
  if (!cid) return
993
+ if (cid.startsWith('cslight')) return
965
994
  this._typing(cid)
966
995
  }
967
996
 
968
997
  _typing = sb.throttle((convoid) => this.api.sendTyping(convoid), 3000, {trailing: false})
969
998
 
999
+ deleteLightConvo(id) {
1000
+ delete this.lightConvos[id]
1001
+ this.kv.put('light_convos', this.lightConvos)
1002
+ }
1003
+
1004
+ startLightConvo(botid, message, locale) {
1005
+ let convoid = 'cslight' + botid + '_' + this.now()
1006
+ let user = this.cache.match('user') || {}
1007
+
1008
+ let old = this.lightConvos[convoid]
1009
+ if (old) return {convo: old}
1010
+ let now = this.now()
1011
+ let ev = {}
1012
+ ev.created = now
1013
+ ev.event_time = now
1014
+ ev.id = 'evfake' + now
1015
+ ev.type = 'message_sent'
1016
+ ev.by = {id: botid, type: 'bot'}
1017
+ ev.data = {message: message}
1018
+ ev.data.message.conversation_id = convoid
1019
+ let newconvo = {
1020
+ id: convoid,
1021
+ locale: locale,
1022
+ initial_by: botid,
1023
+ created: now,
1024
+ actived: now,
1025
+ last_message_sent: ev,
1026
+ members: [
1027
+ {type: 'user', id: user.id},
1028
+ {type: 'bot', id: botid, membership: 'active'},
1029
+ ],
1030
+ state: 'active',
1031
+ touchpoint: {id: convoid, channel: 'subiz', source: 'web'},
1032
+ traffic_source: 'direct',
1033
+ events: [ev],
1034
+ }
1035
+ this.lightConvos[convoid] = newconvo
1036
+ this.kv.put('light_convos', this.lightConvos)
1037
+ this.pubsub.publish('events', ev)
1038
+ return {convo: newconvo}
1039
+ }
1040
+
970
1041
  async startConvo(req) {
971
1042
  if (!req) req = {}
972
1043
  await this.readyGate.entry()
@@ -990,17 +1061,18 @@ export default class Store {
990
1061
  }
991
1062
 
992
1063
  createPostback(cid, postback) {
1064
+ if (cid && cid.startsWith('cslight')) return
993
1065
  return this.api.createPostback(cid, postback)
994
1066
  }
995
1067
 
996
1068
  sendReaction(cid, ev) {
1069
+ if (cid && cid.startsWith('cslight')) return
997
1070
  return this.api.sendConvoEvent(cid, ev)
998
1071
  }
999
1072
 
1000
1073
  async sendConvoEvent(cid, type, data) {
1001
1074
  await this.readyGate.entry()
1002
1075
  var idempotency = sb.randomString(36)
1003
-
1004
1076
  let user = this.cache.match('user') || {}
1005
1077
 
1006
1078
  var event = {
@@ -1044,6 +1116,8 @@ export default class Store {
1044
1116
  })
1045
1117
 
1046
1118
  if (event._error) return this.onWs(event) // dont send error msg
1119
+ if (cid && cid.startsWith('cslight')) return
1120
+
1047
1121
  let {body: ret, error} = await this.api.sendConvoEvent(cid, event)
1048
1122
  if (error) {
1049
1123
  event._error = error
@@ -1059,7 +1133,7 @@ export default class Store {
1059
1133
  dataLayer.push({event: 'SUBIZ Chat', event_action: 'Sent', event_label: 'User', first_interaction: 1})
1060
1134
  }
1061
1135
 
1062
- this.kv.put('last_msg_sent', sb.Date.now())
1136
+ this.kv.put('last_msg_sent', this.now())
1063
1137
  return ret
1064
1138
  }
1065
1139
 
@@ -1179,6 +1253,7 @@ export default class Store {
1179
1253
 
1180
1254
  async listmessageapi(type, anchor, limit) {
1181
1255
  let convoid = type
1256
+ if (convoid && convoid.startsWith('cslight')) return [fakeevents, '', undefined, true]
1182
1257
  let {body: events, code, error} = await this.api.listMessages(convoid, -limit, anchor)
1183
1258
  if (error || code !== 200) return [[], anchor, error]
1184
1259
  anchor = sb.get(sb.last(events), 'id')
@@ -1212,16 +1287,16 @@ export default class Store {
1212
1287
  timeDiff = 0
1213
1288
  async loopTime() {
1214
1289
  if (this._die) return
1215
- let startms = sb.Date.now()
1290
+ let startms = this.now()
1216
1291
 
1217
1292
  // store not init
1218
1293
  if (!sb.get(this.cache.match('user'), 'id')) return sb.setTimeout(() => this.loopTime(), 240000)
1219
1294
 
1220
1295
  let {body: t, error} = await this.api.time()
1221
1296
  if (error) return sb.setTimeout(() => this.loopTime(), 30000)
1222
- var latences = Math.abs(sb.Date.now() - startms) / 2
1297
+ var latences = Math.abs(this.now() - startms) / 2
1223
1298
  var serverMs = new sb.Date(t).getTime() + latences
1224
- var localMs = sb.Date.now()
1299
+ var localMs = this.now()
1225
1300
  this.timeDiff = Math.floor(serverMs - localMs)
1226
1301
  sb.setTimeout(() => this.loopTime(), 240000)
1227
1302
  }