@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.
- package/assets/less/chatbox.less +8 -0
- package/assets/less/message.less +8 -3
- package/assets/less/order.less +10 -0
- package/compiled_src/bot.js +0 -11
- package/compiled_src/call/call.js +0 -61
- package/compiled_src/call/css.js +0 -2
- package/compiled_src/chatbox/AgentsAvatar.js +0 -16
- package/compiled_src/chatbox/AudioAttachments.js +0 -14
- package/compiled_src/chatbox/ButtonAttachments.js +1 -14
- package/compiled_src/chatbox/Buttons.js +0 -13
- package/compiled_src/chatbox/ChatButton.js +1 -78
- package/compiled_src/chatbox/DropdownSelect.js +3 -44
- package/compiled_src/chatbox/Emoji.js +0 -7
- package/compiled_src/chatbox/Event.js +0 -59
- package/compiled_src/chatbox/Events.js +4 -44
- package/compiled_src/chatbox/FileAttachments.js +0 -14
- package/compiled_src/chatbox/FormAttachment.js +1 -52
- package/compiled_src/chatbox/FormAttachments.js +0 -14
- package/compiled_src/chatbox/FormSubmitAttachments.js +0 -11
- package/compiled_src/chatbox/GenericTemplateAttachments.js +1 -20
- package/compiled_src/chatbox/Header.js +1 -46
- package/compiled_src/chatbox/ImageAttachments.js +0 -16
- package/compiled_src/chatbox/InfoMessage.js +0 -13
- package/compiled_src/chatbox/ListField.js +0 -16
- package/compiled_src/chatbox/MessageInput.js +1 -47
- package/compiled_src/chatbox/Order.js +0 -35
- package/compiled_src/chatbox/OrderAttachments.js +98 -39
- package/compiled_src/chatbox/PoweredBy.js +0 -7
- package/compiled_src/chatbox/ProductAttachments.js +0 -19
- package/compiled_src/chatbox/QuickReplies.js +0 -18
- package/compiled_src/chatbox/Rating.js +0 -37
- package/compiled_src/chatbox/ShadowDomRoot.js +0 -9
- package/compiled_src/chatbox/TextMessage.js +0 -64
- package/compiled_src/chatbox/Typing.js +0 -15
- package/compiled_src/chatbox/VideoAttachments.js +0 -14
- package/compiled_src/chatbox/Widget.js +6 -167
- package/compiled_src/chatbox/css.js +0 -2
- package/compiled_src/chatbox/dragscroll.js +1 -5
- package/compiled_src/chatbox/main.js +1 -40
- package/compiled_src/chatbox/releaseAnimation.js +0 -11
- package/compiled_src/chatbox/spinner.js +0 -10
- package/compiled_src/chatbox/time.js +0 -22
- package/compiled_src/chatbox/touchscroll.js +0 -7
- package/compiled_src/common.js +0 -22
- package/compiled_src/contact/contact.js +2 -52
- package/compiled_src/contact/css.js +0 -2
- package/compiled_src/lib.js +19 -46
- package/compiled_src/notif/css.js +0 -2
- package/compiled_src/notif/notif.js +0 -63
- package/compiled_src/order/address_picker.js +4 -38
- package/compiled_src/order/icon.js +0 -16
- package/compiled_src/order/index.js +1 -34
- package/compiled_src/order/order.js +2 -156
- package/compiled_src/plugin.js +1 -77
- package/compiled_src/popup/countdown.js +2 -33
- package/compiled_src/popup/css.js +0 -2
- package/compiled_src/popup/popup.js +2 -165
- package/compiled_src/popup/tick.core.commonjs.js +72 -222
- package/compiled_src/popup/tick.view.flip.commonjs.js +23 -31
- package/compiled_src/store/api.js +99 -65
- package/compiled_src/store/api_fake.js +3 -39
- package/compiled_src/store/realtime_fake.js +0 -6
- package/compiled_src/store/store.js +46 -249
- package/compiled_src/util/campaign.js +0 -13
- package/compiled_src/util/common.js +15 -185
- package/compiled_src/util/common.test.js +0 -4
- package/compiled_src/util/extractor.js +1 -21
- package/compiled_src/util/extractor.test.js +0 -3
- package/compiled_src/util/gate.js +0 -7
- package/compiled_src/util/gate.test.js +1 -2
- package/compiled_src/util/index.js +3 -7
- package/compiled_src/util/mutex.js +31 -0
- package/compiled_src/util/op.js +1 -14
- package/compiled_src/util/order.js +118 -0
- package/compiled_src/util/pubsub.js +0 -6
- package/compiled_src/util/pubsub.test.js +0 -2
- package/package.json +12 -13
- package/src/chatbox/OrderAttachments.js +111 -18
- package/src/lib.js +24 -2
- package/src/store/api.js +110 -21
- package/src/store/api_fake.js +5 -6
- package/src/store/store.js +47 -17
- package/src/util/index.js +4 -0
- package/src/util/mutex.js +27 -0
- package/src/util/order.js +120 -0
- package/dist/132.js +0 -7092
- package/dist/178.js +0 -406
- package/dist/18364d15f37.132.js +0 -1
- package/dist/18364d15f37.178.js +0 -1
- package/dist/18364d15f37.213.js +0 -1
- package/dist/18364d15f37.335.js +0 -1
- package/dist/18364d15f37.362.js +0 -1
- package/dist/18364d15f37.441.js +0 -1
- package/dist/18364d15f37.605.js +0 -1
- package/dist/18364d15f37.678.js +0 -1
- package/dist/18364d15f37.782.js +0 -1
- package/dist/18364d15f37.app.js +0 -1
- package/dist/18364d15f37.index.html +0 -26
- package/dist/18364d15f37.library.html +0 -26
- package/dist/18364d15f37.library.js +0 -1
- package/dist/18364d15f37.preview.html +0 -759
- package/dist/18364d15f37.preview.js +0 -1
- package/dist/18364d15f37.screenshoot.js +0 -1
- package/dist/213.js +0 -3215
- package/dist/335.js +0 -411
- package/dist/362.js +0 -107
- package/dist/441.js +0 -2834
- package/dist/605.js +0 -328
- package/dist/678.js +0 -107
- package/dist/782.js +0 -107
- package/dist/HASH +0 -1
- package/dist/assets/img/Facebook.64607ec.png +0 -0
- package/dist/assets/img/Zalo.6845707.png +0 -0
- package/dist/assets/img/award1a.1a58e10.png +0 -0
- package/dist/assets/img/award3a.3b74423.png +0 -0
- package/dist/assets/img/award4a.ed6ccd4.png +0 -0
- package/dist/assets/img/award5.39eb7ae.png +0 -0
- package/dist/assets/img/bubble_default.7d5e4ab.svg +0 -20
- package/dist/assets/img/caret.998eb8f.svg +0 -1
- package/dist/assets/img/convos_empty.2353007.svg +0 -72
- package/dist/assets/img/default.06e6ee0.jpg +0 -0
- package/dist/assets/img/default_avatar.5b74dc1.png +0 -0
- package/dist/assets/img/done_gray.d7e8203.svg +0 -4
- package/dist/assets/img/done_green.b817fd4.svg +0 -4
- package/dist/assets/img/emoji/messenger/sub_23.529_58.824.d5faf14.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_23.529_60.784.1420241.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_25.490_0.000.1e6b2c0.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_25.490_45.098.3dfbc08.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_25.490_5.882.4b49e94.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_27.451_72.549.7114f58.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_27.451_84.314.231e2b7.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_27.451_96.078.ddbb008.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_29.412_5.882.61f3359.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_100.000.6df72bc.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_45.098.3d7a7ad.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_47.059.0f4d6e2.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_54.902.65724c1.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_64.706.9da3cba.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_72.549.edca5cf.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_78.431.f910f2a.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_88.235.def4e71.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_11.765.49c03d0.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_27.451.46b54e5.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_29.412.e36d284.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_3.922.bb191b0.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_35.294.af7b927.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_7.843.98f2a99.png +0 -0
- package/dist/assets/img/empty.29bed2d.svg +0 -23
- package/dist/assets/img/empty_1.0b98af0.svg +0 -11
- package/dist/assets/img/examplte_logo.f86535f.svg +0 -1
- package/dist/assets/img/external-link.90577cc.svg +0 -1
- package/dist/assets/img/fb_inverse_circle.36c2cfc.png +0 -0
- package/dist/assets/img/ghn.070f8e3.svg +0 -17
- package/dist/assets/img/ghtk.326eac1.svg +0 -18
- package/dist/assets/img/image_default.be0698b.png +0 -0
- package/dist/assets/img/info.ebce7ea.svg +0 -1
- package/dist/assets/img/loader.37000a7.svg +0 -1
- package/dist/assets/img/loading_static.8f403ae.svg +0 -10
- package/dist/assets/img/main-service1.d536cc2.png +0 -0
- package/dist/assets/img/main-service2.9b99549.png +0 -0
- package/dist/assets/img/not_found_search.2ddb8d1.png +0 -0
- package/dist/assets/img/package.8d37d0f.svg +0 -5
- package/dist/assets/img/package_fade.763c001.svg +0 -1
- package/dist/assets/img/rating--average.1ea492c.svg +0 -12
- package/dist/assets/img/rating--great.e3d712e.svg +0 -16
- package/dist/assets/img/rating--poor.2938a25.svg +0 -12
- package/dist/assets/img/search.f9705ff.svg +0 -1
- package/dist/assets/img/send.6db52a9.svg +0 -3
- package/dist/assets/img/shop.f7dc7d8.svg +0 -1
- package/dist/assets/img/shop_faded.df7845a.svg +0 -1
- package/dist/assets/img/subiz-mono.877e9f5.svg +0 -3
- package/dist/assets/img/x.26fdfa3.svg +0 -5
- package/dist/malicious-app.js +0 -13
- package/dist/screenshoot.html +0 -7
- 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='
|
|
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, '
|
|
44
|
+
src={sb.get(product, 'image', require('../../assets/img/default.jpg'))}
|
|
42
45
|
/>
|
|
43
46
|
<div style='overflow: hidden !important;'>
|
|
44
|
-
<div
|
|
45
|
-
|
|
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} · {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
|
|
60
|
-
|
|
61
|
-
<
|
|
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 (
|
|
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
|
|
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'
|
|
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
|
|
13
|
+
constructor(ApiURL, FileURL, RealtimeURL, inmem, fake) {
|
|
14
14
|
this.domGate = new sb.Gate()
|
|
15
|
-
this.store = new Store(ApiURL, FileURL, RealtimeURL, inmem, fake
|
|
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
|
-
|
|
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)
|
package/src/store/api_fake.js
CHANGED
|
@@ -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
|
package/src/store/store.js
CHANGED
|
@@ -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
|
|
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(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
+
}
|