@subiz/widget-v4 4.0.92 → 4.0.94

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 (175) hide show
  1. package/assets/less/chatbox.less +8 -0
  2. package/assets/less/message.less +8 -3
  3. package/assets/less/order.less +10 -0
  4. package/compiled_src/bot.js +0 -11
  5. package/compiled_src/call/call.js +0 -61
  6. package/compiled_src/call/css.js +0 -2
  7. package/compiled_src/chatbox/AgentsAvatar.js +0 -16
  8. package/compiled_src/chatbox/AudioAttachments.js +0 -14
  9. package/compiled_src/chatbox/ButtonAttachments.js +1 -14
  10. package/compiled_src/chatbox/Buttons.js +0 -13
  11. package/compiled_src/chatbox/ChatButton.js +1 -78
  12. package/compiled_src/chatbox/DropdownSelect.js +3 -44
  13. package/compiled_src/chatbox/Emoji.js +0 -7
  14. package/compiled_src/chatbox/Event.js +0 -59
  15. package/compiled_src/chatbox/Events.js +4 -44
  16. package/compiled_src/chatbox/FileAttachments.js +0 -14
  17. package/compiled_src/chatbox/FormAttachment.js +1 -52
  18. package/compiled_src/chatbox/FormAttachments.js +0 -14
  19. package/compiled_src/chatbox/FormSubmitAttachments.js +0 -11
  20. package/compiled_src/chatbox/GenericTemplateAttachments.js +1 -20
  21. package/compiled_src/chatbox/Header.js +1 -46
  22. package/compiled_src/chatbox/ImageAttachments.js +0 -16
  23. package/compiled_src/chatbox/InfoMessage.js +0 -13
  24. package/compiled_src/chatbox/ListField.js +0 -16
  25. package/compiled_src/chatbox/MessageInput.js +1 -47
  26. package/compiled_src/chatbox/Order.js +0 -35
  27. package/compiled_src/chatbox/OrderAttachments.js +98 -39
  28. package/compiled_src/chatbox/PoweredBy.js +0 -7
  29. package/compiled_src/chatbox/ProductAttachments.js +0 -19
  30. package/compiled_src/chatbox/QuickReplies.js +0 -18
  31. package/compiled_src/chatbox/Rating.js +0 -37
  32. package/compiled_src/chatbox/ShadowDomRoot.js +0 -9
  33. package/compiled_src/chatbox/TextMessage.js +0 -64
  34. package/compiled_src/chatbox/Typing.js +0 -15
  35. package/compiled_src/chatbox/VideoAttachments.js +0 -14
  36. package/compiled_src/chatbox/Widget.js +6 -167
  37. package/compiled_src/chatbox/css.js +0 -2
  38. package/compiled_src/chatbox/dragscroll.js +1 -5
  39. package/compiled_src/chatbox/main.js +1 -40
  40. package/compiled_src/chatbox/releaseAnimation.js +0 -11
  41. package/compiled_src/chatbox/spinner.js +0 -10
  42. package/compiled_src/chatbox/time.js +0 -22
  43. package/compiled_src/chatbox/touchscroll.js +0 -7
  44. package/compiled_src/common.js +0 -22
  45. package/compiled_src/contact/contact.js +2 -52
  46. package/compiled_src/contact/css.js +0 -2
  47. package/compiled_src/lib.js +19 -46
  48. package/compiled_src/notif/css.js +0 -2
  49. package/compiled_src/notif/notif.js +0 -63
  50. package/compiled_src/order/address_picker.js +4 -38
  51. package/compiled_src/order/icon.js +0 -16
  52. package/compiled_src/order/index.js +1 -34
  53. package/compiled_src/order/order.js +2 -156
  54. package/compiled_src/plugin.js +1 -77
  55. package/compiled_src/popup/countdown.js +2 -33
  56. package/compiled_src/popup/css.js +0 -2
  57. package/compiled_src/popup/popup.js +2 -165
  58. package/compiled_src/popup/tick.core.commonjs.js +72 -222
  59. package/compiled_src/popup/tick.view.flip.commonjs.js +23 -31
  60. package/compiled_src/store/api.js +99 -65
  61. package/compiled_src/store/api_fake.js +3 -39
  62. package/compiled_src/store/realtime_fake.js +0 -6
  63. package/compiled_src/store/store.js +46 -249
  64. package/compiled_src/util/campaign.js +0 -13
  65. package/compiled_src/util/common.js +15 -185
  66. package/compiled_src/util/common.test.js +0 -4
  67. package/compiled_src/util/extractor.js +1 -21
  68. package/compiled_src/util/extractor.test.js +0 -3
  69. package/compiled_src/util/gate.js +0 -7
  70. package/compiled_src/util/gate.test.js +1 -2
  71. package/compiled_src/util/index.js +3 -7
  72. package/compiled_src/util/mutex.js +31 -0
  73. package/compiled_src/util/op.js +1 -14
  74. package/compiled_src/util/order.js +118 -0
  75. package/compiled_src/util/pubsub.js +0 -6
  76. package/compiled_src/util/pubsub.test.js +0 -2
  77. package/package.json +12 -13
  78. package/src/chatbox/OrderAttachments.js +111 -18
  79. package/src/lib.js +24 -2
  80. package/src/store/api.js +110 -21
  81. package/src/store/api_fake.js +5 -6
  82. package/src/store/store.js +47 -17
  83. package/src/util/index.js +4 -0
  84. package/src/util/mutex.js +27 -0
  85. package/src/util/order.js +120 -0
  86. package/dist/132.js +0 -7092
  87. package/dist/178.js +0 -406
  88. package/dist/18364d15f37.132.js +0 -1
  89. package/dist/18364d15f37.178.js +0 -1
  90. package/dist/18364d15f37.213.js +0 -1
  91. package/dist/18364d15f37.335.js +0 -1
  92. package/dist/18364d15f37.362.js +0 -1
  93. package/dist/18364d15f37.441.js +0 -1
  94. package/dist/18364d15f37.605.js +0 -1
  95. package/dist/18364d15f37.678.js +0 -1
  96. package/dist/18364d15f37.782.js +0 -1
  97. package/dist/18364d15f37.app.js +0 -1
  98. package/dist/18364d15f37.index.html +0 -26
  99. package/dist/18364d15f37.library.html +0 -26
  100. package/dist/18364d15f37.library.js +0 -1
  101. package/dist/18364d15f37.preview.html +0 -759
  102. package/dist/18364d15f37.preview.js +0 -1
  103. package/dist/18364d15f37.screenshoot.js +0 -1
  104. package/dist/213.js +0 -3215
  105. package/dist/335.js +0 -411
  106. package/dist/362.js +0 -107
  107. package/dist/441.js +0 -2834
  108. package/dist/605.js +0 -328
  109. package/dist/678.js +0 -107
  110. package/dist/782.js +0 -107
  111. package/dist/HASH +0 -1
  112. package/dist/assets/img/Facebook.64607ec.png +0 -0
  113. package/dist/assets/img/Zalo.6845707.png +0 -0
  114. package/dist/assets/img/award1a.1a58e10.png +0 -0
  115. package/dist/assets/img/award3a.3b74423.png +0 -0
  116. package/dist/assets/img/award4a.ed6ccd4.png +0 -0
  117. package/dist/assets/img/award5.39eb7ae.png +0 -0
  118. package/dist/assets/img/bubble_default.7d5e4ab.svg +0 -20
  119. package/dist/assets/img/caret.998eb8f.svg +0 -1
  120. package/dist/assets/img/convos_empty.2353007.svg +0 -72
  121. package/dist/assets/img/default.06e6ee0.jpg +0 -0
  122. package/dist/assets/img/default_avatar.5b74dc1.png +0 -0
  123. package/dist/assets/img/done_gray.d7e8203.svg +0 -4
  124. package/dist/assets/img/done_green.b817fd4.svg +0 -4
  125. package/dist/assets/img/emoji/messenger/sub_23.529_58.824.d5faf14.png +0 -0
  126. package/dist/assets/img/emoji/messenger/sub_23.529_60.784.1420241.png +0 -0
  127. package/dist/assets/img/emoji/messenger/sub_25.490_0.000.1e6b2c0.png +0 -0
  128. package/dist/assets/img/emoji/messenger/sub_25.490_45.098.3dfbc08.png +0 -0
  129. package/dist/assets/img/emoji/messenger/sub_25.490_5.882.4b49e94.png +0 -0
  130. package/dist/assets/img/emoji/messenger/sub_27.451_72.549.7114f58.png +0 -0
  131. package/dist/assets/img/emoji/messenger/sub_27.451_84.314.231e2b7.png +0 -0
  132. package/dist/assets/img/emoji/messenger/sub_27.451_96.078.ddbb008.png +0 -0
  133. package/dist/assets/img/emoji/messenger/sub_29.412_5.882.61f3359.png +0 -0
  134. package/dist/assets/img/emoji/messenger/sub_58.824_100.000.6df72bc.png +0 -0
  135. package/dist/assets/img/emoji/messenger/sub_58.824_45.098.3d7a7ad.png +0 -0
  136. package/dist/assets/img/emoji/messenger/sub_58.824_47.059.0f4d6e2.png +0 -0
  137. package/dist/assets/img/emoji/messenger/sub_58.824_54.902.65724c1.png +0 -0
  138. package/dist/assets/img/emoji/messenger/sub_58.824_64.706.9da3cba.png +0 -0
  139. package/dist/assets/img/emoji/messenger/sub_58.824_72.549.edca5cf.png +0 -0
  140. package/dist/assets/img/emoji/messenger/sub_58.824_78.431.f910f2a.png +0 -0
  141. package/dist/assets/img/emoji/messenger/sub_58.824_88.235.def4e71.png +0 -0
  142. package/dist/assets/img/emoji/messenger/sub_60.784_11.765.49c03d0.png +0 -0
  143. package/dist/assets/img/emoji/messenger/sub_60.784_27.451.46b54e5.png +0 -0
  144. package/dist/assets/img/emoji/messenger/sub_60.784_29.412.e36d284.png +0 -0
  145. package/dist/assets/img/emoji/messenger/sub_60.784_3.922.bb191b0.png +0 -0
  146. package/dist/assets/img/emoji/messenger/sub_60.784_35.294.af7b927.png +0 -0
  147. package/dist/assets/img/emoji/messenger/sub_60.784_7.843.98f2a99.png +0 -0
  148. package/dist/assets/img/empty.29bed2d.svg +0 -23
  149. package/dist/assets/img/empty_1.0b98af0.svg +0 -11
  150. package/dist/assets/img/examplte_logo.f86535f.svg +0 -1
  151. package/dist/assets/img/external-link.90577cc.svg +0 -1
  152. package/dist/assets/img/fb_inverse_circle.36c2cfc.png +0 -0
  153. package/dist/assets/img/ghn.070f8e3.svg +0 -17
  154. package/dist/assets/img/ghtk.326eac1.svg +0 -18
  155. package/dist/assets/img/image_default.be0698b.png +0 -0
  156. package/dist/assets/img/info.ebce7ea.svg +0 -1
  157. package/dist/assets/img/loader.37000a7.svg +0 -1
  158. package/dist/assets/img/loading_static.8f403ae.svg +0 -10
  159. package/dist/assets/img/main-service1.d536cc2.png +0 -0
  160. package/dist/assets/img/main-service2.9b99549.png +0 -0
  161. package/dist/assets/img/not_found_search.2ddb8d1.png +0 -0
  162. package/dist/assets/img/package.8d37d0f.svg +0 -5
  163. package/dist/assets/img/package_fade.763c001.svg +0 -1
  164. package/dist/assets/img/rating--average.1ea492c.svg +0 -12
  165. package/dist/assets/img/rating--great.e3d712e.svg +0 -16
  166. package/dist/assets/img/rating--poor.2938a25.svg +0 -12
  167. package/dist/assets/img/search.f9705ff.svg +0 -1
  168. package/dist/assets/img/send.6db52a9.svg +0 -3
  169. package/dist/assets/img/shop.f7dc7d8.svg +0 -1
  170. package/dist/assets/img/shop_faded.df7845a.svg +0 -1
  171. package/dist/assets/img/subiz-mono.877e9f5.svg +0 -3
  172. package/dist/assets/img/x.26fdfa3.svg +0 -5
  173. package/dist/malicious-app.js +0 -13
  174. package/dist/screenshoot.html +0 -7
  175. package/dist/screenshoot.js +0 -5953
