@subiz/widget-v4 4.1.202 → 4.1.204

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 (46) hide show
  1. package/api.md +1224 -0
  2. package/compiled_src/chatbox/ChatButton.js +19 -16
  3. package/compiled_src/chatbox/MessageInput.js +1 -0
  4. package/compiled_src/chatbox/main.js +28 -3
  5. package/compiled_src/lib.js +14 -0
  6. package/compiled_src/store/api.js +36 -4
  7. package/compiled_src/store/store.js +20 -4
  8. package/compiled_src/util/event.test.js +38 -0
  9. package/dist/19e1c4525c1.app.js +1 -0
  10. package/dist/{19d8a41ce33.chat.html → 19e1c4525c1.chat.html} +1 -1
  11. package/dist/19e1c4525c1.chat.js +1 -0
  12. package/dist/{19d8a41ce33.form.html → 19e1c4525c1.form.html} +1 -1
  13. package/dist/19e1c4525c1.form.js +1 -0
  14. package/dist/{19d8a41ce33.index.html → 19e1c4525c1.index.html} +1 -1
  15. package/dist/{19d8a41ce33.library.html → 19e1c4525c1.library.html} +1 -1
  16. package/dist/19e1c4525c1.library.js +1 -0
  17. package/dist/{19d8a41ce33.preview.html → 19e1c4525c1.preview.html} +1 -1
  18. package/dist/19e1c4525c1.preview.js +1 -0
  19. package/dist/19e1c4525c1.webview.js +1 -0
  20. package/dist/HASH +1 -1
  21. package/dist/screenshoot.js +104 -22
  22. package/package.json +8 -8
  23. package/src/chatbox/ChatButton.js +20 -15
  24. package/src/chatbox/MessageInput.js +1 -0
  25. package/src/chatbox/main.js +24 -4
  26. package/src/lib.js +14 -0
  27. package/src/store/api.js +41 -3
  28. package/src/store/store.js +28 -19
  29. package/src/util/event.test.js +31 -0
  30. package/dist/19d8a41ce33.app.js +0 -1
  31. package/dist/19d8a41ce33.chat.js +0 -1
  32. package/dist/19d8a41ce33.form.js +0 -1
  33. package/dist/19d8a41ce33.library.js +0 -1
  34. package/dist/19d8a41ce33.preview.js +0 -1
  35. package/dist/19d8a41ce33.webview.js +0 -1
  36. package/examples/track.js +0 -41
  37. /package/dist/{19d8a41ce33.127.js → 19e1c4525c1.127.js} +0 -0
  38. /package/dist/{19d8a41ce33.132.js → 19e1c4525c1.132.js} +0 -0
  39. /package/dist/{19d8a41ce33.185.js → 19e1c4525c1.185.js} +0 -0
  40. /package/dist/{19d8a41ce33.27.js → 19e1c4525c1.27.js} +0 -0
  41. /package/dist/{19d8a41ce33.278.js → 19e1c4525c1.278.js} +0 -0
  42. /package/dist/{19d8a41ce33.339.js → 19e1c4525c1.339.js} +0 -0
  43. /package/dist/{19d8a41ce33.459.js → 19e1c4525c1.459.js} +0 -0
  44. /package/dist/{19d8a41ce33.490.js → 19e1c4525c1.490.js} +0 -0
  45. /package/dist/{19d8a41ce33.74.js → 19e1c4525c1.74.js} +0 -0
  46. /package/dist/{19d8a41ce33.997.js → 19e1c4525c1.997.js} +0 -0
package/dist/HASH CHANGED
@@ -1 +1 @@
1
- 19d8a41ce33
1
+ 19e1c4525c1
@@ -1282,6 +1282,7 @@ __webpack_require__.r(__webpack_exports__);
1282
1282
  window.cancelAnimationFrame(this.animationframe);
1283
1283
  }
1284
1284
  });
