@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.
Files changed (130) hide show
  1. package/README.md +21 -2
  2. package/assets/img/dot_chat.svg +3 -0
  3. package/assets/less/chatbox.less +9 -0
  4. package/assets/less/form.less +10 -0
  5. package/assets/less/message.less +114 -13
  6. package/assets/less/widget.less +15 -18
  7. package/compiled_src/call/css.js +1 -1
  8. package/compiled_src/chatbox/Buttons.js +7 -4
  9. package/compiled_src/chatbox/ChatButton.js +127 -29
  10. package/compiled_src/chatbox/Event.js +37 -5
  11. package/compiled_src/chatbox/Events.js +348 -129
  12. package/compiled_src/chatbox/FormAttachment.js +1 -1
  13. package/compiled_src/chatbox/Header.js +7 -2
  14. package/compiled_src/chatbox/InfoMessage.js +1 -1
  15. package/compiled_src/chatbox/MessageInput.js +24 -9
  16. package/compiled_src/chatbox/OrderAttachments.js +13 -13
  17. package/compiled_src/chatbox/QuoteMessage.js +3 -2
  18. package/compiled_src/chatbox/TextMessage.js +22 -18
  19. package/compiled_src/chatbox/Widget.js +200 -129
  20. package/compiled_src/chatbox/block.js +39 -29
  21. package/compiled_src/chatbox/conversation-history.js +44 -0
  22. package/compiled_src/chatbox/event-layout.js +59 -0
  23. package/compiled_src/chatbox/greeting.js +32 -0
  24. package/compiled_src/chatbox/main.js +3 -0
  25. package/compiled_src/common.js +13 -9
  26. package/compiled_src/form/css.js +1 -1
  27. package/compiled_src/lib.js +22 -32
  28. package/compiled_src/notif/css.js +1 -1
  29. package/compiled_src/plugin.js +11 -18
  30. package/compiled_src/popup/css.js +1 -1
  31. package/compiled_src/popup/popup.js +1 -1
  32. package/compiled_src/store/api.js +0 -2
  33. package/compiled_src/store/api_fake.js +1 -1
  34. package/compiled_src/store/attachments.js +28 -0
  35. package/compiled_src/store/chain.js +24 -10
  36. package/compiled_src/store/kv.js +9 -13
  37. package/compiled_src/store/lsdb.js +1 -1
  38. package/compiled_src/store/store.js +356 -117
  39. package/compiled_src/util/common.js +48 -41
  40. package/compiled_src/util/event.js +15 -0
  41. package/compiled_src/util/pubsub.js +28 -13
  42. package/compiled_src/util/url.js +18 -0
  43. package/dist/127.js +13 -13
  44. package/dist/132.js +1 -1
  45. package/dist/1a0f6f3ff1a.127.js +1 -0
  46. package/dist/{1a0d80f6e72.132.js → 1a0f6f3ff1a.132.js} +1 -1
  47. package/dist/{1a0d80f6e72.27.js → 1a0f6f3ff1a.27.js} +1 -1
  48. package/dist/1a0f6f3ff1a.278.js +1 -0
  49. package/dist/{1a0d80f6e72.339.js → 1a0f6f3ff1a.339.js} +1 -1
  50. package/dist/1a0f6f3ff1a.459.js +1 -0
  51. package/dist/1a0f6f3ff1a.app.js +1 -0
  52. package/dist/{1a0d80f6e72.chat.html → 1a0f6f3ff1a.chat.html} +1 -1
  53. package/dist/1a0f6f3ff1a.chat.js +1 -0
  54. package/dist/{1a0d80f6e72.form.html → 1a0f6f3ff1a.form.html} +1 -1
  55. package/dist/1a0f6f3ff1a.form.js +1 -0
  56. package/dist/{1a0d80f6e72.index.html → 1a0f6f3ff1a.index.html} +1 -1
  57. package/dist/{1a0d80f6e72.library.html → 1a0f6f3ff1a.library.html} +1 -1
  58. package/dist/1a0f6f3ff1a.library.js +1 -0
  59. package/dist/{1a0d80f6e72.preview.html → 1a0f6f3ff1a.preview.html} +1 -1
  60. package/dist/1a0f6f3ff1a.preview.js +1 -0
  61. package/dist/1a0f6f3ff1a.webview.js +1 -0
  62. package/dist/27.js +2 -2
  63. package/dist/459.js +2 -2
  64. package/dist/HASH +1 -1
  65. package/dist/assets/img/dot_chat.335fd8a.svg +3 -0
  66. package/dist/screenshoot.js +1523 -619
  67. package/package.json +12 -5
  68. package/src/chatbox/Buttons.js +5 -4
  69. package/src/chatbox/ChatButton.js +133 -39
  70. package/src/chatbox/Event.js +38 -5
  71. package/src/chatbox/Events.js +306 -134
  72. package/src/chatbox/FormAttachment.js +1 -1
  73. package/src/chatbox/Header.js +5 -4
  74. package/src/chatbox/InfoMessage.js +1 -1
  75. package/src/chatbox/MessageInput.js +15 -10
  76. package/src/chatbox/OrderAttachments.js +13 -13
  77. package/src/chatbox/QuoteMessage.js +3 -2
  78. package/src/chatbox/TextMessage.js +21 -20
  79. package/src/chatbox/Widget.js +150 -150
  80. package/src/chatbox/block.js +29 -27
  81. package/src/chatbox/conversation-history.js +37 -0
  82. package/src/chatbox/event-layout.js +72 -0
  83. package/src/chatbox/greeting.js +13 -0
  84. package/src/chatbox/main.js +3 -0
  85. package/src/common.js +15 -9
  86. package/src/lib.js +22 -35
  87. package/src/plugin.js +11 -13
  88. package/src/popup/popup.js +1 -1
  89. package/src/store/api.js +0 -2
  90. package/src/store/api_fake.js +1 -1
  91. package/src/store/attachments.js +21 -0
  92. package/src/store/chain.js +25 -12
  93. package/src/store/kv.js +10 -19
  94. package/src/store/lsdb.js +1 -1
  95. package/src/store/store.js +313 -130
  96. package/src/util/common.js +48 -43
  97. package/src/util/event.js +7 -0
  98. package/src/util/pubsub.js +27 -8
  99. package/src/util/url.js +14 -0
  100. package/test.cjs +59 -0
  101. package/assets/less/contact.less +0 -126
  102. package/compiled_src/util/common.test.js +0 -414
  103. package/compiled_src/util/event.test.js +0 -38
  104. package/compiled_src/util/extractor.test.js +0 -299
  105. package/compiled_src/util/extractor.test2.js +0 -61
  106. package/compiled_src/util/gate.test.js +0 -18
  107. package/compiled_src/util/pubsub.test.js +0 -35
  108. package/dist/1a0d80f6e72.127.js +0 -1
  109. package/dist/1a0d80f6e72.278.js +0 -1
  110. package/dist/1a0d80f6e72.459.js +0 -1
  111. package/dist/1a0d80f6e72.74.js +0 -1
  112. package/dist/1a0d80f6e72.997.js +0 -1
  113. package/dist/1a0d80f6e72.app.js +0 -1
  114. package/dist/1a0d80f6e72.chat.js +0 -1
  115. package/dist/1a0d80f6e72.form.js +0 -1
  116. package/dist/1a0d80f6e72.library.js +0 -1
  117. package/dist/1a0d80f6e72.preview.js +0 -1
  118. package/dist/1a0d80f6e72.webview.js +0 -1
  119. package/dist/74.js +0 -376
  120. package/dist/997.js +0 -37
  121. package/src/contact/contact.js +0 -400
  122. package/src/contact/css.js +0 -1
  123. package/src/util/common.test.js +0 -301
  124. package/src/util/event.test.js +0 -31
  125. package/src/util/extractor.test.js +0 -240
  126. package/src/util/extractor.test2.js +0 -56
  127. package/src/util/gate.test.js +0 -20
  128. package/src/util/pubsub.test.js +0 -37
  129. /package/dist/{1a0d80f6e72.185.js → 1a0f6f3ff1a.185.js} +0 -0
  130. /package/dist/{1a0d80f6e72.490.js → 1a0f6f3ff1a.490.js} +0 -0
