@subiz/widget-v4 4.1.214 → 4.1.216
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/api.md +24 -8
- package/assets/img/bubble_default.svg +1 -0
- package/assets/less/widget.less +12 -0
- package/compiled_src/chatbox/ChatButton.js +19 -17
- package/compiled_src/lib.js +2 -15
- package/compiled_src/store/api.js +0 -1
- package/compiled_src/store/store.js +68 -1
- package/dist/{1a12a372880.278.js → 1a12ecfb6db.278.js} +1 -1
- package/dist/{1a12a372880.webview.js → 1a12ecfb6db.app.js} +1 -1
- package/dist/{1a12a372880.chat.html → 1a12ecfb6db.chat.html} +1 -1
- package/dist/1a12ecfb6db.chat.js +1 -0
- package/dist/{1a12a372880.form.html → 1a12ecfb6db.form.html} +1 -1
- package/dist/1a12ecfb6db.form.js +1 -0
- package/dist/{1a12a372880.index.html → 1a12ecfb6db.index.html} +1 -1
- package/dist/{1a12a372880.library.html → 1a12ecfb6db.library.html} +1 -1
- package/dist/1a12ecfb6db.library.js +1 -0
- package/dist/{1a12a372880.preview.html → 1a12ecfb6db.preview.html} +1 -1
- package/dist/1a12ecfb6db.preview.js +1 -0
- package/dist/1a12ecfb6db.webview.js +1 -0
- package/dist/HASH +1 -0
- package/dist/assets/img/{bubble_default.7d5e4ab.svg → bubble_default.6f6eec8.svg} +1 -0
- package/dist/screenshoot.js +90 -35
- package/package.json +1 -1
- package/src/chatbox/ChatButton.js +22 -20
- package/src/lib.js +2 -18
- package/src/store/api.js +0 -3
- package/src/store/store.js +52 -0
- package/up +18 -0
- package/dist/.HASH +0 -1
- package/dist/1a12a372880.app.js +0 -1
- package/dist/1a12a372880.chat.js +0 -1
- package/dist/1a12a372880.form.js +0 -1
- package/dist/1a12a372880.library.js +0 -1
- package/dist/1a12a372880.preview.js +0 -1
- /package/dist/{1a12a372880.127.js → 1a12ecfb6db.127.js} +0 -0
- /package/dist/{1a12a372880.132.js → 1a12ecfb6db.132.js} +0 -0
- /package/dist/{1a12a372880.185.js → 1a12ecfb6db.185.js} +0 -0
- /package/dist/{1a12a372880.27.js → 1a12ecfb6db.27.js} +0 -0
- /package/dist/{1a12a372880.339.js → 1a12ecfb6db.339.js} +0 -0
- /package/dist/{1a12a372880.459.js → 1a12ecfb6db.459.js} +0 -0
- /package/dist/{1a12a372880.490.js → 1a12ecfb6db.490.js} +0 -0
package/package.json
CHANGED
|
@@ -391,6 +391,7 @@ export default class ChatButton extends Com {
|
|
|
391
391
|
|
|
392
392
|
let imgurl = sb.get(accountSetting, 'desktop_button.background_image_url')
|
|
393
393
|
let maskurl = sb.get(accountSetting, 'desktop_button.background_mask_url')
|
|
394
|
+
// Keep configured dimensions even when the image fails; default only missing dimensions to 70px.
|
|
394
395
|
let imgwidth = sb.get(accountSetting, 'desktop_button.width') || 70
|
|
395
396
|
let imgheight = sb.get(accountSetting, 'desktop_button.height') || 70
|
|
396
397
|
|
|
@@ -450,18 +451,10 @@ export default class ChatButton extends Com {
|
|
|
450
451
|
appearance.maskAsset ? prepareButtonMask(appearance.maskAsset) : Promise.resolve(null),
|
|
451
452
|
])
|
|
452
453
|
const stale = () => !this._isMounted || this._buttonImageVersion !== version
|
|
453
|
-
if (stale())
|
|
454
|
-
if (mask) URL.revokeObjectURL(mask)
|
|
455
|
-
return
|
|
456
|
-
}
|
|
454
|
+
if (stale()) return
|
|
457
455
|
const fallback = loaded.some((success) => !success) || (!!appearance.maskAsset && !mask)
|
|
458
456
|
const ready = !fallback || (await preloadButtonImage(defaultBubble))
|
|
459
|
-
if (stale())
|
|
460
|
-
if (mask) URL.revokeObjectURL(mask)
|
|
461
|
-
return
|
|
462
|
-
}
|
|
463
|
-
if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl)
|
|
464
|
-
this._buttonMaskUrl = mask
|
|
457
|
+
if (stale()) return
|
|
465
458
|
// Commit the hidden style before revealing even when images are cached.
|
|
466
459
|
// Otherwise older browsers can paint only the final state and skip the transition.
|
|
467
460
|
if (this._buttonElement) window.getComputedStyle(this._buttonElement).transform
|
|
@@ -470,7 +463,6 @@ export default class ChatButton extends Com {
|
|
|
470
463
|
}
|
|
471
464
|
|
|
472
465
|
componentWillUnmount() {
|
|
473
|
-
if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl)
|
|
474
466
|
if (this._animateTimeout) clearTimeout(this._animateTimeout)
|
|
475
467
|
}
|
|
476
468
|
|
|
@@ -478,12 +470,13 @@ export default class ChatButton extends Com {
|
|
|
478
470
|
if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_button = sb.Date.now()
|
|
479
471
|
const appearance = this.getButtonAppearance()
|
|
480
472
|
let {imgsrc, imgurl, maskurl, background, position, imgwidth, imgheight} = appearance
|
|
473
|
+
const fallback = this.state.readyImageKey === appearance.key && this.state.fallbackButtonImage
|
|
481
474
|
// Never attach a remote CSS mask while the local copy is being prepared.
|
|
482
475
|
if (appearance.maskAsset) {
|
|
483
476
|
maskurl = this.state.readyImageKey === appearance.key ? this.state.buttonMaskUrl : ''
|
|
484
477
|
if (imgsrc === appearance.maskAsset) imgsrc = maskurl || ''
|
|
485
478
|
}
|
|
486
|
-
if (
|
|
479
|
+
if (fallback) {
|
|
487
480
|
imgsrc = imgurl = defaultBubble
|
|
488
481
|
maskurl = ''
|
|
489
482
|
}
|
|
@@ -548,7 +541,12 @@ export default class ChatButton extends Com {
|
|
|
548
541
|
`
|
|
549
542
|
}
|
|
550
543
|
|
|
544
|
+
if (fallback) {
|
|
545
|
+
imgcontainerstyle += `width: ${imgwidth}px !important; height: ${imgheight}px !important;`
|
|
546
|
+
}
|
|
547
|
+
|
|
551
548
|
let cls = 'chat-button chat-button--' + position
|
|
549
|
+
if (fallback) cls += ' chat-button--fallback'
|
|
552
550
|
// dont render till all image is loaded
|
|
553
551
|
|
|
554
552
|
let whiteBgStyle = `
|
|
@@ -679,18 +677,22 @@ function preloadButtonImage(src, {crossOrigin = false} = {}) {
|
|
|
679
677
|
})
|
|
680
678
|
}
|
|
681
679
|
|
|
682
|
-
//
|
|
683
|
-
//
|
|
680
|
+
// Pin the mask bytes with a data URL: some sites allow data: images but block blob:.
|
|
681
|
+
// If inline images or fetch are blocked, preload the original URL with CORS for CSS masks.
|
|
684
682
|
async function prepareButtonMask(src) {
|
|
685
|
-
let url
|
|
686
683
|
try {
|
|
687
684
|
const response = await fetch(src)
|
|
688
685
|
if (!response.ok) return null
|
|
689
|
-
|
|
690
|
-
|
|
686
|
+
const blob = await response.blob()
|
|
687
|
+
const url = await new Promise((resolve) => {
|
|
688
|
+
const reader = new FileReader()
|
|
689
|
+
reader.onload = () => resolve(reader.result)
|
|
690
|
+
reader.onerror = reader.onabort = () => resolve('')
|
|
691
|
+
reader.readAsDataURL(blob)
|
|
692
|
+
})
|
|
693
|
+
if (url && await preloadButtonImage(url)) return url
|
|
691
694
|
} catch (_) {
|
|
692
|
-
//
|
|
695
|
+
// A blocked fetch or failed conversion does not mean the original image is unavailable.
|
|
693
696
|
}
|
|
694
|
-
|
|
695
|
-
return null
|
|
697
|
+
return await preloadButtonImage(src, {crossOrigin: true}) ? src : null
|
|
696
698
|
}
|
package/src/lib.js
CHANGED
|
@@ -235,13 +235,13 @@ class Widget {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
const payload = formRequest(formid, values, options)
|
|
238
|
-
return
|
|
238
|
+
return this.store.submitWebsiteForm(formid, payload)
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
async getFormStatus(formid) {
|
|
242
242
|
validateWebsiteFormId(formid)
|
|
243
243
|
await this.store.readyGate.entry()
|
|
244
|
-
return
|
|
244
|
+
return this.store.getFormStatus(formid)
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
// req.bot_id != "" -> start conversation with bot id
|
|
@@ -418,19 +418,3 @@ function formRequest(formid, fields, options = {}) {
|
|
|
418
418
|
}),
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
|
-
|
|
422
|
-
function formResult(response, operation) {
|
|
423
|
-
const body = response && response.body
|
|
424
|
-
const detail = (response && response.error) || (body && body.error)
|
|
425
|
-
const status = response && response.code
|
|
426
|
-
const valid = operation === 'submit'
|
|
427
|
-
? body && typeof body.conversation_id === 'string' && body.conversation_id && typeof body.submission_id === 'string' && body.submission_id
|
|
428
|
-
: body && typeof body.submitted === 'boolean' && typeof body.status === 'string'
|
|
429
|
-
if (detail || !(status >= 200 && status < 300) || !valid) {
|
|
430
|
-
const error = new Error('Subiz form request failed')
|
|
431
|
-
error.code = (detail && detail.code) || 'form_request_failed'
|
|
432
|
-
error.status = status
|
|
433
|
-
throw error
|
|
434
|
-
}
|
|
435
|
-
return body
|
|
436
|
-
}
|
package/src/store/api.js
CHANGED
|
@@ -277,9 +277,6 @@ export default class {
|
|
|
277
277
|
submitWebsiteForm = (formid, payload) => this.websiteFormRequest()
|
|
278
278
|
.post(`/accounts/${this.accid}/external-forms/${encodeURIComponent(formid)}/submissions`, payload)
|
|
279
279
|
|
|
280
|
-
getWebsiteFormStatus = (formid) => this.websiteFormRequest()
|
|
281
|
-
.get(`/accounts/${this.accid}/external-forms/${encodeURIComponent(formid)}/status`)
|
|
282
|
-
|
|
283
280
|
submitForm = (form_id, page_url, device, data, user) =>
|
|
284
281
|
this.apireq
|
|
285
282
|
.addQuery('x-referer', sb.get(location, 'origin', ''))
|
package/src/store/store.js
CHANGED
|
@@ -955,6 +955,37 @@ export default class Store {
|
|
|
955
955
|
}
|
|
956
956
|
}
|
|
957
957
|
|
|
958
|
+
async submitWebsiteForm(formid, payload) {
|
|
959
|
+
await this.readyGate.entry()
|
|
960
|
+
const key = websiteFormStorageKey(this.api, formid)
|
|
961
|
+
const kv = this.kv
|
|
962
|
+
const values = sb.cloneDeep(payload.values)
|
|
963
|
+
const result = formResult(await this.api.submitWebsiteForm(formid, payload))
|
|
964
|
+
try {
|
|
965
|
+
kv.put(key, {conversation_id: result.conversation_id, values, expires_at: sb.Date.now() + 7 * 24 * 60 * 60 * 1000})
|
|
966
|
+
} catch (_) { /* Storage failure must not make callers retry a successful submission. */ }
|
|
967
|
+
return result
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
getFormStatus(formid) {
|
|
971
|
+
const key = websiteFormStorageKey(this.api, formid)
|
|
972
|
+
try {
|
|
973
|
+
const receipt = this.kv.match(key)
|
|
974
|
+
if (!receipt) return {submitted: false, status: 'not_found'}
|
|
975
|
+
if (!Number.isFinite(receipt.expires_at) || typeof receipt.conversation_id !== 'string' || !receipt.conversation_id) {
|
|
976
|
+
this.kv.del(key)
|
|
977
|
+
return {submitted: false, status: 'not_found'}
|
|
978
|
+
}
|
|
979
|
+
if (receipt.expires_at <= sb.Date.now()) {
|
|
980
|
+
this.kv.del(key)
|
|
981
|
+
return {submitted: false, status: 'expired'}
|
|
982
|
+
}
|
|
983
|
+
return {submitted: true, status: 'found', conversation_id: receipt.conversation_id, values: Array.isArray(receipt.values) ? sb.cloneDeep(receipt.values) : []}
|
|
984
|
+
} catch (_) {
|
|
985
|
+
return {submitted: false, status: 'storage_unavailable'}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
958
989
|
async submitForm(formid, data, user) {
|
|
959
990
|
await this.readyGate.entry()
|
|
960
991
|
if (user) await this.setUser(user)
|
|
@@ -1117,6 +1148,9 @@ export default class Store {
|
|
|
1117
1148
|
async _onWs(ev) {
|
|
1118
1149
|
if (this._die) return
|
|
1119
1150
|
if (!ev || !ev.type) return
|
|
1151
|
+
// Website form conversations are handled in the agent inbox, not the chat widget.
|
|
1152
|
+
// Ignore their realtime events before an unknown conversation triggers a GET.
|
|
1153
|
+
if (sb.get(ev, 'touchpoint.channel') === 'form' || sb.get(ev, 'data.conversation.touchpoint.channel') === 'form') return
|
|
1120
1154
|
ev.created = sb.getMs(ev.created)
|
|
1121
1155
|
if (ev.type === 'user_info_updated') {
|
|
1122
1156
|
return
|
|
@@ -1916,3 +1950,21 @@ var xhrsend = function (method, url, body, cb) {
|
|
|
1916
1950
|
}
|
|
1917
1951
|
|
|
1918
1952
|
const dieErr = new Error('sdk unloaded')
|
|
1953
|
+
|
|
1954
|
+
function websiteFormStorageKey(api, formid) {
|
|
1955
|
+
return 'sbz_form_submitted:' + sb.stringify([api.accid, api.uid, formid])
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
function formResult(response) {
|
|
1959
|
+
const body = response && response.body
|
|
1960
|
+
const detail = (response && response.error) || (body && body.error)
|
|
1961
|
+
const status = response && response.code
|
|
1962
|
+
const valid = body && typeof body.conversation_id === 'string' && body.conversation_id && typeof body.submission_id === 'string' && body.submission_id
|
|
1963
|
+
if (detail || !(status >= 200 && status < 300) || !valid) {
|
|
1964
|
+
const error = new Error('Subiz form request failed')
|
|
1965
|
+
error.code = (detail && detail.code) || 'form_request_failed'
|
|
1966
|
+
error.status = status
|
|
1967
|
+
throw error
|
|
1968
|
+
}
|
|
1969
|
+
return body
|
|
1970
|
+
}
|
package/up
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
set -e # exit when any command fails
|
|
3
3
|
|
|
4
|
+
# The storage client still references ssl.PROTOCOL_TLSv1; the local Python 3.14
|
|
5
|
+
# uses OpenSSL 4, which removed the underlying legacy API.
|
|
6
|
+
# Respect an explicit interpreter; otherwise prefer a compatible installed Python.
|
|
7
|
+
if [ -z "${CLOUDSDK_PYTHON:-}" ]; then
|
|
8
|
+
for python in python3.13 python3.12 python3.11 python3.10; do
|
|
9
|
+
if command -v "$python" >/dev/null 2>&1; then
|
|
10
|
+
CLOUDSDK_PYTHON=$(command -v "$python")
|
|
11
|
+
export CLOUDSDK_PYTHON
|
|
12
|
+
break
|
|
13
|
+
fi
|
|
14
|
+
done
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Fail before rebuilding if gcloud cannot load the upload command.
|
|
18
|
+
gcloud storage cp --help >/dev/null
|
|
19
|
+
|
|
4
20
|
# safety first
|
|
5
21
|
echo "Clean"
|
|
6
22
|
#rm -rf node_modules && rm -rf package-lock.json && npm i --no-optional
|
|
@@ -17,8 +33,10 @@ echo "Build OK. New version hash: $(cat dist/HASH)"
|
|
|
17
33
|
# DEPLOY TO WIDGET SERVER
|
|
18
34
|
# (do not deploy dist/HASH)
|
|
19
35
|
mv dist/HASH dist/.HASH
|
|
36
|
+
trap 'if [ -f dist/.HASH ]; then mv dist/.HASH dist/HASH; fi' 0
|
|
20
37
|
gcloud storage cp -r --cache-control="public, max-age=31536000" --gzip-local="html,js,css,svg,jpg,png" dist/* gs://public-gcs.subiz-cdn.com/widget-v4/public/
|
|
21
38
|
mv dist/.HASH dist/HASH
|
|
39
|
+
trap - 0
|
|
22
40
|
|
|
23
41
|
if [ "$1" = 'prod' ]; then
|
|
24
42
|
gcloud storage cp --cache-control="public, max-age=10" --content-type="text/plain" dist/HASH gs://public-gcs.subiz-cdn.com/widget-v4/public/
|
package/dist/.HASH
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1a12a372880
|