1285
+ (Object.getOwnPropertyDescriptor(__WEBPACK_DEFAULT_EXPORT__, "name") || {}).writable || Object.defineProperty(__WEBPACK_DEFAULT_EXPORT__, "name", { value: "default", configurable: true });
1285
1286
  function linearTransform(from, to, t, duration) {
1286
1287
  if (eqFloat(from, to)) return from;
1287
1288
  var s = to - from;
@@ -4389,12 +4390,31 @@ let trackResponseFn = (param, done) => {
4389
4390
  _defineProperty(this, "time", _ => ajax.setBaseUrl(this.ApiURL).withCredentials(true).setParser('json').get('/time'));
4390
4391
  _defineProperty(this, "updateUser", data => this.apireq.post(`/accounts/${this.accid}/users/${this.uid}`, data));
4391
4392
  _defineProperty(this, "getUser", _ => this.apireq.get(`/accounts/${this.accid}/users/${this.uid}`));
4392
- _defineProperty(this, "getAccountSetting", p => {
4393
+ _defineProperty(this, "getAccountSetting", async p => {
4393
4394
  let href = util/* default */.A.get(document, 'location.href');
4395
+ let res = {};
4394
4396
  if (p && p.bot_id) {
4395
- return this.apireq.addQuery('url', href).addQuery('bot_id', p.bot_id).get(`/accounts/${this.accid}/settings`);
4396
- }
4397
- return this.apireq.addQuery('url', href).get(`/accounts/${this.accid}/settings`);
4397
+ res = await this.apireq.addQuery('url', href).addQuery('bot_id', p.bot_id).get(`/accounts/${this.accid}/settings`);
4398
+ } else {
4399
+ res = await this.apireq.addQuery('url', href).get(`/accounts/${this.accid}/settings`);
4400
+ }
4401
+ if (res.error) return res;
4402
+ let plugins = util/* default */.A.get(res, 'body.plugins') || [];
4403
+ util/* default */.A.map(plugins, plugin => {
4404
+ if (plugin.type === 'popup') {
4405
+ let pages = util/* default */.A.get(plugin, 'popup.pages') || [];
4406
+ util/* default */.A.map(pages, page => {
4407
+ util/* default */.A.map(page.components, com => {
4408
+ if (com.type === 'image') {
4409
+ let url = util/* default */.A.get(com, 'image.url');
4410
+ util/* default */.A.set(com, 'image.url', fallbackGCSUrl(url));
4411
+ }
4412
+ });
4413
+ });
4414
+ }
4415
+ });
4416
+ util/* default */.A.set(res, 'body.plugins', plugins);
4417
+ return res;
4398
4418
  });
4399
4419
  _defineProperty(this, "sendImpression", impression => this.apireq.post(`/accounts/${this.accid}/campaigns/${impression.campaign_id}/impressions`, impression));
4400
4420
  _defineProperty(this, "sendConversion", conversion => this.apireq.post(`/accounts/${this.accid}/campaigns/${conversion.campaign_id}/conversions`, conversion));
@@ -4580,6 +4600,20 @@ let trackResponseFn = (param, done) => {
4580
4600
  });
4581
4601
  }
4582
4602
  });
4603
+ (Object.getOwnPropertyDescriptor(api, "name") || {}).writable || Object.defineProperty(api, "name", { value: "default", configurable: true });
4604
+ function fallbackGCSUrl(url) {
4605
+ try {
4606
+ const u = new URL(url);
4607
+ const isGCS = u.hostname === 'storage.googleapis.com';
4608
+ const isSubizBucket = u.pathname.startsWith('/public-gcs.subiz-cdn.com/');
4609
+ if (isGCS && isSubizBucket) {
4610
+ return `https://${u.pathname.slice(1)}`;
4611
+ }
4612
+ return url;
4613
+ } catch (e) {
4614
+ return url;
4615
+ }
4616
+ }
4583
4617
  ;// ./src/store/lsdb.js
4584
4618
  let memlocalStorage = __webpack_require__(450);
4585
4619
 
@@ -4641,6 +4675,7 @@ const LIMIT = 80;
4641
4675
  });
4642
4676
  }
4643
4677
  });
4678
+ (Object.getOwnPropertyDescriptor(lsdb, "name") || {}).writable || Object.defineProperty(lsdb, "name", { value: "default", configurable: true });
4644
4679
  function lsdb_toString(obj) {
4645
4680
  if (obj === undefined) return 'null';
4646
4681
  return JSON.stringify(obj);
@@ -4945,6 +4980,7 @@ let KvMaxSize = 500;
4945
4980
  this.initialized = false;
4946
4981
  }