@@ -2,6 +2,7 @@ import sb from '../util/index.js'
2
2
  import Emoji from './Emoji.js'
3
3
  import {Com} from '../common.js'
4
4
  import {h} from 'preact'
5
+ import {safeUrl} from '../util/url.js'
5
6
 
6
7
  export default class Block extends Com {
7
8
  constructor(props) {
@@ -43,9 +44,8 @@ export default class Block extends Com {
43
44
  }
44
45
 
45
46
  if (block.type == 'heading') {
46
- if (block.level < 1) block.level = 1
47
- if (block.level > 6) block.level = 6
48
- ele.tag = 'h' + block.level
47
+ let level = Math.max(1, Math.min(6, Math.floor(Number(block.level) || 1)))
48
+ ele.tag = 'h' + level
49
49
  }
50
50
 
51
51
  if (block.type == 'paragraph' || block.type == 'section' || block.type == 'div') ele.tag = 'div'
@@ -61,8 +61,10 @@ export default class Block extends Com {
61
61
  }
62
62
  }
63
63
 
64
- if (block.underline) ele.Style += 'text-decoration:underline;'
65
- if (block.strike_through) ele.Style += 'text-decoration: line-through;'
64
+ let decorations = []
65
+ if (block.underline) decorations.push('underline')
66
+ if (block.strike_through) decorations.push('line-through')
67
+ if (decorations.length) ele.style += 'text-decoration: ' + decorations.join(' ') + ' !important;'
66
68
  }