@@ -15,12 +15,15 @@ export default class ProductAttachments extends Com {
15
15
  if (orders.length === 0) return null
16
16
 
17
17
  let $orders = sb.map(orders, (order, index) => {
18
+ sb.calcOrderTotal(order)
18
19
  let $note = null
19
20
  if (order.note) {
20
21
  $note = (
21
22
  <div>
22
23
  <div class='order_attachment__label'>{this.props.t('note')}</div>
23
- <div class='order_attachment__text_truncate'>{order.note}</div>
24
+ <div class='order_attachment__note' title={order.note}>
25
+ {order.note}
26
+ </div>
24
27
  </div>
25
28
  )
26
29
  }
@@ -38,11 +41,16 @@ export default class ProductAttachments extends Com {
38
41
  <img
39
42
  class='order_attachment__product_img'
40
43
  alt={'' + sb.get(product, 'id')}
41
- src={sb.get(product, 'images.0', require('../../assets/img/default.jpg'))}
44
+ src={sb.get(product, 'image', require('../../assets/img/default.jpg'))}
42
45
  />
43
46
  <div style='overflow: hidden !important;'>
44
- <div class='order_attachment__product_name'>{product.name}</div>
45
- <div class='order_attachment__product_desc'>{product.description || product.long_description}</div>
47
+ <div
48
+ class='order_attachment__product_name'
49
+ title={this.props.t.i18n(product.i18n_name) || product.name}
50
+ >
51
+ {this.props.t.i18n(product.i18n_name) || product.name}
52
+ </div>
53
+ <div class='order_attachment__product_desc'>{sb.getProductPropName(product, ' / ')}</div>
46
54
  <div class='order_attachment__product_desc'>
47
55
  {this.props.t('qty')} {item.quantity}&nbsp;·&nbsp;{sb.formatCurrency(product.price)}
48
56
  </div>
@@ -56,17 +64,15 @@ export default class ProductAttachments extends Com {
56
64
 
57
65
  let $total = (
58
66
  <div class='order_attachment__footer'>
59
- <div>{this.props.t('total')}</div>{' '}
60
- <div>
61
- <b>{sb.formatCurrency(order.total)}</b>
67
+ <div class='d-flex align-items-center justify-content-between'>
68
+ <div>{this.props.t('total')}</div>{' '}
69
+ <div>
70
+ <b class='order_attachment__total'>{sb.formatCurrency(order.total)}</b>
71
+ </div>
62
72
  </div>
63
73
  </div>
64
74
  )
65
75
 
66
- let addr = sb.get(order, 'shipping.address', {})
67
- let fulladdr = [addr.street, addr.ward, addr.district, addr.region, addr.country].filter((a) => a).join(', ')
68
- let address = fulladdr
69
-
70
76
  let phoneattr = sb.find(sb.get(order, 'user.attributes', []), (attr) => attr.key === 'phones') || {
71
77
  key: 'phones',
72
78
  text: '',
@@ -76,29 +82,116 @@ export default class ProductAttachments extends Com {
76
82
  if (phone) phone = this.props.t('phn') + ': ' + phone
77
83
 
78
84
  let $shipto = null
79
- if (address || phone) {
85
+ if (sb.displayOrderShippingAddress(order) || phone) {
80
86
  $shipto = (
81
87
  <div>
82
88
  <div class='order_attachment__label'>{this.props.t('ship_to')}</div>
83
- <div class='order_attachment__text_truncate'>{address}</div>
89
+ <div>{sb.displayOrderShippingAddress(order)}</div>
84
90
  <div class='order_attachment__text_truncate'>{phone}</div>
85
91
  </div>
86
92
  )
87
93
  }
88
94
 
95
+ let $beforeTax = null
96
+ if (order._discount_before_tax) {
97
+ $beforeTax = (
98
+ <div class='order_attachment__footer'>
99
+ <div class='d-flex align-items-center justify-content-between'>
100
+ <div class='order_attachment__label no_margin'>{this.props.t('subtotal_before_tax')}</div>{' '}
101
+ <div>{sb.formatCurrency(order._order_value)}</div>
102
+ </div>
103
+ <div class='d-flex align-items-center justify-content-between'>
104
+ <div class='order_attachment__label no_margin'>{this.props.t('discount_before_tax')}</div>{' '}
105
+ <div>-{sb.formatCurrency(order._discount_before_tax)}</div>
106
+ </div>
107
+ </div>
108
+ )
109
+ }
110
+
111
+ let $subtotal = (
112
+ <div class='d-flex align-items-center justify-content-between'>
113
+ <div class='order_attachment__label no_margin'>{this.props.t('subtotal')}</div>{' '}
114
+ <div style=''>{sb.formatCurrency(order.subtotal)}</div>
115
+ </div>
116
+ )
117
+ let $tax = null
118
+ if (order.total_tax) {
119
+ let taxname = ''
120
+ if (sb.size(order._taxM) === 1) {
121
+ let tax = sb.get(order._taxM, '0.tax', {})
122
+ if (tax.name && tax.percentage) taxname = `(${tax.name} ${Math.round(tax.percentage || 0) / 100}%)`
123
+ }
124
+ $tax = (
125
+ <div class='d-flex align-items-center justify-content-between'>
126
+ <div class='order_attachment__label no_margin'>
127
+ {this.props.t('tax')} {taxname}
128
+ </div>
129
+ <div style=''>{sb.formatCurrency(order.total_tax)}</div>
130
+ </div>
131
+ )
132
+ }
133
+ let $discount = null
134
+ if (order.discount_type === 'percentage' && order.discount_percentage) {
135
+ $discount = (
136
+ <div class='d-flex align-items-center justify-content-between'>
137
+ <div class='order_attachment__label no_margin'>
138
+ {this.props.t('discount_after_tax')} ({Math.round(order.discount_percentage / 100)}%)
139
+ </div>{' '}
140
+ <div style=''>-{sb.formatCurrency(order._computed_discount)}</div>
141
+ </div>
142
+ )
143
+ }
144
+ if (order.discount_type === 'amount' && order.discount_amount) {
145
+ $discount = (
146
+ <div class='d-flex align-items-center justify-content-between'>
147
+ <div class='order_attachment__label no_margin'>{this.props.t('discount_after_tax')}</div>{' '}
148
+ <div class='no-shrink'>-{sb.formatCurrency(order._computed_discount)}</div>
149
+ </div>
150
+ )
151
+ }
152
+ let $shipping = null
153
+ if (sb.get(order, 'shipping.nominal_fee')) {
154
+ $shipping = (
155
+ <div class='d-flex align-items-center justify-content-between'>
156
+ <div class='order_attachment__label no_margin'>{this.props.t('shipping_fee')}</div>{' '}
157
+ <div style=''>{sb.formatCurrency(sb.get(order, 'shipping.nominal_fee', 0))}</div>
158
+ </div>
159
+ )
160
+ }
161
+ let $adjustment = null
162
+ if (order.adjustment) {
163
+ $adjustment = (
164
+ <div class='d-flex align-items-center justify-content-between'>
165
+ <div class='order_attachment__label no_margin'>{order.adjustment_description}</div>{' '}
166
+ <div class='no-shrink'>{sb.formatCurrency(order.adjustment)}</div>
167
+ </div>
168
+ )
169
+ }
170
+
171
+ $subtotal = (
172
+ <div class='order_attachment__footer'>
173
+ {$subtotal}
174
+ {$tax}
175
+ {$shipping}
176
+ {$discount}
177
+ {$adjustment}
178
+ </div>
179
+ )
180
+ if (!$shipping && !$adjustment && !$discount && !$tax) {
181
+ $subtotal = null
182
+ }
183
+
89
184
  return (
90
185
  <div class={`order_attachment order_attachment__${byType}`}>
91
186
  <div class='order_attachment__header'>
92
187
  {this.props.t('order')}
93
- <span class='order_attachment__text_muted order_attachment__text_sm'>#{order.id}</span>
188
+ <span class='order_attachment__text_muted order_attachment__text_sm'>{sb.getOrderDisplayName(order)}</span>
94
189
  </div>
95
190
  {$products}
96
191
  {$shipto}
97
- <div>
98
- <div class='order_attachment__label'>{this.props.t('payment')}</div>
99
- <div>COD</div>
100
- </div>
101
192
  {$note}
193
+ {$beforeTax}
194
+ {$subtotal}
102
195
  {$total}
103
196
  </div>
104
197
  )
package/src/lib.js CHANGED
@@ -10,9 +10,9 @@ document.head.append($link)
10
10
  sb.preloadWebfonts(['Roboto'], [400, 500])
11
11
 
12
12
  class Widget {
13
- constructor(ApiURL, FileURL, RealtimeURL, inmem, fake, agenttoken) {
13
+ constructor(ApiURL, FileURL, RealtimeURL, inmem, fake) {
14
14
  this.domGate = new sb.Gate()
15
- this.store = new Store(ApiURL, FileURL, RealtimeURL, inmem, fake, agenttoken)
15
+ this.store = new Store(ApiURL, FileURL, RealtimeURL, inmem, fake)
16
16
 
17
17
  // expose store for debugging purpose
18
18
  window.sbz_store = this.store
@@ -37,6 +37,28 @@ class Widget {
37
37
  cb()
38
38
  }
39
39
 
40
+ async cleanLogin(user_id, tokenGenerator) {}
41
+
42
+ async login(accid, user_id, tokenGenerator, $container) {
43
+ let user = await this.store.login(accid, user_id, tokenGenerator)
44
+
45
+ if (!$container) $container = document.body
46
+ this.pluginMgr = new PluginMgr(this, $container)
47
+
48
+ let clean = setInterval(() => {
49
+ // check if the dom is deleted
50
+ if ($container && $container.isConnected) return
51
+ this.pluginMgr.destroy()
52
+ clearInterval(clean)
53
+ }, 1000)
54
+ this.domGate.open()
55
+ return user
56
+ }
57
+
58
+ logout(accid) {
59
+ return this.store.logout()
60
+ }
61
+
40
62
  async setAccount(id, $container, mask, cb) {
41
63
  let outmask = await this.store.init(id, mask)
42
64
  // add container to client site
package/src/store/api.js CHANGED
@@ -13,6 +13,8 @@ function jsonifyTextPlain(param, done) {
13
13
 
14
14
  export default class {
15
15
  constructor(ApiURL, FileURL) {
16
+ this.refreshLock = new sb.Mutex()
17
+
16
18
  this.FileURL = FileURL
17
19
  this.ApiURL = ApiURL
18
20
  this.apireq = ajax
@@ -23,7 +25,19 @@ export default class {
23
25
  .beforeHook(jsonifyTextPlain)
24
26
  .setParser('json')
25
27
  .beforeHook((param, done) => {
26
- // attachAccessToken
28
+ if (this.token_generator) {
29
+ let user_id = this.profile_id
30
+ this.token_generator().then((token) => {
31
+ if (user_id != this.profile_id) return done(true) // outdated
32
+
33
+ if (!token) return done(false)
34
+ if (token == 'error') return done(false)
35
+ param.request = param.request.addQuery('x-access-token', token)
36
+ return done(true)
37
+ })
38
+ return
39
+ }
40
+
27
41
  if (this.mask) param.request = param.request.addQuery('x-user-mask', this.mask)
28
42
  return done(true)
29
43
  })
@@ -40,6 +54,101 @@ export default class {
40
54
  this.uid = obj.uid
41
55
  }
42
56
 
57
+ async setTokenGenerator(accid, uid, tokenGenerator) {
58
+ this.tokensession = this.tokensession || 0
59
+ this.tokensession++
60
+ let tokensession = this.tokensession
61
+
62
+ if (!accid || !sb.startsWith(accid, 'ac')) return {error: 'invalid_account_id'}
63
+ if (!uid) return {error: 'invalid_user_id'}
64
+ if (typeof tokenGenerator !== 'function') return {error: 'invalid_token_generator'}
65
+
66
+ this.accid = accid
67
+ this.profile_id = uid
68
+ this.token_generator = async (force) => {
69
+ await this.refreshLock.lock()
70
+ if (tokensession != this.tokensession) {
71
+ this.refreshLock.unlock()
72
+ return // outdated
73
+ }
74
+ let age = (sb.Date.now() - (this.last_token_at || 0)) / 1000
75
+
76
+ // very good token, use this one
77
+ if (!force && this.last_token && age < 1800) {
78
+ this.refreshLock.unlock()
79
+ return this.last_token
80
+ }
81
+
82
+ if (!force && this.last_token && age < 3000) {
83
+ // the token is still good, but should get new one
84
+ if (this.token_generator)
85
+ setTimeout(async () => {
86
+ let token, err
87
+ try {
88
+ token = await tokenGenerator(uid)
89
+ } catch (e) {
90
+ err = e
91
+ }
92
+ if (tokensession != this.tokensession) {
93
+ this.refreshLock.unlock()
94
+ return // outdated
95
+ }
96
+
97
+ if (err) this.last_token = 'error'
98
+ else this.last_token = token
99
+ this.last_token_at = sb.Date.now()
100
+ this.refreshLock.unlock()
101
+ })
102
+ return this.last_token
103
+ }
104
+
105
+ // the token is outdated, must get new one
106
+ let token
107
+ let err
108
+ try {
109
+ token = await tokenGenerator(uid)
110
+ } catch (e) {
111
+ err = e
112
+ }
113
+
114
+ if (tokensession != this.tokensession) {
115
+ this.refreshLock.unlock()
116
+ return // outdated
117
+ }
118
+
119
+ if (err) this.last_token = 'error'
120
+ else this.last_token = token
121
+ this.last_token_at = sb.Date.now()
122
+ this.refreshLock.unlock()
123
+ return this.last_token
124
+ }
125
+
126
+ let outuser
127
+ for (let i = 0; true; i++) {
128
+ await this.token_generator(true)
129
+ if (tokensession != this.tokensession) break // outdated
130
+ outuser = await ajax
131
+ .setBaseUrl(this.ApiURL)
132
+ .withCredentials(true)
133
+ .addQuery('v', '6')
134
+ .addQuery('x-access-token', this.last_token)
135
+ .setParser('json')
136
+ .get(`/accounts/${accid}/users/${uid}`)
137
+ if (!sb.get(outuser, 'body.id')) {
138
+ let sleeptime = Math.min(2000 + i * 100, 120000)
139
+ await sb.sleep(sleeptime)
140
+ continue
141
+ }
142
+ break
143
+ }
144
+ if (outuser.error) {
145
+ if (sb.get(outuser.body.code)) return {error: outuser.body.code}
146
+ return outuser
147
+ }
148
+ this.uid = outuser.body.id
149
+ return outuser.body
150
+ }
151
+
43
152
  listConversations = (_) =>
44
153
  this.apireq
45
154
  .addQuery('user_id', this.uid)
@@ -119,26 +228,6 @@ export default class {
119
228
 
120
229
  getChallenge = (accid) => this.apireq.post(`/accounts/${accid}/scrypt-challenges`)
121
230
 
122
- async createAgentUser(agenttoken) {
123
- for (var i = 0; i < 10; i++) {
124
- let {body, error} = await ajax
125
- .setBaseUrl(this.ApiURL)
126
- .setContentType('text/plain')
127
- .beforeHook(jsonifyTextPlain)
128
- .setParser('json')
129
- .addQuery('x-access-token', agenttoken)
130
- .post(`/agent-users`)
131
- if (error) {
132
- await sb.sleep(1000)
133
- continue
134
- }
135
-
136
- let userid = body ? body.id || '' : ''
137
- if (!userid) return ''
138
- return body.account_id + '@' + userid
139
- }
140
- }
141
-
142
231
  async createUser(accid) {
143
232
  for (var i = 0; i < 10; i++) {
144
233
  const {body: challenge} = await this.getChallenge(accid)
@@ -16,11 +16,7 @@ const OK200 = Promise.resolve({code: 200})
16
16
  export default class {
17
17
  constructor(ApiURL, FileURL) {
18
18
  this.FileURL = FileURL
19
- this.apireq = ajax
20
- .setBaseUrl(ApiURL)
21
- .setContentType('text/plain')
22
- .beforeHook(jsonifyTextPlain)
23
- .setParser('json')
19
+ this.apireq = ajax.setBaseUrl(ApiURL).setContentType('text/plain').beforeHook(jsonifyTextPlain).setParser('json')
24
20
  }
25
21
 
26
22
  // for faking data (optional for real api)
@@ -28,6 +24,10 @@ export default class {
28
24
  this.agentid = agid
29
25
  }
30
26
 
27
+ setTokenGenerator(accid, uid, tokenGenerator) {
28
+ this.accid = accid
29
+ }
30
+
31
31
  setAuth(obj) {
32
32
  this.accid = obj.accid
33
33
  this.mask = obj.mask
@@ -66,7 +66,6 @@ export default class {
66
66
  getChallenge = (accid) => OK200
67
67
 
68
68
  createUser = (accid) => Promise.resolve({account_id: accid, id: 'usqzgtgfake', mask: 'fakemask'})
69
- createAgentUser = (agenttoken) => Promise.resolve('fakemask')
70
69
 
71
70
  time = (_) => Promise.resolve({code: 200, body: new sb.Date().toISOString()})
72
71
  updateUser = (data) => OK200
@@ -15,8 +15,7 @@ const page_loaded = sb.Date.now()
15
15
 
16
16
  // available localstorage key mask, user, hasManuallyClosed, isopen, close_preview_at, lastview, bot_run_logs, plugin_run_logs
17
17
  export default class Store {
18
- constructor(ApiURL, FileURL, RealtimeURL, inmem, fakeapi, agenttoken) {
19
- this.agent_token = agenttoken
18
+ constructor(ApiURL, FileURL, RealtimeURL, inmem, fakeapi) {
20
19
  this.g_sync = {}
21
20
  this.forceDevice = null
22
21
  this.forcePlugins = {}
@@ -318,18 +317,22 @@ export default class Store {
318
317
  matchHasManuallyClosed = () => !!this.kv.match('hasManuallyClosed') // true if user has ever closed the widget
319
318
  matchClosePreviewAt = () => this.kv.match('close_preview_at') || 0
320
319
 
321
- sendPresence = sb.throttle(async () => {
322
- await this.readyGate.entry()
323
- if (!sb.get(this.kv.match('user'), 'id')) return // store not init
324
- if (sb.isUserBanned(this.kv.match('user'))) return
325
- return this.api.sendPresence(
326
- this.device,
327
- this.browserVisibility === 'visible' && document.hasFocus(),
328
- this._currentViewingProduct.id,
329
- this._currentViewingProduct.source_id,
330
- this._currentViewingProduct.sku,
331
- )
332
- }, 1000)
320
+ sendPresence = sb.throttle(
321
+ async () => {
322
+ await this.readyGate.entry()
323
+ if (!sb.get(this.kv.match('user'), 'id')) return // store not init
324
+ if (sb.isUserBanned(this.kv.match('user'))) return
325
+ return this.api.sendPresence(
326
+ this.device,
327
+ this.browserVisibility === 'visible' && document.hasFocus(),
328
+ this._currentViewingProduct.id,
329
+ this._currentViewingProduct.source_id,
330
+ this._currentViewingProduct.sku,
331
+ )
332
+ },
333
+ 1000, // dont send presence update too tight
334
+ {leading: false}, // dont send right after load. should send after content_view
335
+ )
333
336
 
334
337
  now = () => sb.Date.now() + this.timeDiff // millisecond on server
335
338
 
@@ -354,9 +357,6 @@ export default class Store {
354
357
  // trust cookie, remove localstorage
355
358
  if (mask && localstorageMask && mask !== localstorageMask) this.clearBrowserData()
356
359
 
357
- // use this mask
358
- if (this.agent_token) mask = await this.api.createAgentUser(this.agent_token)
359
-
360
360
  var userId = ''
361
361
  if (mask) {
362
362
  let parts = mask.split('@')
@@ -396,6 +396,36 @@ export default class Store {
396
396
  return mask
397
397
  }
398
398
 
399
+ async login(accid, user_id, tokenGenerator) {
400
+ let user = await this.api.setTokenGenerator(accid, user_id, tokenGenerator)
401
+ if (user.error) return user
402
+
403
+ this.clearBrowserData()
404
+ this.readyGate.close()
405
+ accid = (accid || '').trim()
406
+
407
+ this.g_sync = {}
408
+ this.new_visitor = false // for checking returning or new visitor
409
+
410
+ if (this.realtime) this.realtime.stop()
411
+ if (this.fake) this.realtime = new RealtimeFake()
412
+ else this.realtime = new Realtime(this.RealtimeURL, {getAccessToken: () => this.api.token_generator()})
413
+
414
+ this.realtime.onEvent(this.onWs.bind(this))
415
+ this.realtime.onInterrupted(() => this.reconnectWs(accid, user.id))
416
+
417
+ this.seenLoop()
418
+ await this.reconnect()
419
+ this.readyGate.open()
420
+ this.reconnectWs(accid, user.id)
421
+
422
+ return user
423
+ }
424
+
425
+ logout(user_id) {
426
+ this.api.setTokenGenerator(undefined, undefined, undefined)
427
+ }
428
+
399
429
  seenQueue = [] // convoid will be seen
400
430
  seenLoop() {
401
431
  // periodically send seen
package/src/util/index.js CHANGED
@@ -18,6 +18,7 @@ import * as common from './common.js'
18
18
  sb = Object.assign(sb, common)
19
19
 
20
20
  sb.Gate = require('./gate.js').default
21
+ sb.Mutex = require('./mutex.js').default
21
22
 
22
23
  var op = require('./op.js')
23
24
  sb.get = op.get
@@ -31,4 +32,7 @@ sb = Object.assign(sb, extractor)
31
32
  import * as campaign from './campaign.js'
32
33
  sb = Object.assign(sb, campaign)
33
34
 
35
+ import * as order from './order.js'
36
+ sb = Object.assign(sb, order)
37
+
34
38
  export default sb
@@ -0,0 +1,27 @@
1
+ export default class Mutex {
2
+ constructor() {
3
+ this.isOpen = true
4
+ this.cbs = []
5
+ }
6
+
7
+ _check() {
8
+ if (!this.isOpen) return
9
+ let cb = this.cbs.pop()
10
+ if (!cb) return
11
+ this.isOpen = false
12
+ cb()
13
+ }
14
+
15
+ lock() {
16
+ return new Promise((resolve) => {
17
+ this.cbs.push(resolve)
18
+ this._check()
19
+ })
20
+ }
21
+
22
+ unlock() {
23
+ if (this.isOpen) return
24
+ this.isOpen = true
25
+ this._check()
26
+ }
27
+ }
@@ -0,0 +1,120 @@
1
+ const op = require('./op.js')
2
+ const common = require('./common')
3
+ op.filter = common.filter
4
+ op.map = common.map
5
+ op.each = common.each
6
+ op.size = common.size
7
+ op.find = common.find
8
+
9
+ export function getProductPropName(product, seperator = ' - ') {
10
+ let options = op.get(product, 'options', [])
11
+ let props = op.get(product, 'props', [])
12
+ let multiPropsOptions = op.filter(options, (option) => op.size(option.values) > 1)
13
+ let names = []
14
+ op.each(multiPropsOptions, (option) => {
15
+ let prop = op.find(props, (prop) => prop.key === option.key) || {}
16
+ if (prop.value) names.push(prop.value)
17
+ })
18
+ return names.join(seperator)
19
+ }
20
+
21
+ export function getOrderDisplayName(order = {}) {
22
+ return order.number || order.id || ''
23
+ }
24
+
25
+ export function displayOrderShippingAddress(order, isShorten) {
26
+ let addr = op.get(order, 'shipping.address', {})
27
+ let fulladdr = [addr.address, addr.ward, addr.district, addr.province]
28
+ .filter((a) => a)
29
+ .join(', ')
30
+ .trim()
31
+ if (isShorten) fulladdr = shortenAddr(fulladdr)
32
+ return fulladdr
33
+ }
34
+
35
+ function shortenAddr(str) {
36
+ str = str || ''
37
+ str = str.replace(/Hà Nội/gi, 'HN')
38
+ str = str.replace(/Hồ Chí Minh/gi, 'HCM')
39
+ str = str.replace(/Thành Phố/gi, 'TP')
40
+
41
+ return str
42
+ }
43
+
44
+ export function calcOrderTotal(order) {
45
+ // compute subtotal first, since it doesn't depend on tax or gloal discount
46
+ let subtotal = 0
47
+ let beforeTaxDiscount = 0
48
+ let ordervalue = 0
49
+ op.map(order.items, (item) => {
50
+ if (!item.product) return 0
51
+ let price = op.get(item, 'product.price', 0) * (item.quantity || 0)
52
+ ordervalue += price
53
+
54
+ // some products that are discounted before tax
55
+ // the discount amount is calculated in product price for
56
+ // simpler calucation in future
57
+ if (item.discount_type == 'percentage') {
58
+ if (item.discount_percentage > 0) {
59
+ beforeTaxDiscount += (price * item.discount_percentage) / 10000
60
+ price = price * (1 - (item.discount_percentage || 0) / 10000)
61
+ }
62
+ } else if (item.discount_type == 'amount') {
63
+ if (item.discount_amount > 0) {
64
+ beforeTaxDiscount += item.discount_amount
65
+ price = price - (item.discount_amount || 0)
66
+ if (price <= 0) price = 0
67
+ }
68
+ }
69
+ item.total = price
70
+ subtotal += price
71
+ })
72
+
73
+ // TAX
74
+ let taxM = {}
75
+ op.map(order.items, (item, i) => {
76
+ if (!item.product) return
77
+ let tax = item.product.tax
78
+ if (!tax) return
79
+ if (!tax.id) return
80
+ let taxprice = (item.total * tax.percentage) / 10000
81
+ item.total_tax = taxprice
82
+ taxM[i] = {tax: tax, taxprice: taxprice}
83
+ })
84
+
85
+ let totaltax = 0
86
+ op.map(taxM, (t) => (totaltax += t.taxprice))
87
+
88
+ let computed_discount = 0
89
+ if (order.discount_type == 'percentage' && order.discount_percentage > 0) {
90
+ computed_discount = ((subtotal + totaltax) * order.discount_percentage) / 10000
91
+ } else if (order.discount_type == 'amount') {
92
+ computed_discount = order.discount_amount || 0
93
+ }
94
+
95
+ // SHIP
96
+ let shippingfee = op.get(order, 'shipping.nominal_fee', 0)
97
+ if (shippingfee > 0) {
98
+ if (op.get(order, 'shipping.tax.id')) {
99
+ let tax = order.shipping.tax
100
+ let taxprice = shippingfee * (tax.percentage / 10000)
101
+ taxM['ship'] = {tax: tax, taxprice: taxprice}
102
+ order.shipping.total_tax = taxprice
103
+ }
104
+ }
105
+
106
+ totaltax = 0
107
+ op.map(taxM, (t) => (totaltax += t.taxprice))
108
+ let total = subtotal + totaltax - computed_discount + shippingfee
109
+
110
+ // add adjustment
111
+ total += order.adjustment || 0
112
+ if (total < 0) total = 0
113
+ order.subtotal = subtotal
114
+ order._computed_discount = computed_discount
115
+ order._taxM = taxM
116
+ order.total = total
117
+ order.total_tax = totaltax
118
+ order._discount_before_tax = beforeTaxDiscount
119
+ order._order_value = ordervalue
120
+ }