4947
4982
  });
4983
+ (Object.getOwnPropertyDescriptor(kv, "name") || {}).writable || Object.defineProperty(kv, "name", { value: "default", configurable: true });
4948
4984
  ;// ./src/store/api_fake.js
4949
4985
  function api_fake_defineProperty(e, r, t) { return (r = api_fake_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4950
4986
  function api_fake_toPropertyKey(t) { var i = api_fake_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
@@ -5035,6 +5071,7 @@ const OK200 = Promise.resolve({
5035
5071
  };
5036
5072
  }
5037
5073
  });
5074
+ (Object.getOwnPropertyDescriptor(api_fake, "name") || {}).writable || Object.defineProperty(api_fake, "name", { value: "default", configurable: true });
5038
5075
  function makeFakeConvo(accid, convoid, userid, agentid) {
5039
5076
  let inteid = accid + '.subizv4.subikon';
5040
5077
  return {
@@ -5333,6 +5370,7 @@ function makeFakeMessages(accid, convoid, userid, agentid) {
5333
5370
  }
5334
5371
  reconnect() {}
5335
5372
  });
5373
+ (Object.getOwnPropertyDescriptor(realtime_fake, "name") || {}).writable || Object.defineProperty(realtime_fake, "name", { value: "default", configurable: true });
5336
5374
  ;// ./src/store/store.js
5337
5375
  function store_defineProperty(e, r, t) { return (r = store_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5338
5376
  function store_toPropertyKey(t) { var i = store_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
@@ -6853,21 +6891,8 @@ class ChatButton extends common/* Com */.Mw {
6853
6891
  if (util/* default */.A.get(ev, 'type') !== 'message_sent') return;
6854
6892
  let bytype = util/* default */.A.get(ev, 'by.type');
6855
6893
  if (bytype !== 'system' && bytype !== 'agent' && bytype !== 'bot') return;
6856
- let isquickreplies = false;
6857
- util/* default */.A.map(util/* default */.A.get(ev, 'data.message.attachments', []), att => {
6858
- if (!att) return;
6859
- if (att.type === 'quick_replies') {
6860
- isquickreplies = true;
6861
- }
6862
- });
6863
- let format = util/* default */.A.get(ev, 'data.message.format');
6864
- let block = util/* default */.A.get(ev, 'data.message.block');
6865
- if (block || format == 'block') {
6866
- if (util/* default */.A.isBlockEmpty(block) && isquickreplies) return;
6867
- } else {
6868
- let message = util/* default */.A.get(ev, 'data.message.text') || '';
6869
- if (!message && isquickreplies) return;
6870
- }
6894
+ let message = util/* default */.A.get(ev, 'data.message') || {};
6895
+ if (util/* default */.A.isEmptyMessage(message)) return;
6871
6896
  let screen = util/* default */.A.get(this.props.store.matchLastview(), 'screen');
6872
6897
  let minimized = util/* default */.A.get(this.props.store.matchLastview(), 'minimized');
6873
6898
  if (screen === 'widget') return;
@@ -7226,6 +7251,9 @@ class ChatButton extends common/* Com */.Mw {
7226
7251
  imgurl = this.props.store.replaceFileUrl(imgurl);
7227
7252
  imgsrc = this.props.store.replaceFileUrl(imgsrc);
7228
7253
  maskurl = this.props.store.replaceFileUrl(maskurl);
7254
+ imgurl = addVersionParam(imgurl);
7255
+ imgsrc = addVersionParam(imgsrc);
7256
+ maskurl = addVersionParam(maskurl);
7229
7257
  if (!imgurl && !maskurl) {
7230
7258
  whitebgcls += ' chat-button--white-bg--mask-only';
7231
7259
  imgcontainerstyle = `
@@ -7357,6 +7385,19 @@ function randomAgent(agentM) {
7357
7385
  const Ms30Mins = 30 * 60 * 1000;
7358
7386
  const DEF_BG_URL = 'https://vcdn.subiz-cdn.com/file/bubble_11.svg?v=5';
7359
7387
  const DEF_BG = 'linear-gradient(280.16deg, #0053d3 22.03%, #0265ff 78.2%)';
7388
+ function addVersionParam(url, version = 8) {
7389
+ if (!url) return url;
7390
+ try {
7391
+ const u = new URL(url);
7392
+ u.searchParams.set('v', version);
7393
+ return u.toString();
7394
+ } catch (e) {
7395
+ const [base, hash] = url.split('#');
7396
+ const hasQuery = base.includes('?');
7397
+ const newUrl = base + (hasQuery ? '&' : '?') + `v=${version}`;
7398
+ return hash ? newUrl + '#' + hash : newUrl;
7399
+ }
7400
+ }
7360
7401
  ;// ./src/chatbox/PoweredBy.js
7361
7402
  var logo = __webpack_require__(9529);
7362
7403
 
@@ -7785,6 +7826,7 @@ class MessageInput extends common/* Com */.Mw {
7785
7826
  }, 500);
7786
7827
  this.resizeInput = util/* default */.A.throttle(function () {
7787
7828
  const input = this.refMessageInput.current;
7829
+ if (!input) return;
7788
7830
  input.style.setProperty('height', '1em', 'important');
7789
7831
  const height = Math.min(200, input.scrollHeight);
7790
7832
  input.style.setProperty('height', `${height}px`, 'important');
@@ -12159,6 +12201,9 @@ function encode(key) {
12159
12201
  // EXTERNAL MODULE: ./src/chatbox/css.js
12160
12202
  var chatbox_css = __webpack_require__(9278);
12161
12203
  ;// ./src/chatbox/main.js
12204
+ function main_defineProperty(e, r, t) { return (r = main_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
12205
+ function main_toPropertyKey(t) { var i = main_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
12206
+ function main_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
12162
12207
 
12163
12208
 
12164
12209
 
@@ -12168,6 +12213,10 @@ var chatbox_css = __webpack_require__(9278);
12168
12213
  class Main extends common/* Com */.Mw {
12169
12214
  constructor(props) {
12170
12215
  super(props);
12216
+ main_defineProperty(this, "state", {
12217
+ cssReady: false,
12218
+ changing: false
12219
+ });
12171
12220
  this.refButton = (0,preact_module/* createRef */._3)();
12172
12221
  this.refWidget = (0,preact_module/* createRef */._3)();
12173
12222
  }
@@ -12195,9 +12244,6 @@ class Main extends common/* Com */.Mw {
12195
12244
  this.$cssStyleDiv.type = 'text/css';
12196
12245
  this.$cssStyleDiv.id = 'subiz-style-for-' + this.props.plugin.id;
12197
12246
  }
12198
- this.setState({
12199
- cssReady: false
12200
- });
12201
12247
  this.checkExpand();
12202
12248
  this.props.store.onResetCSS(this, () => this.refreshCSS());
12203
12249
  this.props.store.onConvoEvent(this, ev => this.onConvoEvent(ev));
@@ -12209,11 +12255,32 @@ class Main extends common/* Com */.Mw {
12209
12255
  });
12210
12256
  });
12211
12257
  this.refreshCSS();
12258
+ let lastview = this.props.store.matchLastview() || {};
12259
+ let cid = util/* default */.A.get(lastview, 'opts.cid');
12260
+ if (cid) {
12261
+ this._last_cid = cid;
12262
+ this.setState({
12263
+ changing: true
12264
+ }, () => {
12265
+ this.props.store.fetchConvo(cid).then(() => {
12266
+ let convo = this.props.store.matchConvo(cid);
12267
+ if (cid != this._last_cid) return;
12268
+ this.setState({
12269
+ changing: false
12270
+ });
12271
+ if (!convo) return this.showWidget({});
12272
+ });
12273
+ });
12274
+ }
12212
12275
  this.setState({
12213
12276
  cssReady: true
12214
12277
  });
12215
12278
  }
12216
12279
  render() {
12280
+ if (this.state.changing) {
12281
+ if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_changing_ready = util/* default */.A.Date.now();
12282
+ return null;
12283
+ }
12217
12284
  if (!this.state.cssReady) {
12218
12285
  if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_css_not_ready = util/* default */.A.Date.now();
12219
12286
  return null;
@@ -12709,6 +12776,21 @@ class lib_Widget {
12709
12776
  lib_defineProperty(this, "setViewingProduct", p => this.store.linkProduct(p));
12710
12777
  lib_defineProperty(this, "getViewingProduct", () => this.store.getLinkedProduct());
12711
12778
  lib_defineProperty(this, "track", v => this.store.track(v));
12779
+ lib_defineProperty(this, "event", async v => {
12780
+ await this.store.readyGate.entry();
12781
+ return this.store.api.sendEvent(v);
12782
+ });
12783
+ lib_defineProperty(this, "pageview", v => {
12784
+ v = v || {};
12785
+ return this.event({
12786
+ type: 'content_viewed',
12787
+ payload: {
12788
+ url: v.url || location.href,
12789
+ title: v.title || document.title,
12790
+ referrer: v.referrer || document.referrer
12791
+ }
12792
+ });
12793
+ });
12712
12794
  this.impressionTokens = {};
12713
12795
  this.domGate = new util/* default */.A.Gate();
12714
12796
  this.store = new Store(ApiURL, FileURL, RealtimeURL, inmem, fake);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.1.202",
3
+ "version": "4.1.204",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "@subiz/ajax": "^3.0.22",
13
13
  "@subiz/flow": "^1.1.0",
14
- "@subiz/wsclient": "^4.0.34",
14
+ "@subiz/wsclient": "^4.0.35",
15
15
  "cookie_js": "1.4.2",
16
16
  "core-js": "^3.49.0",
17
17
  "css-loader": "5.2.7",
@@ -26,14 +26,14 @@
26
26
  "@babel/core": "^7.29.0",
27
27
  "@babel/plugin-proposal-class-properties": "^7.18.6",
28
28
  "@babel/plugin-transform-react-jsx": "^7.28.6",
29
- "@babel/preset-env": "^7.29.2",
29
+ "@babel/preset-env": "^7.29.5",
30
30
  "babel-loader": "^10.1.1",
31
31
  "babel-plugin-syntax-jsx": "^6.18.0",
32
- "html-webpack-plugin": "^5.6.6",
33
- "puppeteer": "^24.40.0",
34
- "webpack": "^5.105.4",
35
- "webpack-bundle-analyzer": "^5.2.0",
32
+ "html-webpack-plugin": "^5.6.7",
33
+ "puppeteer": "^25.0.4",
34
+ "webpack": "^5.107.1",
35
+ "webpack-bundle-analyzer": "^5.3.0",
36
36
  "webpack-cli": "^7.0.2",
37
- "webpack-dev-server": "^5.2.3"
37
+ "webpack-dev-server": "^5.2.4"
38
38
  }
39
39
  }
@@ -27,21 +27,8 @@ export default class ChatButton extends Com {
27
27
  let bytype = sb.get(ev, 'by.type')
28
28
  if (bytype !== 'system' && bytype !== 'agent' && bytype !== 'bot') return
29
29
 
30
- let isquickreplies = false
31
- sb.map(sb.get(ev, 'data.message.attachments', []), (att) => {
32
- if (!att) return
33
- if (att.type === 'quick_replies') {
34
- isquickreplies = true
35
- }
36
- })
37
- let format = sb.get(ev, 'data.message.format')
38
- let block = sb.get(ev, 'data.message.block')
39
- if (block || format == 'block') {
40
- if (sb.isBlockEmpty(block) && isquickreplies) return
41
- } else {
42
- let message = sb.get(ev, 'data.message.text') || ''
43
- if (!message && isquickreplies) return
44
- }
30
+ let message = sb.get(ev, 'data.message') || {}
31
+ if (sb.isEmptyMessage(message)) return
45
32
  let screen = sb.get(this.props.store.matchLastview(), 'screen')
46
33
  let minimized = sb.get(this.props.store.matchLastview(), 'minimized')
47
34
  if (screen === 'widget') return
@@ -453,6 +440,9 @@ export default class ChatButton extends Com {
453
440
  imgurl = this.props.store.replaceFileUrl(imgurl)
454
441
  imgsrc = this.props.store.replaceFileUrl(imgsrc)
455
442
  maskurl = this.props.store.replaceFileUrl(maskurl)
443
+ imgurl = addVersionParam(imgurl)
444
+ imgsrc = addVersionParam(imgsrc)
445
+ maskurl = addVersionParam(maskurl)
456
446
 
457
447
  if (!imgurl && !maskurl) {
458
448
  whitebgcls += ' chat-button--white-bg--mask-only'
@@ -595,3 +585,18 @@ const Ms30Mins = 30 * 60 * 1000
595
585
  // v=5 trick to clear cache
596
586
  const DEF_BG_URL = 'https://vcdn.subiz-cdn.com/file/bubble_11.svg?v=5'
597
587
  const DEF_BG = 'linear-gradient(280.16deg, #0053d3 22.03%, #0265ff 78.2%)'
588
+
589
+ function addVersionParam(url, version = 8) {
590
+ if (!url) return url
591
+ try {
592
+ const u = new URL(url)
593
+ u.searchParams.set('v', version)
594
+ return u.toString()
595
+ } catch (e) {
596
+ // fallback nếu url không hợp lệ hoàn toàn
597
+ const [base, hash] = url.split('#')
598
+ const hasQuery = base.includes('?')
599
+ const newUrl = base + (hasQuery ? '&' : '?') + `v=${version}`
600
+ return hash ? newUrl + '#' + hash : newUrl
601
+ }
602
+ }
@@ -25,6 +25,7 @@ export default class MessageInput extends Com {
25
25
  this.resizeInput = sb.throttle(function () {
26
26
  // TODO: ctrl+A then press backspace dont reset size rightaway
27
27
  const input = this.refMessageInput.current
28
+ if (!input) return
28
29
  input.style.setProperty('height', '1em', 'important')
29
30
  const height = Math.min(200, input.scrollHeight)
30
31
  input.style.setProperty('height', `${height}px`, 'important')
@@ -17,6 +17,11 @@ export default class Main extends Com {
17
17
  this.refWidget = createRef()
18
18
  }
19
19
 
20
+ state = {
21
+ cssReady: false,
22
+ changing: false,
23
+ }
24
+
20
25
  refreshCSS() {
21
26
  // make sure you are still alive
22
27
  let plugins = this.props.store.matchPlugin()
@@ -45,10 +50,6 @@ export default class Main extends Com {
45
50
  this.$cssStyleDiv.id = 'subiz-style-for-' + this.props.plugin.id
46
51
  }
47
52
 
48
- this.setState({
49
- cssReady: false,
50
- })
51
-
52
53
  this.checkExpand()
53
54
  this.props.store.onResetCSS(this, () => this.refreshCSS())
54
55
  this.props.store.onConvoEvent(this, (ev) => this.onConvoEvent(ev))
@@ -60,10 +61,29 @@ export default class Main extends Com {
60
61
  })
61
62
  })
62
63
  this.refreshCSS()
64
+
65
+ let lastview = this.props.store.matchLastview() || {}
66
+ let cid = sb.get(lastview, 'opts.cid')
67
+ if (cid) {
68
+ this._last_cid = cid
69
+ this.setState({changing: true}, () => {
70
+ this.props.store.fetchConvo(cid).then(() => {
71
+ let convo = this.props.store.matchConvo(cid)
72
+ if (cid != this._last_cid) return // outdated
73
+ this.setState({changing: false})
74
+ if (!convo) return this.showWidget({}) // invalid convo, must redirect
75
+ })
76
+ })
77
+ }
78
+
63
79
  this.setState({cssReady: true})
64
80
  }
65
81
 
66
82
  render() {
83
+ if (this.state.changing) {
84
+ if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_changing_ready = sb.Date.now()
85
+ return null
86
+ }
67
87
  if (!this.state.cssReady) {
68
88
  if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_css_not_ready = sb.Date.now()
69
89
  return null
package/src/lib.js CHANGED
@@ -317,6 +317,11 @@ class Widget {
317
317
  getViewingProduct = () => this.store.getLinkedProduct()
318
318
 
319
319
  track = (v) => this.store.track(v)
320
+ event = async (type, payload, options) => {
321
+ await this.store.readyGate.entry()
322
+ return this.store.api.sendEvent(normalizeCdpEvent(type, payload, options))
323
+ }
324
+ pageview = (v) => this.store.sendPageview(v)
320
325
 
321
326
  async setLocale(locale) {
322
327
  await this.domGate.entry()
@@ -398,6 +403,15 @@ class Widget {
398
403
  }
399
404
  }
400
405
 
406
+ function normalizeCdpEvent(type, payload, options) {
407
+ if (type && typeof type === 'object') return type
408
+
409
+ let data = {type: type}
410
+ if (payload !== undefined) data.payload = payload
411
+ if (options && typeof options === 'object') data = Object.assign({}, options, data)
412
+ return data
413
+ }
414
+
401
415
  // for debugging purpose
402
416
  window.__sbz_WIDGET = Widget
403
417
  export default Widget
package/src/store/api.js CHANGED
@@ -394,12 +394,33 @@ export default class {
394
394
  updateUser = (data) => this.apireq.post(`/accounts/${this.accid}/users/${this.uid}`, data)
395
395
 
396
396
  getUser = (_) => this.apireq.get(`/accounts/${this.accid}/users/${this.uid}`)
397
- getAccountSetting = (p) => {
397
+ getAccountSetting = async (p) => {
398
398
  let href = sb.get(document, 'location.href') // send full href, this could be used to select which bot (ai-agent) should be returned
399
+ let res = {}
399
400
  if (p && p.bot_id) {
400
- return this.apireq.addQuery('url', href).addQuery('bot_id', p.bot_id).get(`/accounts/${this.accid}/settings`)
401
+ res = await this.apireq.addQuery('url', href).addQuery('bot_id', p.bot_id).get(`/accounts/${this.accid}/settings`)
402
+ } else {
403
+ res = await this.apireq.addQuery('url', href).get(`/accounts/${this.accid}/settings`)
401
404
  }
402
- return this.apireq.addQuery('url', href).get(`/accounts/${this.accid}/settings`)
405
+ if (res.error) return res
406
+
407
+ // fix image error image url link in popup
408
+ let plugins = sb.get(res, 'body.plugins') || []
409
+ sb.map(plugins, (plugin) => {
410
+ if (plugin.type === 'popup') {
411
+ let pages = sb.get(plugin, 'popup.pages') || []
412
+ sb.map(pages, (page) => {
413
+ sb.map(page.components, (com) => {
414
+ if (com.type === 'image') {
415
+ let url = sb.get(com, 'image.url')
416
+ sb.set(com, 'image.url', fallbackGCSUrl(url))
417
+ }
418
+ })
419
+ })
420
+ }
421
+ })
422
+ sb.set(res, 'body.plugins', plugins)
423
+ return res
403
424
  }
404
425
 
405
426
  sendImpression = (impression) =>
@@ -432,3 +453,20 @@ export default class {
432
453
  suggest_address = (keyword) => this.apireq.setQuery({keyword: keyword}).get(`/accounts/${this.accid}/addresses`)
433
454
  create_ticket = (ticket) => this.apireq.post(`/accounts/${this.accid}/tickets`, ticket)
434
455
  }
456
+
457
+ function fallbackGCSUrl(url) {
458
+ try {
459
+ const u = new URL(url)
460
+
461
+ const isGCS = u.hostname === 'storage.googleapis.com'
462
+ const isSubizBucket = u.pathname.startsWith('/public-gcs.subiz-cdn.com/')
463
+
464
+ if (isGCS && isSubizBucket) {
465
+ return `https://${u.pathname.slice(1)}`
466
+ }
467
+
468
+ return url
469
+ } catch (e) {
470
+ return url
471
+ }
472
+ }
@@ -76,6 +76,13 @@ export default class Store {
76
76
  this.sendPresence()
77
77
  })
78
78
  window.addEventListener('beforeunload', () => this.api.sendLostFocus())
79
+ this._lastPresenceURL = window.location.href
80
+ this._urlCheckInterval = sb.setInterval(() => {
81
+ if (this._die) return sb.clearInterval(this._urlCheckInterval)
82
+ if (this._lastPresenceURL === window.location.href) return
83
+ this._lastPresenceURL = window.location.href
84
+ this.sendPresence()
85
+ }, 2_000)
79
86
 
80
87
  this.doNotStartBot = () => {
81
88
  this._donotstartbot = true
@@ -508,24 +515,16 @@ export default class Store {
508
515
  submitForm2 = (accid, token, data) => this.api.submitForm2(accid, token, data)
509
516
  sendPresence = sb.throttle(
510
517
  async () => {
518
+ if (!this._track_page_view) return
511
519
  if (this._die) return dieErr
512
520
  await this.readyGate.entry()
513
521
  if (!sb.get(this.cache.match('user'), 'id')) return // store not init
514
522
  if (sb.isUserBanned(this.cache.match('user'))) return
515
523
 
516
- if (!this._track_page_view) return
517
-
518
524
  let hasfocus = this.browserVisibility === 'visible' && document.hasFocus()
519
525
  if (!hasfocus) return this.api.sendLostFocus()
520
526
 
521
- this.api.sendContentView(
522
- this.device,
523
- location.href,
524
- document.title,
525
- this._currentViewingProduct.id,
526
- this._currentViewingProduct.source_id,
527
- this._currentViewingProduct.sku,
528
- )
527
+ this.sendPageview()
529
528
  },
530
529
  1000, // dont send presence update too tight
531
530
  {leading: false}, // dont send right after load. should send after content_view
@@ -915,21 +914,30 @@ export default class Store {
915
914
  this.pubsub.publish('account')
916
915
  }
917
916
 
917
+ async sendPageview(p) {
918
+ if (!p) p = {}
919
+ let device = Object.assign({}, this.device)
920
+ if (p.referrer) device.source_referrer = p.referrer
921
+ device.page_url = p.url || location.href
922
+ device.page_title = p.title || document.title
923
+ return this.api.sendContentView(
924
+ device,
925
+ p.url || location.href,
926
+ p.title || document.title,
927
+ this._currentViewingProduct.id,
928
+ this._currentViewingProduct.source_id,
929
+ this._currentViewingProduct.sku,
930
+ )
931
+ }
932
+
918
933
  // send the content view, the first presence and record domain
919
934
  async track(e) {
920
935
  if (!e) {
921
- let out = await this.api.sendContentView(
922
- this.device,
923
- location.href,
924
- document.title,
925
- this._currentViewingProduct.id,
926
- this._currentViewingProduct.source_id,
927
- this._currentViewingProduct.sku,
928
- )
936
+ let out = await this.sendPageview()
929
937
 
930
938
  let traffic_source = sb.get(out, 'body.by.device.source', '')
931
939
  if (traffic_source) this.kv.put('traffic_source', traffic_source)
932
- return
940
+ return out
933
941
  }
934
942
 
935
943
  if (typeof e === 'object') {
@@ -1501,6 +1509,7 @@ export default class Store {
1501
1509
  this._die = true
1502
1510
  this.clearBrowserData()
1503
1511
  sb.clearInterval(this._seenInterval)
1512
+ sb.clearInterval(this._urlCheckInterval)
1504
1513
  if (this.realtime) this.realtime.stop()
1505
1514
  this.readyGate.close()
1506
1515
  window.removeEventListener('blur', this.sendPresence)
@@ -0,0 +1,31 @@
1
+ var test = require('tape')
2
+ var {normalizeCdpEvent} = require('./event.js')
3
+
4
+ test('normalize cdp event new signature', (t) => {
5
+ t.deepEqual(
6
+ normalizeCdpEvent('content_viewed', {
7
+ url: 'https://example.com/products/pd2345',
8
+ title: 'Bàn phím cơ không dây',
9
+ }, {event_source_id: 'evt_1234'}),
10
+ {
11
+ type: 'content_viewed',
12
+ payload: {
13
+ url: 'https://example.com/products/pd2345',
14
+ title: 'Bàn phím cơ không dây',
15
+ },
16
+ event_source_id: 'evt_1234',
17
+ },
18
+ )
19
+ t.end()
20
+ })
21
+
22
+ test('normalize cdp event without payload', (t) => {
23
+ t.deepEqual(normalizeCdpEvent('login'), {type: 'login'})
24
+ t.end()
25
+ })
26
+
27
+ test('normalize cdp event keeps old object signature', (t) => {
28
+ let event = {type: 'product_viewed', payload: {id: 'pd2345'}}
29
+ t.equal(normalizeCdpEvent(event), event)
30
+ t.end()
31
+ })