67
69
 
68
70
  if (block.type == 'dynamic-field') {
@@ -72,7 +74,7 @@ export default class Block extends Com {
72
74
 
73
75
  if (block.type == 'link') {
74
76
  ele.tag = 'a'
75
- ele.attrs['href'] = block.href
77
+ ele.attrs['href'] = safeUrl(block.href)
76
78
  ele.attrs['target'] = block.target
77
79
  ele.attrs['title'] = block.title
78
80
  }
@@ -80,15 +82,15 @@ export default class Block extends Com {
80
82
  ele.tag = 'span'
81
83
  ele.class = 'mention'
82
84
 
83
- let id = (block.attrs.id = '')
85
+ let id = sb.get(block, 'attrs.id', '')
84
86
 
85
- if (block.attrs.id == '*' || block.attrs.id == 'all') {
86
- ele.text = '@' + this.$t('all')
87
+ if (id == '*' || id == 'all') {
88
+ ele.text = '@' + (this.props.t ? this.props.t('all') : 'all')
87
89
  }
88
90
 
89
- if (id.startsWith('ag')) {
90
- let ag = store.matchAgent(id)
91
- let name = sb.getAgentDisplayName(ag, insert.mention.fullname)
91
+ if (typeof id === 'string' && /^(ag|at)/.test(id) && this.props.store) {
92
+ let ag = this.props.store.matchAgent(id)
93
+ let name = sb.getAgentDisplayName(this.props.store.matchAccount(), ag)
92
94
  ele.text = '@' + name
93
95
  }
94
96
  }
@@ -96,19 +98,16 @@ export default class Block extends Com {
96
98
  if (block.type == 'emoji') {
97
99
  let code = ''
98
100
  if (sb.size(block.attrs) > 0) code = block.attrs['code']
99
- if (code == '') code = block.Text
101
+ if (!code) code = block.text || block.Text
100
102
  if (code != '') {
101
103
  ele.tag = 'Emoji' //
102
104
  ele.attrs['emojiCode'] = code
103
105
  }
104
106
  }
105
107
 
106
- if (block.type == 'table') {
107
- return null
108
- }
109
108
  if (block.type === 'image') {
110
109
  ele.tag = 'img'
111
- ele.attrs['src'] = sb.get(block, 'image.url')
110
+ ele.attrs['src'] = safeUrl(sb.get(block, 'image.url'), true)
112
111
  ele.attrs['alt'] = block.alt_text
113
112
  }
114
113
  if (block.type == 'hard_break') {
@@ -131,9 +130,11 @@ export default class Block extends Com {
131
130
  ) {
132
131
  url = '//' + url
133
132
  }
133
+ url = safeUrl(url)
134
+ if (!url) return tag.text
134
135
 
135
136
  return (
136
- <a href={url} target='_blank' onClick={(e) => this.props.onLink(e, url)}>
137
+ <a href={url} target='_blank' rel='noopener noreferrer' onClick={(e) => this.props.onLink && this.props.onLink(e, url)}>
137
138
  {tag.text}
138
139
  </a>
139
140
  )
@@ -148,11 +149,13 @@ export default class Block extends Com {
148
149
  }
149
150
  switch (ele.tag) {
150
151
  case 'img':
152
+ if (!ele.attrs.src) return block.alt_text || null
151
153
  return (
152
154
  <a
153
155
  class={this.props.textMessage ? 'message-text-inline-image-wrapper' : ''}
154
156
  href={sb.get(ele, 'attrs.src')}
155
157
  target='_blank'
158
+ rel='noopener noreferrer'
156
159
  >
157
160
  <img
158
161
  class={this.props.textMessage ? 'message-text-inline-image' : ''}
@@ -186,22 +189,25 @@ export default class Block extends Com {
186
189
  case 'li':
187
190
  return <li class={this.props.textMessage ? 'text-message-li' : ''}>{$body}</li>
188
191
  case 'span':
189
- return <span>{$body}</span>
192
+ return <span class={ele.class} style={ele.style}>{$body}</span>
190
193
  case 'em':
191
- return <em>{$body}</em>
194
+ return <em style={ele.style}>{$body}</em>
192
195
  case 'b':
193
- return <b>{$body}</b>
196
+ return <b style={ele.style}>{$body}</b>
194
197
  case 'hr':
195
198
  return <hr />
196
199
  case 'br':
197
200
  return <br />
198
201
  case 'a':
202
+ if (!ele.attrs.href) return <span>{$body}</span>
199
203
  return (
200
204
  <a
201
205
  class={this.props.textMessage ? 'message-text-link' : ''}
202
206
  href={ele.attrs['href']}
203
207
  target={this.props.textMessage ? '_blank' : ele.attrs['target']}
204
208
  title={ele.attrs['title']}
209
+ rel='noopener noreferrer'
210
+ onClick={(e) => this.props.onLink && this.props.onLink(e, ele.attrs.href)}
205
211
  >
206
212
  {$body}
207
213
  </a>
@@ -210,15 +216,11 @@ export default class Block extends Com {
210
216
  return <Emoji emojiCode={ele.attrs['emojiCode']} />
211
217
  }
212
218
 
213
- return null
219
+ // Unknown containers should retain their readable content.
220
+ return $body
214
221
  }
215
222
 
216
223
  render() {
217
224
  return this.renderBlock(this.props.block)
218
225
  }
219
226
  }
220
-
221
- function absLink(url) {
222
- if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('//')) return 'http://' + url
223
- return url
224
- }
@@ -0,0 +1,37 @@
1
+ import sb from '../util/index.js'
2
+
3
+ // Creation order is stable even when an older conversation receives a new reply.
4
+ export function conversationHistory(store, cid) {
5
+ if (!cid) return []
6
+ const current = store.matchConvo(cid)
7
+ if (!current || !current.created) return [current || {id: cid}]
8
+ const conversations = sb.map(store.matchConvo('*'))
9
+ const unique = new Map()
10
+ for (const convo of conversations) {
11
+ if (!convo || !convo.id || convo.id === cid || convo.id.startsWith('cslight')) continue
12
+ if (current.account_id && convo.account_id !== current.account_id) continue
13
+ if (convo.created && sb.getMs(convo.created) < sb.getMs(current.created)) unique.set(convo.id, convo)
14
+ }
15
+ return [...unique.values()].sort((a, b) => sb.getMs(a.created) - sb.getMs(b.created) || a.id.localeCompare(b.id)).concat(current)
16
+ }
17
+
18
+ export function conversationEvents(store, convo) {
19
+ const events = sb.orderBy(sb.map(store.listEvents(convo.id)), 'created', 'asc')
20
+ const unique = new Map()
21
+ for (const ev of events) {
22
+ const key = conversationEventKey(ev)
23
+ const previous = unique.get(key)
24
+ if (!previous) unique.set(key, ev)
25
+ // History can arrive before the optimistic event is reconciled by the socket.
26
+ // Prefer the confirmed payload while keeping the original message position.
27
+ else if (previous.id === previous.source_event_id && ev.id !== ev.source_event_id) unique.set(key, {...ev, created: previous.created})
28
+ }
29
+ return [...unique.values()].map(ev => ({ev}))
30
+ }
31
+
32
+ export function conversationEventKey(ev) {
33
+ // A send can also produce conversation_joined with the same source_event_id.
34
+ // Only message copies use that ID for optimistic acknowledgement reconciliation.
35
+ const id = ev.type === 'message_sent' ? ev.source_event_id || ev.id : ev.id
36
+ return ev.type + ':' + id
37
+ }
@@ -0,0 +1,72 @@
1
+ import sb from '../util/index.js'
2
+
3
+ //_mode = lead
4
+ export function processEvents(convo, events) {
5
+ events = events.filter((event) => visibleEvent(event.ev))
6
+ if (events.length < 1) return
7
+
8
+ // show time for the first msg sent
9
+ // insert time
10
+ var lastAgentEvent
11
+ for (var i = 1; i < events.length; i++) {
12
+ let curEvent = events[i]
13
+ let lastEvent = events[i - 1]
14
+ if (sb.get(curEvent, 'ev.by.type') != 'user' && !curEvent.is_typing) {
15
+ lastAgentEvent = curEvent
16
+ }
17
+
18
+ if (visibleEvent(curEvent.ev)) {
19
+ let elapsedSec = sb.unixSec(curEvent.ev.created) - sb.unixSec(lastEvent.ev.created)
20
+ if (elapsedSec > 300) curEvent.show_time = true // 5 mins
21
+ }
22
+ }
23
+
24
+ if (lastAgentEvent) lastAgentEvent.show_time = true
25
+
26
+ for (var i = 0; i < events.length; i++) {
27
+ let curEvent = events[i]
28
+ if (sb.get(curEvent, 'ev.type') === 'message_sent') {
29
+ curEvent.show_time = true
30
+ break
31
+ }
32
+ }
33
+
34
+ let lastMsgSentIdx = -1
35
+ // insert lead
36
+ for (var i = 0; i < events.length; i++) {
37
+ let curEvent = events[i]
38
+ let prevEvent = events[i - 1]
39
+ let nextEvent = events[i + 1]
40
+
41
+ if (curEvent.ev.type === 'message_sent') {
42
+ // ignore fake typing message_sent event at last
43
+ lastMsgSentIdx = i
44
+ curEvent.mode = 'lead'
45
+ if (
46
+ sb.get(prevEvent, 'ev.type') === 'message_sent' &&
47
+ sb.get(prevEvent, 'ev.by.id') === sb.get(curEvent, 'ev.by.id')
48
+ ) {
49
+ curEvent.mode = 'follow'
50
+ }
51
+ if (
52
+ sb.get(nextEvent, 'ev.type') !== 'message_sent' ||
53
+ sb.get(nextEvent, 'ev.by.id') !== sb.get(curEvent, 'ev.by.id')
54
+ ) {
55
+ curEvent.is_end = true
56
+ }
57
+ continue
58
+ }
59
+ }
60
+ if (lastMsgSentIdx > -1) {
61
+ events[lastMsgSentIdx].is_last_message_send = true
62
+ }
63
+ }
64
+
65
+ function visibleEvent(ev) {
66
+ let type = sb.get(ev, 'type')
67
+ if (type === 'message_sent') return true
68
+ if (type === 'conversation_state_updated') {
69
+ return sb.get(ev, 'data.conversation.state') == 'ended'
70
+ }
71
+ }
72
+
@@ -0,0 +1,13 @@
1
+ import sb from '../util/index.js'
2
+
3
+ export function widgetGreeting(chatbox, t, created = sb.Date.now()) {
4
+ const greeting = sb.get(chatbox, 'greeting', {})
5
+ const localized = t.i18n ? t.i18n(greeting.greeting || {}) : ''
6
+ const text = sb.trim(localized) || sb.trim(greeting.message) || sb.trim(t('team_intro')) || t('empty_convo_text')
7
+ return {
8
+ id: 'widget-greeting-' + sb.randomString(24),
9
+ type: 'message_sent', created,
10
+ by: {type: 'system', id: 'system', ...(greeting.agent_id ? {display_type: 'agent', display_id: greeting.agent_id} : {})},
11
+ data: {message: {text, format: 'plaintext'}},
12
+ }
13
+ }
@@ -201,6 +201,9 @@ export default class Main extends Com {
201
201
  newView.view = 'chat'
202
202
  newView.opts = {cid}
203
203
  }
204
+ if (newView.view === 'chat' && !newView.opts.cid && this.refWidget.current) {
205
+ return this.refWidget.current.goToConvo('')
206
+ }
204
207
 
205
208
  if (this.props.store.isMobile()) modifyViewport(this.shouldLockPageScroll())
206
209
  this.props.store.updateLastview(newView)
package/src/common.js CHANGED
@@ -5,15 +5,21 @@ export class Com extends Component {
5
5
  constructor(props) {
6
6
  super(props)
7
7
  this._isMounted = true
8
- sb.setTimeout(() => {
9
- if (this.componentWillUnmount) {
10
- let old = this.componentWillUnmount.bind(this)
11
- this.componentWillUnmount = () => {
12
- old()
13
- this._isMounted = false
14
- }
15
- }
16
- }, 10)
8
+ this._subscriptions = new Set()
9
+ const onUnmount = this.componentWillUnmount
10
+ this.componentWillUnmount = () => {
11
+ if (!this._isMounted) return
12
+ this._isMounted = false
13
+ for (const unsubscribe of this._subscriptions) unsubscribe()
14
+ this._subscriptions.clear()
15
+ if (onUnmount) onUnmount.call(this)
16
+ }
17
+ }
18
+
19
+ onDispose(cleanup) {
20
+ if (this._isMounted) this._subscriptions.add(cleanup)
21
+ else cleanup()
22
+ return () => this._subscriptions.delete(cleanup)
17
23
  }
18
24
  }
19
25
 
package/src/lib.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import Store from './store/store.js'
2
2
  import sb from './util/index.js'
3
3
  import PluginMgr from './plugin.js'
4
+ import {normalizeCdpEvent} from './util/event.js'
4
5
 
5
6
  /*
6
7
  var $link = document.createElement('link')
@@ -14,6 +15,7 @@ sb.preloadWebfonts(['Inter'], [400, 500])
14
15
  class Widget {
15
16
  constructor(ApiURL, FileURL, RealtimeURL, inmem, fake) {
16
17
  this.impressionTokens = {}
18
+ this._containerTimers = new Set()
17
19
  this.domGate = new sb.Gate()
18
20
  this.store = new Store(ApiURL, FileURL, RealtimeURL, inmem, fake)
19
21
 
@@ -37,6 +39,17 @@ class Widget {
37
39
  cb()
38
40
  }
39
41
 
42
+ watchContainer(container) {
43
+ const manager = this.pluginMgr
44
+ const timer = sb.setInterval(() => {
45
+ if (!this.store.isDead() && container && container.isConnected) return
46
+ manager.destroy()
47
+ sb.clearInterval(timer)
48
+ this._containerTimers.delete(timer)
49
+ }, 1000)
50
+ this._containerTimers.add(timer)
51
+ }
52
+
40
53
  setAIDevMode(mode) {
41
54
  this.store.setAIDevMode(mode)
42
55
  }
@@ -60,12 +73,7 @@ class Widget {
60
73
  if (!$container) $container = document.body
61
74
  this.pluginMgr = new PluginMgr(this, $container)
62
75
 
63
- let clean = sb.setInterval(() => {
64
- // check if the dom is deleted
65
- if ($container && $container.isConnected) return
66
- this.pluginMgr.destroy()
67
- sb.clearInterval(clean)
68
- }, 1000)
76
+ this.watchContainer($container)
69
77
  this.domGate.open()
70
78
  return user
71
79
  }
@@ -92,12 +100,7 @@ class Widget {
92
100
  if (!$container) $container = document.body || document
93
101
  this.pluginMgr = new PluginMgr(this, $container)
94
102
 
95
- let clean = sb.setInterval(() => {
96
- // check if the dom is deleted
97
- if ($container && $container.isConnected) return
98
- this.pluginMgr.destroy()
99
- sb.clearInterval(clean)
100
- }, 1000)
103
+ this.watchContainer($container)
101
104
  this.domGate.open()
102
105
  return user
103
106
  }
@@ -111,12 +114,7 @@ class Widget {
111
114
  if (!$container) $container = document.body || document
112
115
  this.pluginMgr = new PluginMgr(this, $container)
113
116
 
114
- let clean = sb.setInterval(() => {
115
- // check if the dom is deleted
116
- if ($container && $container.isConnected) return
117
- this.pluginMgr.destroy()
118
- sb.clearInterval(clean)
119
- }, 1000)
117
+ this.watchContainer($container)
120
118
  this.domGate.open()
121
119
  cb && cb(outmask)
122
120
  return outmask
@@ -144,12 +142,7 @@ class Widget {
144
142
  if (!$container) $container = document.body || document
145
143
  this.pluginMgr = new PluginMgr(this, $container)
146
144
 
147
- let clean = sb.setInterval(() => {
148
- // check if the dom is deleted
149
- if ($container && $container.isConnected) return
150
- this.pluginMgr.destroy()
151
- sb.clearInterval(clean)
152
- }, 1000)
145
+ this.watchContainer($container)
153
146
  this.domGate.open()
154
147
  if (typeof sbz_info != 'undefined') sbz_info.set_account_done = sb.Date.now()
155
148
 
@@ -181,7 +174,7 @@ class Widget {
181
174
 
182
175
  async createTicket(ticket) {
183
176
  await this.domGate.entry()
184
- return this.createTicket(ticket)
177
+ return this.store.createTicket(ticket)
185
178
  }
186
179
 
187
180
  expandWidget(view, convo) {
@@ -274,7 +267,7 @@ class Widget {
274
267
  if (req.bot_id) {
275
268
  let botid = req.bot_id
276
269
  delete req.bot_id
277
- if (!sb.find(members, (m) => b.id == botid)) {
270
+ if (!sb.find(members, (m) => m.id == botid)) {
278
271
  members.push({type: 'bot', id: botid, bot_version: req.bot_version})
279
272
  }
280
273
  }
@@ -343,6 +336,9 @@ class Widget {
343
336
  }
344
337
 
345
338
  unload() {
339
+ for (const timer of this._containerTimers) sb.clearInterval(timer)
340
+ this._containerTimers.clear()
341
+ this.subs = []
346
342
  // this.store.overridePlugin([])
347
343
  if (this.pluginMgr) this.pluginMgr.destroy()
348
344
  this.store.destroy()
@@ -403,15 +399,6 @@ class Widget {
403
399
  }
404
400
  }
405
401
 
406
- function normalizeCdpEvent(type, payload, options) {
407
- if (type && typeof type === 'object') return type
408
-
409
- let data = {type: type}
410
- if (payload !== undefined) data.payload = payload
411
- if (options && typeof options === 'object') data = Object.assign({}, options, data)
412
- return data
413
- }
414
-
415
402
  // for debugging purpose
416
403
  window.__sbz_WIDGET = Widget
417
404
  export default Widget
package/src/plugin.js CHANGED
@@ -4,7 +4,6 @@ import {h, render, createRef} from 'preact'
4
4
 
5
5
  // const LoadChatbox = () => import(/*webpackChunkName: "chatbox" */ './chatbox/main.js')
6
6
  import Chatbox from './chatbox/main.js'
7
- const LoadContact = () => import(/*webpackChunkName: "contact" */ './contact/contact.js')
8
7
  const LoadPopup = () => import(/*webpackChunkName: "popup" */ './popup/popup.js')
9
8
  const LoadNotif = () => import(/*webpackChunkName: "notif" */ './notif/notif.js')
10
9
 
@@ -59,7 +58,6 @@ function Plugin(plugin, store, $container, lib) {
59
58
 
60
59
  // let Chatbox = null
61
60
  let Popup = null
62
- let Contact = null
63
61
  let Notif = null
64
62
 
65
63
  var showCurrentChatbox = (p) => lib.showWidget(p)
@@ -107,11 +105,6 @@ function Plugin(plugin, store, $container, lib) {
107
105
  )
108
106
  }
109
107
 
110
- if (plugin.type === 'contact') {
111
- if (!Contact) Contact = (await LoadContact()).default
112
- render(<Contact destroy={_destroy} t={this.t} plugin={plugin} store={store} ref={_ref} />, $div)
113
- }
114
-
115
108
  domGate.open()
116
109
  }
117
110
 
@@ -123,10 +116,11 @@ function Plugin(plugin, store, $container, lib) {
123
116
 
124
117
  var _destroy = false
125
118
  this.destroy = () => {
119
+ if (_destroy) return
126
120
  _destroy = true
127
121
  render(null, $div) // trigger unmount
128
122
 
129
- $div.parentNode.removeChild($div)
123
+ if ($div.parentNode) $div.parentNode.removeChild($div)
130
124
  // remove div
131
125
  }
132
126
 
@@ -229,14 +223,15 @@ export default class PluginMgr {
229
223
  this.pluginM = {}
230
224
 
231
225
  this._destroy = false
226
+ this._subscriptions = []
232
227
 
233
- lib.store.onHighlight({_isMounted: true}, (id, comref) => {
228
+ this._subscriptions.push(lib.store.onHighlight({_isMounted: true}, (id, comref) => {
234
229
  let plugin = this.pluginM[id]
235
230
  if (!plugin) return
236
231
  plugin.highlight(comref)
237
- })
232
+ }))
238
233
 
239
- lib.store.onUpdate({_isMounted: true}, () => {
234
+ this._subscriptions.push(lib.store.onUpdate({_isMounted: true}, () => {
240
235
  if (this._destroy) return
241
236
  let pluginM = this.lib.store.matchPlugin()
242
237
  let foundchatbox = false
@@ -259,7 +254,7 @@ export default class PluginMgr {
259
254
  this.pluginM[id].destroy()
260
255
  delete this.pluginM[id]
261
256
  })
262
- })
257
+ }))
263
258
 
264
259
  let pluginM = this.lib.store.matchPlugin()
265
260
  if (typeof sbz_info != 'undefined') sbz_info['plugins'] = pluginM
@@ -351,11 +346,14 @@ export default class PluginMgr {
351
346
  }
352
347
 
353
348
  destroy() {
349
+ if (this._destroy) return
350
+ this._destroy = true
351
+ for (const unsubscribe of this._subscriptions) unsubscribe()
352
+ this._subscriptions = []
354
353
  sb.map(this.pluginM, (conf, id) => {
355
354
  conf.destroy()
356
355
  delete this.pluginM[id]
357
356
  })
358
357
 
359
- this._destroy = true
360
358
  }
361
359
  }
@@ -63,7 +63,7 @@ export default class Popup extends Com {
63
63
  for (var i = 0; i < fonts.length; i++) {
64
64
  try {
65
65
  if (document.fonts.check('normal 16px ' + fonts[i])) ready[fonts[i]] = true
66
- if (Object.keys(readycount) == fonts.length) return
66
+ if (Object.keys(ready).length == fonts.length) return
67
67
  } catch (e) {}
68
68
  }
69
69
  await sb.sleep(200)
package/src/store/api.js CHANGED
@@ -39,7 +39,6 @@ export default class {
39
39
 
40
40
  this.FileURL = FileURL
41
41
  this.ApiURL = ApiURL
42
- this.bugReq = ajax.setBaseUrl('https://api5.subiz.com.vn/4.1').setContentType('text/plain')
43
42
  this.api41 = ajax
44
43
  .setBaseUrl('https://api.sbz.vn/4.1')
45
44
  .withCredentials(true)
@@ -230,7 +229,6 @@ export default class {
230
229
  return {error: 'invalid_auth'}
231
230
  }
232
231
 
233
- sendBugReport = (str) => this.bugReq.post('/bugs', str)
234
232
 
235
233
  listConversations = (_) =>
236
234
  this.apireq
@@ -208,6 +208,7 @@ function makeFakeMessages(accid, convoid, userid, agentid) {
208
208
  },
209
209
  {
210
210
  id: 'evqyiyjmzatvtgpdmmpzwnwgn',
211
+ source_event_id: 'wrueysyjbhqrkiwhsejpqrwqrdkkyaibajdw',
211
212
  account_id: accid,
212
213
  created: 1616213202685,
213
214
  type: 'message_sent',
@@ -217,7 +218,6 @@ function makeFakeMessages(accid, convoid, userid, agentid) {
217
218
  conversation_id: convoid,
218
219
  text: 'Xin chào',
219
220
  format: 'plaintext',
220
- idempotency_key: 'wrueysyjbhqrkiwhsejpqrwqrdkkyaibajdw',
221
221
  integration_id: inteid,
222
222
  pongs: [{member_id: userid, type: 'seen', created: 1616213203068}],
223
223
  },
@@ -0,0 +1,21 @@
1
+ // Keep upload errors across the entire batch. A later successful upload must
2
+ // not turn an incomplete message into a successful one.
3
+ export async function uploadAttachments(attachments, upload) {
4
+ let error, errorMessage
5
+ for (const attachment of attachments) {
6
+ try {
7
+ const result = await upload(attachment._file)
8
+ if (result.body && result.body.code === 'invalid_field_size') {
9
+ errorMessage = 'cannot_send_too_big_file'
10
+ }
11
+ if (result.error || !result.url) {
12
+ error = error || result.error || 'upload_failed'
13
+ continue
14
+ }
15
+ attachment.url = result.url
16
+ } catch (err) {
17
+ error = error || err
18
+ }
19
+ }
20
+ return {error, errorMessage}
21
+ }
@@ -111,6 +111,17 @@ export default class Chain {
111
111
  return {}
112
112
  }
113
113
 
114
+ merge(chain, items) {
115
+ if (this.dead) return
116
+ const cached = this.cache[chain] || {}
117
+ for (const item of items) {
118
+ const node = {...item, chain, full_id: chain + ':' + item.id}
119
+ cached[item.id] = node
120
+ this.db.put('items', node.full_id, node)
121
+ }
122
+ this.cache[chain] = cached
123
+ }
124
+
114
125
  del(chain, id) {
115
126
  if (this.dead) return
116
127
  let inmemItemM = this.cache[chain] || {}
@@ -127,7 +138,7 @@ export default class Chain {
127
138
  async reload(chain, api) {
128
139
  if (this.dead) return
129
140
 
130
- let top = maxIndex(this.listM(chain)) || {}
141
+ let top = maxIndex(this.cache[chain] || {}) || {}
131
142
  let topindex = top.index
132
143
  if (!topindex) topindex = Date.now()
133
144
  var lastanchor = ''
@@ -142,8 +153,17 @@ export default class Chain {
142
153
  })
143
154
  if (newItems.length === 0 || newanchor === lastanchor) break // out of item
144
155
 
156
+ let minindexitem = minIndex(fetchedItems) || {}
157
+ // try to join old data
158
+ if (minindexitem.index <= topindex) {
159
+ // successfully joined, reuse anchor
160
+
161
+ lastanchor = this.anchorCache[chain] || newanchor
162
+ break
163
+ }
164
+
145
165
  // still cannot join
146
- if (fetchedItems.length >= 200) {
166
+ if (size(fetchedItems) >= 200) {
147
167
  // fetch too much but still cannot merge with old data
148
168
  // clear old data and write new data
149
169
 
@@ -155,22 +175,15 @@ export default class Chain {
155
175
  })
156
176
  this.db.removes('items', removeids)
157
177
  this.cache[chain] = {}
178
+ lastanchor = newanchor
158
179
  break
159
180
  }
160
181
 
161
- let minindexitem = minIndex(fetchedItems) || {}
162
- // try to join old data
163
- if (minindexitem.index <= topindex) {
164
- // successfully joined, reuse anchor
165
182
 
166
- lastanchor = this.anchorCache[chain] || newanchor
167
- break
168
- }
169
183
  lastanchor = newanchor
170
184
  }
171
185
 
172
- for (var i = 0; i < Object.keys(fetchedItems).length; i++) {
173
- let item = fetchedItems[Object.keys(fetchedItems)[i]]
186
+ for (const item of Object.values(fetchedItems)) {
174
187
  this.db.put('items', item.full_id, item)
175
188
  this.cache[item.chain] = this.cache[item.chain] || {}
176
189
  this.cache[item.chain][item.id] = item
@@ -210,7 +223,7 @@ function maxIndex(obj) {
210
223
  }
211
224
  }
212
225
 
213
- return obj[keys[maxKey]]
226
+ return obj[maxKey]
214
227
  }
215
228
 
216
229
  function map(collection, predicate) {