@subiz/widget-v4 4.1.213 → 4.1.215
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/api.md +69 -0
- package/compiled_src/lib.js +39 -30
- package/compiled_src/store/api.js +2 -0
- package/compiled_src/store/store.js +65 -1
- package/dist/.HASH +1 -0
- package/dist/{1a1101c351b.app.js → 1a12a5ed6a9.app.js} +1 -1
- package/dist/{1a1101c351b.chat.html → 1a12a5ed6a9.chat.html} +1 -1
- package/dist/{1a1101c351b.chat.js → 1a12a5ed6a9.chat.js} +1 -1
- package/dist/{1a1101c351b.form.html → 1a12a5ed6a9.form.html} +1 -1
- package/dist/1a12a5ed6a9.form.js +1 -0
- package/dist/{1a1101c351b.index.html → 1a12a5ed6a9.index.html} +1 -1
- package/dist/{1a1101c351b.library.html → 1a12a5ed6a9.library.html} +1 -1
- package/dist/{1a1101c351b.library.js → 1a12a5ed6a9.library.js} +1 -1
- package/dist/{1a1101c351b.preview.html → 1a12a5ed6a9.preview.html} +1 -1
- package/dist/{1a1101c351b.preview.js → 1a12a5ed6a9.preview.js} +1 -1
- package/dist/{1a1101c351b.webview.js → 1a12a5ed6a9.webview.js} +1 -1
- package/dist/screenshoot.js +104 -29
- package/package.json +1 -1
- package/src/lib.js +38 -24
- package/src/store/api.js +6 -0
- package/src/store/store.js +51 -0
- package/dist/1a1101c351b.form.js +0 -1
- package/dist/HASH +0 -1
- /package/dist/{1a1101c351b.127.js → 1a12a5ed6a9.127.js} +0 -0
- /package/dist/{1a1101c351b.132.js → 1a12a5ed6a9.132.js} +0 -0
- /package/dist/{1a1101c351b.185.js → 1a12a5ed6a9.185.js} +0 -0
- /package/dist/{1a1101c351b.27.js → 1a12a5ed6a9.27.js} +0 -0
- /package/dist/{1a1101c351b.278.js → 1a12a5ed6a9.278.js} +0 -0
- /package/dist/{1a1101c351b.339.js → 1a12a5ed6a9.339.js} +0 -0
- /package/dist/{1a1101c351b.459.js → 1a12a5ed6a9.459.js} +0 -0
- /package/dist/{1a1101c351b.490.js → 1a12a5ed6a9.490.js} +0 -0
package/dist/screenshoot.js
CHANGED
|
@@ -4586,6 +4586,8 @@ let trackResponseFn = (param, done) => {
|
|
|
4586
4586
|
_defineProperty(this, "getConversation", cid => this.apireq.get(`/accounts/${this.accid}/conversations/${cid}`));
|
|
4587
4587
|
_defineProperty(this, "getLocaleContent", locale => this.apireq.get(`/accounts/${this.accid}/languages/${locale}`));
|
|
4588
4588
|
_defineProperty(this, "sendEvent", data => this.apireq.addQuery('shorten', true).addQuery('x-referer', util/* default */.A.get(location, 'origin', '')).post(`/accounts/${this.accid}/users/${this.uid}/events`, data));
|
|
4589
|
+
_defineProperty(this, "websiteFormRequest", () => this.apireq.setBaseUrl(this.ApiURL.replace(/\/4\.[01]\/?$/, '/4.1')));
|
|
4590
|
+
_defineProperty(this, "submitWebsiteForm", (formid, payload) => this.websiteFormRequest().post(`/accounts/${this.accid}/external-forms/${encodeURIComponent(formid)}/submissions`, payload));
|
|
4589
4591
|
_defineProperty(this, "submitForm", (form_id, page_url, device, data, user) => this.apireq.addQuery('x-referer', util/* default */.A.get(location, 'origin', '')).post(`/accounts/${this.accid}/users/${this.uid}/events`, {
|
|
4590
4592
|
by: {
|
|
4591
4593
|
id: this.uid,
|
|
@@ -6577,6 +6579,53 @@ class Store {
|
|
|
6577
6579
|
this.reconnectGate.open();
|
|
6578
6580
|
}
|
|
6579
6581
|
}
|
|
6582
|
+
async submitWebsiteForm(formid, payload) {
|
|
6583
|
+
await this.readyGate.entry();
|
|
6584
|
+
const key = websiteFormStorageKey(this.api, formid);
|
|
6585
|
+
const kv = this.kv;
|
|
6586
|
+
const result = formResult(await this.api.submitWebsiteForm(formid, payload));
|
|
6587
|
+
try {
|
|
6588
|
+
kv.put(key, {
|
|
6589
|
+
conversation_id: result.conversation_id,
|
|
6590
|
+
expires_at: util/* default */.A.Date.now() + 7 * 24 * 60 * 60 * 1000
|
|
6591
|
+
});
|
|
6592
|
+
} catch (_) {}
|
|
6593
|
+
return result;
|
|
6594
|
+
}
|
|
6595
|
+
getFormStatus(formid) {
|
|
6596
|
+
const key = websiteFormStorageKey(this.api, formid);
|
|
6597
|
+
try {
|
|
6598
|
+
const receipt = this.kv.match(key);
|
|
6599
|
+
if (!receipt) return {
|
|
6600
|
+
submitted: false,
|
|
6601
|
+
status: 'not_found'
|
|
6602
|
+
};
|
|
6603
|
+
if (!Number.isFinite(receipt.expires_at) || typeof receipt.conversation_id !== 'string' || !receipt.conversation_id) {
|
|
6604
|
+
this.kv.del(key);
|
|
6605
|
+
return {
|
|
6606
|
+
submitted: false,
|
|
6607
|
+
status: 'not_found'
|
|
6608
|
+
};
|
|
6609
|
+
}
|
|
6610
|
+
if (receipt.expires_at <= util/* default */.A.Date.now()) {
|
|
6611
|
+
this.kv.del(key);
|
|
6612
|
+
return {
|
|
6613
|
+
submitted: false,
|
|
6614
|
+
status: 'expired'
|
|
6615
|
+
};
|
|
6616
|
+
}
|
|
6617
|
+
return {
|
|
6618
|
+
submitted: true,
|
|
6619
|
+
status: 'found',
|
|
6620
|
+
conversation_id: receipt.conversation_id
|
|
6621
|
+
};
|
|
6622
|
+
} catch (_) {
|
|
6623
|
+
return {
|
|
6624
|
+
submitted: false,
|
|
6625
|
+
status: 'storage_unavailable'
|
|
6626
|
+
};
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6580
6629
|
async submitForm(formid, data, user) {
|
|
6581
6630
|
await this.readyGate.entry();
|
|
6582
6631
|
if (user) await this.setUser(user);
|
|
@@ -6704,6 +6753,7 @@ class Store {
|
|
|
6704
6753
|
async _onWs(ev) {
|
|
6705
6754
|
if (this._die) return;
|
|
6706
6755
|
if (!ev || !ev.type) return;
|
|
6756
|
+
if (util/* default */.A.get(ev, 'touchpoint.channel') === 'form' || util/* default */.A.get(ev, 'data.conversation.touchpoint.channel') === 'form') return;
|
|
6707
6757
|
ev.created = util/* default */.A.getMs(ev.created);
|
|
6708
6758
|
if (ev.type === 'user_info_updated') {
|
|
6709
6759
|
return;
|
|
@@ -7459,6 +7509,22 @@ var xhrsend = function (method, url, body, cb) {
|
|
|
7459
7509
|
request.send(body);
|
|
7460
7510
|
};
|
|
7461
7511
|
const dieErr = new Error('sdk unloaded');
|
|
7512
|
+
function websiteFormStorageKey(api, formid) {
|
|
7513
|
+
return 'sbz_form_submitted:' + util/* default */.A.stringify([api.accid, api.uid, formid]);
|
|
7514
|
+
}
|
|
7515
|
+
function formResult(response) {
|
|
7516
|
+
const body = response && response.body;
|
|
7517
|
+
const detail = response && response.error || body && body.error;
|
|
7518
|
+
const status = response && response.code;
|
|
7519
|
+
const valid = body && typeof body.conversation_id === 'string' && body.conversation_id && typeof body.submission_id === 'string' && body.submission_id;
|
|
7520
|
+
if (detail || !(status >= 200 && status < 300) || !valid) {
|
|
7521
|
+
const error = new Error('Subiz form request failed');
|
|
7522
|
+
error.code = detail && detail.code || 'form_request_failed';
|
|
7523
|
+
error.status = status;
|
|
7524
|
+
throw error;
|
|
7525
|
+
}
|
|
7526
|
+
return body;
|
|
7527
|
+
}
|
|
7462
7528
|
// EXTERNAL MODULE: ./src/common.js
|
|
7463
7529
|
var common = __webpack_require__(2840);
|
|
7464
7530
|
// EXTERNAL MODULE: ./node_modules/preact/dist/preact.module.js
|
|
@@ -14149,8 +14215,9 @@ class lib_Widget {
|
|
|
14149
14215
|
this.impressionTokens[formid] = impressiontoken;
|
|
14150
14216
|
return impressiontoken;
|
|
14151
14217
|
}
|
|
14152
|
-
async submitForm(formid, values) {
|
|
14153
|
-
if (!formid || !values)
|
|
14218
|
+
async submitForm(formid, values, options = {}) {
|
|
14219
|
+
if (!formid || !values) throw new TypeError('submitForm requires a form ID and fields');
|
|
14220
|
+
await this.store.readyGate.entry();
|
|
14154
14221
|
if (util/* default */.A.startsWith(formid, 'cp')) {
|
|
14155
14222
|
let storeuser = util/* default */.A.cloneDeep(this.store.matchUser());
|
|
14156
14223
|
let user = {
|
|
@@ -14181,33 +14248,13 @@ class lib_Widget {
|
|
|
14181
14248
|
this.store.setUserDiff(user);
|
|
14182
14249
|
return this.store.sendConversion(formid, values, impressiontoken);
|
|
14183
14250
|
}
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
return this.store.startConvo({
|
|
14192
|
-
subject: 'Khách điền form',
|
|
14193
|
-
touchpoint: {
|
|
14194
|
-
channel: 'form',
|
|
14195
|
-
source: window.location.hostname,
|
|
14196
|
-
id: Date.now() + '_' + util/* default */.A.randomString()
|
|
14197
|
-
},
|
|
14198
|
-
form_submission: {
|
|
14199
|
-
id: formid,
|
|
14200
|
-
values: values
|
|
14201
|
-
},
|
|
14202
|
-
message_sent: {
|
|
14203
|
-
data: {
|
|
14204
|
-
message: {
|
|
14205
|
-
format: 'plaintext',
|
|
14206
|
-
text: text
|
|
14207
|
-
}
|
|
14208
|
-
}
|
|
14209
|
-
}
|
|
14210
|
-
});
|
|
14251
|
+
const payload = formRequest(formid, values, options);
|
|
14252
|
+
return this.store.submitWebsiteForm(formid, payload);
|
|
14253
|
+
}
|
|
14254
|
+
async getFormStatus(formid) {
|
|
14255
|
+
validateWebsiteFormId(formid);
|
|
14256
|
+
await this.store.readyGate.entry();
|
|
14257
|
+
return this.store.getFormStatus(formid);
|
|
14211
14258
|
}
|
|
14212
14259
|
startConvo(req) {
|
|
14213
14260
|
req = util/* default */.A.cloneDeep(req) || {};
|
|
@@ -14320,6 +14367,34 @@ class lib_Widget {
|
|
|
14320
14367
|
}
|
|
14321
14368
|
window.__sbz_WIDGET = lib_Widget;
|
|
14322
14369
|
/* harmony default export */ const lib = (lib_Widget);
|
|
14370
|
+
function validateWebsiteFormId(formid) {
|
|
14371
|
+
if (typeof formid !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(formid)) {
|
|
14372
|
+
throw new TypeError('Form ID must contain 1-64 letters, digits, underscores or hyphens');
|
|
14373
|
+
}
|
|
14374
|
+
if (formid.startsWith('cp')) throw new TypeError('Form IDs starting with cp are reserved for Subiz campaigns');
|
|
14375
|
+
}
|
|
14376
|
+
function formRequest(formid, fields, options = {}) {
|
|
14377
|
+
validateWebsiteFormId(formid);
|
|
14378
|
+
if (!Array.isArray(fields) || !fields.length) throw new TypeError('Form fields must be a nonempty array');
|
|
14379
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('Form options must be an object');
|
|
14380
|
+
return {
|
|
14381
|
+
form_name: options.name === undefined ? formid : options.name,
|
|
14382
|
+
page_url: options.page_url === undefined ? window.location.href : options.page_url,
|
|
14383
|
+
submission_id: options.submission_id === undefined ? util/* default */.A.randomString(36) : options.submission_id,
|
|
14384
|
+
values: fields.map(field => {
|
|
14385
|
+
if (!field || typeof field.key !== 'string') throw new TypeError('Each field requires a key');
|
|
14386
|
+
const value = field.value;
|
|
14387
|
+
if (value != null && !['string', 'number', 'boolean'].includes(typeof value)) {
|
|
14388
|
+
throw new TypeError('Form field values must be strings, numbers or booleans');
|
|
14389
|
+
}
|
|
14390
|
+
return {
|
|
14391
|
+
key: field.key,
|
|
14392
|
+
label: field.label === undefined ? field.key : field.label,
|
|
14393
|
+
value: value == null ? '' : String(value)
|
|
14394
|
+
};
|
|
14395
|
+
})
|
|
14396
|
+
};
|
|
14397
|
+
}
|
|
14323
14398
|
;// ./screenshoot.js
|
|
14324
14399
|
|
|
14325
14400
|
let accid = 'acqsulrowbxiugvginhw';
|
package/package.json
CHANGED
package/src/lib.js
CHANGED
|
@@ -208,8 +208,9 @@ class Widget {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// cpxdf, [{key:'fullname', type:'text', value: 'thanhpk@liec.om'}]
|
|
211
|
-
async submitForm(formid, values) {
|
|
212
|
-
if (!formid || !values)
|
|
211
|
+
async submitForm(formid, values, options = {}) {
|
|
212
|
+
if (!formid || !values) throw new TypeError('submitForm requires a form ID and fields')
|
|
213
|
+
await this.store.readyGate.entry()
|
|
213
214
|
if (sb.startsWith(formid, 'cp')) {
|
|
214
215
|
let storeuser = sb.cloneDeep(this.store.matchUser())
|
|
215
216
|
let user = {id: storeuser.id, account_id: storeuser.account_id}
|
|
@@ -233,28 +234,14 @@ class Widget {
|
|
|
233
234
|
return this.store.sendConversion(formid, values, impressiontoken)
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return this.store.startConvo({
|
|
245
|
-
subject: 'Khách điền form',
|
|
246
|
-
touchpoint: {
|
|
247
|
-
channel: 'form',
|
|
248
|
-
source: window.location.hostname,
|
|
249
|
-
id: Date.now() + '_' + sb.randomString(),
|
|
250
|
-
},
|
|
251
|
-
form_submission: {id: formid, values: values},
|
|
252
|
-
message_sent: {
|
|
253
|
-
data: {
|
|
254
|
-
message: {format: 'plaintext', text: text},
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
})
|
|
237
|
+
const payload = formRequest(formid, values, options)
|
|
238
|
+
return this.store.submitWebsiteForm(formid, payload)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async getFormStatus(formid) {
|
|
242
|
+
validateWebsiteFormId(formid)
|
|
243
|
+
await this.store.readyGate.entry()
|
|
244
|
+
return this.store.getFormStatus(formid)
|
|
258
245
|
}
|
|
259
246
|
|
|
260
247
|
// req.bot_id != "" -> start conversation with bot id
|
|
@@ -404,3 +391,30 @@ window.__sbz_WIDGET = Widget
|
|
|
404
391
|
export default Widget
|
|
405
392
|
|
|
406
393
|
// lib.submitForm('cprkztryyobndhbvwzjab', [{key: 'fullname', value: 'Pham Kieu Thanh 2' }, {key: 'phones', value: "0366621895"}]
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
function validateWebsiteFormId(formid) {
|
|
397
|
+
if (typeof formid !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(formid)) {
|
|
398
|
+
throw new TypeError('Form ID must contain 1-64 letters, digits, underscores or hyphens')
|
|
399
|
+
}
|
|
400
|
+
if (formid.startsWith('cp')) throw new TypeError('Form IDs starting with cp are reserved for Subiz campaigns')
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function formRequest(formid, fields, options = {}) {
|
|
404
|
+
validateWebsiteFormId(formid)
|
|
405
|
+
if (!Array.isArray(fields) || !fields.length) throw new TypeError('Form fields must be a nonempty array')
|
|
406
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('Form options must be an object')
|
|
407
|
+
return {
|
|
408
|
+
form_name: options.name === undefined ? formid : options.name,
|
|
409
|
+
page_url: options.page_url === undefined ? window.location.href : options.page_url,
|
|
410
|
+
submission_id: options.submission_id === undefined ? sb.randomString(36) : options.submission_id,
|
|
411
|
+
values: fields.map((field) => {
|
|
412
|
+
if (!field || typeof field.key !== 'string') throw new TypeError('Each field requires a key')
|
|
413
|
+
const value = field.value
|
|
414
|
+
if (value != null && !['string', 'number', 'boolean'].includes(typeof value)) {
|
|
415
|
+
throw new TypeError('Form field values must be strings, numbers or booleans')
|
|
416
|
+
}
|
|
417
|
+
return {key: field.key, label: field.label === undefined ? field.key : field.label, value: value == null ? '' : String(value)}
|
|
418
|
+
}),
|
|
419
|
+
}
|
|
420
|
+
}
|
package/src/store/api.js
CHANGED
|
@@ -271,6 +271,12 @@ export default class {
|
|
|
271
271
|
.addQuery('x-referer', sb.get(location, 'origin', ''))
|
|
272
272
|
.post(`/accounts/${this.accid}/users/${this.uid}/events`, data)
|
|
273
273
|
|
|
274
|
+
// Keep the configured API host and all widget authentication hooks.
|
|
275
|
+
websiteFormRequest = () => this.apireq.setBaseUrl(this.ApiURL.replace(/\/4\.[01]\/?$/, '/4.1'))
|
|
276
|
+
|
|
277
|
+
submitWebsiteForm = (formid, payload) => this.websiteFormRequest()
|
|
278
|
+
.post(`/accounts/${this.accid}/external-forms/${encodeURIComponent(formid)}/submissions`, payload)
|
|
279
|
+
|
|
274
280
|
submitForm = (form_id, page_url, device, data, user) =>
|
|
275
281
|
this.apireq
|
|
276
282
|
.addQuery('x-referer', sb.get(location, 'origin', ''))
|
package/src/store/store.js
CHANGED
|
@@ -955,6 +955,36 @@ 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 result = formResult(await this.api.submitWebsiteForm(formid, payload))
|
|
963
|
+
try {
|
|
964
|
+
kv.put(key, {conversation_id: result.conversation_id, expires_at: sb.Date.now() + 7 * 24 * 60 * 60 * 1000})
|
|
965
|
+
} catch (_) { /* Storage failure must not make callers retry a successful submission. */ }
|
|
966
|
+
return result
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
getFormStatus(formid) {
|
|
970
|
+
const key = websiteFormStorageKey(this.api, formid)
|
|
971
|
+
try {
|
|
972
|
+
const receipt = this.kv.match(key)
|
|
973
|
+
if (!receipt) return {submitted: false, status: 'not_found'}
|
|
974
|
+
if (!Number.isFinite(receipt.expires_at) || typeof receipt.conversation_id !== 'string' || !receipt.conversation_id) {
|
|
975
|
+
this.kv.del(key)
|
|
976
|
+
return {submitted: false, status: 'not_found'}
|
|
977
|
+
}
|
|
978
|
+
if (receipt.expires_at <= sb.Date.now()) {
|
|
979
|
+
this.kv.del(key)
|
|
980
|
+
return {submitted: false, status: 'expired'}
|
|
981
|
+
}
|
|
982
|
+
return {submitted: true, status: 'found', conversation_id: receipt.conversation_id}
|
|
983
|
+
} catch (_) {
|
|
984
|
+
return {submitted: false, status: 'storage_unavailable'}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
958
988
|
async submitForm(formid, data, user) {
|
|
959
989
|
await this.readyGate.entry()
|
|
960
990
|
if (user) await this.setUser(user)
|
|
@@ -1117,6 +1147,9 @@ export default class Store {
|
|
|
1117
1147
|
async _onWs(ev) {
|
|
1118
1148
|
if (this._die) return
|
|
1119
1149
|
if (!ev || !ev.type) return
|
|
1150
|
+
// Website form conversations are handled in the agent inbox, not the chat widget.
|
|
1151
|
+
// Ignore their realtime events before an unknown conversation triggers a GET.
|
|
1152
|
+
if (sb.get(ev, 'touchpoint.channel') === 'form' || sb.get(ev, 'data.conversation.touchpoint.channel') === 'form') return
|
|
1120
1153
|
ev.created = sb.getMs(ev.created)
|
|
1121
1154
|
if (ev.type === 'user_info_updated') {
|
|
1122
1155
|
return
|
|
@@ -1916,3 +1949,21 @@ var xhrsend = function (method, url, body, cb) {
|
|
|
1916
1949
|
}
|
|
1917
1950
|
|
|
1918
1951
|
const dieErr = new Error('sdk unloaded')
|
|
1952
|
+
|
|
1953
|
+
function websiteFormStorageKey(api, formid) {
|
|
1954
|
+
return 'sbz_form_submitted:' + sb.stringify([api.accid, api.uid, formid])
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
function formResult(response) {
|
|
1958
|
+
const body = response && response.body
|
|
1959
|
+
const detail = (response && response.error) || (body && body.error)
|
|
1960
|
+
const status = response && response.code
|
|
1961
|
+
const valid = body && typeof body.conversation_id === 'string' && body.conversation_id && typeof body.submission_id === 'string' && body.submission_id
|
|
1962
|
+
if (detail || !(status >= 200 && status < 300) || !valid) {
|
|
1963
|
+
const error = new Error('Subiz form request failed')
|
|
1964
|
+
error.code = (detail && detail.code) || 'form_request_failed'
|
|
1965
|
+
error.status = status
|
|
1966
|
+
throw error
|
|
1967
|
+
}
|
|
1968
|
+
return body
|
|
1969
|
+
}
|