@hellotext/hellotext 2.4.51 → 2.4.53
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/dist/hellotext.js +1 -1
- package/lib/api/acks.cjs +2 -1
- package/lib/api/acks.js +6 -2
- package/lib/api/events.cjs +13 -3
- package/lib/api/events.js +13 -3
- package/lib/api/index.cjs +21 -0
- package/lib/api/index.js +23 -1
- package/lib/hellotext.cjs +8 -3
- package/lib/hellotext.js +9 -4
- package/lib/models/session.cjs +17 -2
- package/lib/models/session.js +17 -1
- package/package.json +1 -1
- package/src/api/acks.js +2 -1
- package/src/api/events.js +13 -3
- package/src/api/index.js +24 -0
- package/src/hellotext.js +28 -15
- package/src/models/session.js +11 -2
package/lib/api/acks.cjs
CHANGED
|
@@ -23,8 +23,9 @@ let AcksAPI = /*#__PURE__*/function () {
|
|
|
23
23
|
}
|
|
24
24
|
}, {
|
|
25
25
|
key: "send",
|
|
26
|
-
value: async function send() {
|
|
26
|
+
value: async function send(params = {}) {
|
|
27
27
|
const payload = {
|
|
28
|
+
...params,
|
|
28
29
|
session: _hellotext.default.session,
|
|
29
30
|
at: new Date().toISOString()
|
|
30
31
|
};
|
package/lib/api/acks.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
2
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
3
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
1
4
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
2
5
|
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
3
6
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
@@ -20,10 +23,11 @@ var AcksAPI = /*#__PURE__*/function () {
|
|
|
20
23
|
key: "send",
|
|
21
24
|
value: function () {
|
|
22
25
|
var _send = _asyncToGenerator(function* () {
|
|
23
|
-
var
|
|
26
|
+
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
27
|
+
var payload = _objectSpread(_objectSpread({}, params), {}, {
|
|
24
28
|
session: Hellotext.session,
|
|
25
29
|
at: new Date().toISOString()
|
|
26
|
-
};
|
|
30
|
+
});
|
|
27
31
|
fetch(this.endpoint, {
|
|
28
32
|
method: 'POST',
|
|
29
33
|
headers: Hellotext.headers,
|
package/lib/api/events.cjs
CHANGED
|
@@ -25,18 +25,28 @@ let EventsAPI = /*#__PURE__*/function () {
|
|
|
25
25
|
key: "create",
|
|
26
26
|
value: async function create({
|
|
27
27
|
headers,
|
|
28
|
-
body
|
|
28
|
+
body,
|
|
29
|
+
keepalive = false
|
|
29
30
|
}) {
|
|
30
31
|
if (_models.Query.inPreviewMode) {
|
|
31
32
|
return new _response.Response(true, {
|
|
32
33
|
received: true
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
|
-
const
|
|
36
|
+
const fetchOptions = {
|
|
36
37
|
method: 'POST',
|
|
37
38
|
headers,
|
|
38
39
|
body: JSON.stringify(body)
|
|
39
|
-
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Do not send `keepalive: false`. The track caller opts in only for payloads
|
|
43
|
+
// that fit the browser keepalive budget; omitting the key for larger events
|
|
44
|
+
// preserves normal fetch behavior for rich carts/orders instead of asking
|
|
45
|
+
// the browser to use an unload-safe transport it may reject.
|
|
46
|
+
if (keepalive) {
|
|
47
|
+
fetchOptions.keepalive = true;
|
|
48
|
+
}
|
|
49
|
+
const response = await fetch(this.endpoint, fetchOptions);
|
|
40
50
|
return new _response.Response(response.status === 200, await response.json());
|
|
41
51
|
}
|
|
42
52
|
}]);
|
package/lib/api/events.js
CHANGED
|
@@ -23,18 +23,28 @@ var EventsAPI = /*#__PURE__*/function () {
|
|
|
23
23
|
var _create = _asyncToGenerator(function* (_ref) {
|
|
24
24
|
var {
|
|
25
25
|
headers,
|
|
26
|
-
body
|
|
26
|
+
body,
|
|
27
|
+
keepalive = false
|
|
27
28
|
} = _ref;
|
|
28
29
|
if (Query.inPreviewMode) {
|
|
29
30
|
return new Response(true, {
|
|
30
31
|
received: true
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
|
-
var
|
|
34
|
+
var fetchOptions = {
|
|
34
35
|
method: 'POST',
|
|
35
36
|
headers,
|
|
36
37
|
body: JSON.stringify(body)
|
|
37
|
-
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Do not send `keepalive: false`. The track caller opts in only for payloads
|
|
41
|
+
// that fit the browser keepalive budget; omitting the key for larger events
|
|
42
|
+
// preserves normal fetch behavior for rich carts/orders instead of asking
|
|
43
|
+
// the browser to use an unload-safe transport it may reject.
|
|
44
|
+
if (keepalive) {
|
|
45
|
+
fetchOptions.keepalive = true;
|
|
46
|
+
}
|
|
47
|
+
var response = yield fetch(this.endpoint, fetchOptions);
|
|
38
48
|
return new Response(response.status === 200, yield response.json());
|
|
39
49
|
});
|
|
40
50
|
function create(_x) {
|
package/lib/api/index.cjs
CHANGED
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "Response", {
|
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
12
|
exports.default = void 0;
|
|
13
|
+
exports.keepaliveFor = keepaliveFor;
|
|
13
14
|
var _businesses = _interopRequireDefault(require("./businesses"));
|
|
14
15
|
var _events = _interopRequireDefault(require("./events"));
|
|
15
16
|
var _forms = _interopRequireDefault(require("./forms"));
|
|
@@ -23,6 +24,26 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+
|
|
|
23
24
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
24
25
|
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
25
26
|
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
27
|
+
// Browsers keep `fetch(..., { keepalive: true })` requests alive during page
|
|
28
|
+
// unload/navigation, which is exactly the failure mode for analytics events
|
|
29
|
+
// fired from checkout redirects, form submissions, tab closes, and pixel
|
|
30
|
+
// lifecycles. The platform also caps keepalive request bodies around 64KB, and
|
|
31
|
+
// browsers can reject or drop oversized requests instead of making delivery
|
|
32
|
+
// more reliable. Keep the limit below that ceiling so the SDK only opts in when
|
|
33
|
+
// the payload is small enough for the keepalive transport contract.
|
|
34
|
+
const KEEPALIVE_BODY_LIMIT = 60000;
|
|
35
|
+
function keepaliveFor(body) {
|
|
36
|
+
const serializedBody = JSON.stringify(body);
|
|
37
|
+
|
|
38
|
+
// Use byte size when the runtime exposes Blob because non-ASCII JSON can be
|
|
39
|
+
// larger on the wire than its JavaScript string length. The string-length
|
|
40
|
+
// fallback keeps older/non-browser runtimes conservative without pulling in
|
|
41
|
+
// another encoder just for this transport hint.
|
|
42
|
+
if (typeof Blob === 'undefined') {
|
|
43
|
+
return serializedBody.length < KEEPALIVE_BODY_LIMIT;
|
|
44
|
+
}
|
|
45
|
+
return new Blob([serializedBody]).size < KEEPALIVE_BODY_LIMIT;
|
|
46
|
+
}
|
|
26
47
|
let API = /*#__PURE__*/function () {
|
|
27
48
|
function API() {
|
|
28
49
|
_classCallCheck(this, API);
|
package/lib/api/index.js
CHANGED
|
@@ -9,6 +9,27 @@ import FormsAPI from './forms';
|
|
|
9
9
|
import IdentificationsAPI from './identifications';
|
|
10
10
|
import WebchatsAPI from './webchats';
|
|
11
11
|
import AcksAPI from './acks';
|
|
12
|
+
|
|
13
|
+
// Browsers keep `fetch(..., { keepalive: true })` requests alive during page
|
|
14
|
+
// unload/navigation, which is exactly the failure mode for analytics events
|
|
15
|
+
// fired from checkout redirects, form submissions, tab closes, and pixel
|
|
16
|
+
// lifecycles. The platform also caps keepalive request bodies around 64KB, and
|
|
17
|
+
// browsers can reject or drop oversized requests instead of making delivery
|
|
18
|
+
// more reliable. Keep the limit below that ceiling so the SDK only opts in when
|
|
19
|
+
// the payload is small enough for the keepalive transport contract.
|
|
20
|
+
var KEEPALIVE_BODY_LIMIT = 60000;
|
|
21
|
+
function keepaliveFor(body) {
|
|
22
|
+
var serializedBody = JSON.stringify(body);
|
|
23
|
+
|
|
24
|
+
// Use byte size when the runtime exposes Blob because non-ASCII JSON can be
|
|
25
|
+
// larger on the wire than its JavaScript string length. The string-length
|
|
26
|
+
// fallback keeps older/non-browser runtimes conservative without pulling in
|
|
27
|
+
// another encoder just for this transport hint.
|
|
28
|
+
if (typeof Blob === 'undefined') {
|
|
29
|
+
return serializedBody.length < KEEPALIVE_BODY_LIMIT;
|
|
30
|
+
}
|
|
31
|
+
return new Blob([serializedBody]).size < KEEPALIVE_BODY_LIMIT;
|
|
32
|
+
}
|
|
12
33
|
var API = /*#__PURE__*/function () {
|
|
13
34
|
function API() {
|
|
14
35
|
_classCallCheck(this, API);
|
|
@@ -47,4 +68,5 @@ var API = /*#__PURE__*/function () {
|
|
|
47
68
|
return API;
|
|
48
69
|
}();
|
|
49
70
|
export { API as default };
|
|
50
|
-
export { Response } from './response';
|
|
71
|
+
export { Response } from './response';
|
|
72
|
+
export { keepaliveFor };
|
package/lib/hellotext.cjs
CHANGED
|
@@ -29,9 +29,9 @@ let Hellotext = /*#__PURE__*/function () {
|
|
|
29
29
|
*/
|
|
30
30
|
async function initialize(business, config = {}) {
|
|
31
31
|
this.business = new _models.Business(business);
|
|
32
|
-
_core.Configuration.assign(config);
|
|
33
|
-
_models.Session.initialize();
|
|
34
32
|
this.page = new _models.Page();
|
|
33
|
+
_core.Configuration.assign(config);
|
|
34
|
+
_models.Session.initialize(this.page);
|
|
35
35
|
this.forms = new _models.FormCollection();
|
|
36
36
|
this.query = new _models.Query();
|
|
37
37
|
const businessData = await this.business.hydrate();
|
|
@@ -104,7 +104,12 @@ let Hellotext = /*#__PURE__*/function () {
|
|
|
104
104
|
delete body.headers;
|
|
105
105
|
return await _api.default.events.create({
|
|
106
106
|
headers,
|
|
107
|
-
body
|
|
107
|
+
body,
|
|
108
|
+
// Track is the SDK's unload-sensitive analytics path. Keepalive belongs
|
|
109
|
+
// here rather than on identify/forms/webchat calls because event tracking
|
|
110
|
+
// is allowed to be fire-and-navigate, while those other calls have
|
|
111
|
+
// stronger request/response or interaction contracts.
|
|
112
|
+
keepalive: (0, _api.keepaliveFor)(body)
|
|
108
113
|
});
|
|
109
114
|
}
|
|
110
115
|
|
package/lib/hellotext.js
CHANGED
|
@@ -9,7 +9,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
|
|
|
9
9
|
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
10
10
|
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
11
11
|
import { Configuration, Event } from './core';
|
|
12
|
-
import API, { Response } from './api';
|
|
12
|
+
import API, { Response, keepaliveFor } from './api';
|
|
13
13
|
import { Business, Fingerprint, FormCollection, Page, Query, Session, User, Webchat } from './models';
|
|
14
14
|
import { NotInitializedError } from './errors';
|
|
15
15
|
var Hellotext = /*#__PURE__*/function () {
|
|
@@ -28,9 +28,9 @@ var Hellotext = /*#__PURE__*/function () {
|
|
|
28
28
|
var _initialize = _asyncToGenerator(function* (business) {
|
|
29
29
|
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
30
30
|
this.business = new Business(business);
|
|
31
|
-
Configuration.assign(config);
|
|
32
|
-
Session.initialize();
|
|
33
31
|
this.page = new Page();
|
|
32
|
+
Configuration.assign(config);
|
|
33
|
+
Session.initialize(this.page);
|
|
34
34
|
this.forms = new FormCollection();
|
|
35
35
|
this.query = new Query();
|
|
36
36
|
var businessData = yield this.business.hydrate();
|
|
@@ -101,7 +101,12 @@ var Hellotext = /*#__PURE__*/function () {
|
|
|
101
101
|
delete body.headers;
|
|
102
102
|
return yield API.events.create({
|
|
103
103
|
headers,
|
|
104
|
-
body
|
|
104
|
+
body,
|
|
105
|
+
// Track is the SDK's unload-sensitive analytics path. Keepalive belongs
|
|
106
|
+
// here rather than on identify/forms/webchat calls because event tracking
|
|
107
|
+
// is allowed to be fire-and-navigate, while those other calls have
|
|
108
|
+
// stronger request/response or interaction contracts.
|
|
109
|
+
keepalive: keepaliveFor(body)
|
|
105
110
|
});
|
|
106
111
|
});
|
|
107
112
|
function track(_x2) {
|
package/lib/models/session.cjs
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.Session = void 0;
|
|
7
7
|
var _core = require("../core");
|
|
8
8
|
var _cookies = require("./cookies");
|
|
9
|
+
var _page2 = require("./page");
|
|
9
10
|
var _query2 = require("./query");
|
|
10
11
|
var _api = _interopRequireDefault(require("../api"));
|
|
11
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -19,6 +20,7 @@ var id = 0;
|
|
|
19
20
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
|
20
21
|
var _session = /*#__PURE__*/_classPrivateFieldLooseKey("session");
|
|
21
22
|
var _query = /*#__PURE__*/_classPrivateFieldLooseKey("query");
|
|
23
|
+
var _page = /*#__PURE__*/_classPrivateFieldLooseKey("page");
|
|
22
24
|
let Session = /*#__PURE__*/function () {
|
|
23
25
|
function Session() {
|
|
24
26
|
_classCallCheck(this, Session);
|
|
@@ -36,14 +38,23 @@ let Session = /*#__PURE__*/function () {
|
|
|
36
38
|
_cookies.Cookies.delete('hello_session_ack_at');
|
|
37
39
|
}
|
|
38
40
|
if (!_cookies.Cookies.get('hello_session_ack_at')) {
|
|
39
|
-
_api.default.acks.send();
|
|
41
|
+
_api.default.acks.send(this.ackPayload);
|
|
40
42
|
_cookies.Cookies.set('hello_session_ack_at', new Date().toISOString());
|
|
41
43
|
}
|
|
42
44
|
return _classPrivateFieldLooseBase(this, _session)[_session];
|
|
43
45
|
}
|
|
46
|
+
}, {
|
|
47
|
+
key: "ackPayload",
|
|
48
|
+
get: function () {
|
|
49
|
+
var _classPrivateFieldLoo;
|
|
50
|
+
return {
|
|
51
|
+
utm_params: ((_classPrivateFieldLoo = _classPrivateFieldLooseBase(this, _page)[_page]) === null || _classPrivateFieldLoo === void 0 ? void 0 : _classPrivateFieldLoo.utmParams) || {}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
44
54
|
}, {
|
|
45
55
|
key: "initialize",
|
|
46
|
-
value: function initialize() {
|
|
56
|
+
value: function initialize(page = new _page2.Page()) {
|
|
57
|
+
_classPrivateFieldLooseBase(this, _page)[_page] = page;
|
|
47
58
|
_classPrivateFieldLooseBase(this, _query)[_query] = new _query2.Query();
|
|
48
59
|
this.session = _classPrivateFieldLooseBase(this, _query)[_query].session || _core.Configuration.session || _cookies.Cookies.get('hello_session');
|
|
49
60
|
if (!this.session && _core.Configuration.autoGenerateSession) {
|
|
@@ -61,4 +72,8 @@ Object.defineProperty(Session, _session, {
|
|
|
61
72
|
Object.defineProperty(Session, _query, {
|
|
62
73
|
writable: true,
|
|
63
74
|
value: void 0
|
|
75
|
+
});
|
|
76
|
+
Object.defineProperty(Session, _page, {
|
|
77
|
+
writable: true,
|
|
78
|
+
value: void 0
|
|
64
79
|
});
|
package/lib/models/session.js
CHANGED
|
@@ -8,10 +8,12 @@ var id = 0;
|
|
|
8
8
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
|
9
9
|
import { Configuration } from '../core';
|
|
10
10
|
import { Cookies } from './cookies';
|
|
11
|
+
import { Page } from './page';
|
|
11
12
|
import { Query } from './query';
|
|
12
13
|
import API from '../api';
|
|
13
14
|
var _session = /*#__PURE__*/_classPrivateFieldLooseKey("session");
|
|
14
15
|
var _query = /*#__PURE__*/_classPrivateFieldLooseKey("query");
|
|
16
|
+
var _page = /*#__PURE__*/_classPrivateFieldLooseKey("page");
|
|
15
17
|
var Session = /*#__PURE__*/function () {
|
|
16
18
|
function Session() {
|
|
17
19
|
_classCallCheck(this, Session);
|
|
@@ -29,14 +31,24 @@ var Session = /*#__PURE__*/function () {
|
|
|
29
31
|
Cookies.delete('hello_session_ack_at');
|
|
30
32
|
}
|
|
31
33
|
if (!Cookies.get('hello_session_ack_at')) {
|
|
32
|
-
API.acks.send();
|
|
34
|
+
API.acks.send(this.ackPayload);
|
|
33
35
|
Cookies.set('hello_session_ack_at', new Date().toISOString());
|
|
34
36
|
}
|
|
35
37
|
return _classPrivateFieldLooseBase(this, _session)[_session];
|
|
36
38
|
}
|
|
39
|
+
}, {
|
|
40
|
+
key: "ackPayload",
|
|
41
|
+
get: function get() {
|
|
42
|
+
var _classPrivateFieldLoo;
|
|
43
|
+
return {
|
|
44
|
+
utm_params: ((_classPrivateFieldLoo = _classPrivateFieldLooseBase(this, _page)[_page]) === null || _classPrivateFieldLoo === void 0 ? void 0 : _classPrivateFieldLoo.utmParams) || {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
37
47
|
}, {
|
|
38
48
|
key: "initialize",
|
|
39
49
|
value: function initialize() {
|
|
50
|
+
var page = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Page();
|
|
51
|
+
_classPrivateFieldLooseBase(this, _page)[_page] = page;
|
|
40
52
|
_classPrivateFieldLooseBase(this, _query)[_query] = new Query();
|
|
41
53
|
this.session = _classPrivateFieldLooseBase(this, _query)[_query].session || Configuration.session || Cookies.get('hello_session');
|
|
42
54
|
if (!this.session && Configuration.autoGenerateSession) {
|
|
@@ -54,4 +66,8 @@ Object.defineProperty(Session, _query, {
|
|
|
54
66
|
writable: true,
|
|
55
67
|
value: void 0
|
|
56
68
|
});
|
|
69
|
+
Object.defineProperty(Session, _page, {
|
|
70
|
+
writable: true,
|
|
71
|
+
value: void 0
|
|
72
|
+
});
|
|
57
73
|
export { Session };
|
package/package.json
CHANGED
package/src/api/acks.js
CHANGED
package/src/api/events.js
CHANGED
|
@@ -8,16 +8,26 @@ export default class EventsAPI {
|
|
|
8
8
|
return Configuration.endpoint('track/events')
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
static async create({ headers, body }) {
|
|
11
|
+
static async create({ headers, body, keepalive = false }) {
|
|
12
12
|
if (Query.inPreviewMode) {
|
|
13
13
|
return new Response(true, { received: true })
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const fetchOptions = {
|
|
17
17
|
method: 'POST',
|
|
18
18
|
headers,
|
|
19
19
|
body: JSON.stringify(body),
|
|
20
|
-
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Do not send `keepalive: false`. The track caller opts in only for payloads
|
|
23
|
+
// that fit the browser keepalive budget; omitting the key for larger events
|
|
24
|
+
// preserves normal fetch behavior for rich carts/orders instead of asking
|
|
25
|
+
// the browser to use an unload-safe transport it may reject.
|
|
26
|
+
if (keepalive) {
|
|
27
|
+
fetchOptions.keepalive = true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const response = await fetch(this.endpoint, fetchOptions)
|
|
21
31
|
|
|
22
32
|
return new Response(response.status === 200, await response.json())
|
|
23
33
|
}
|
package/src/api/index.js
CHANGED
|
@@ -5,6 +5,29 @@ import IdentificationsAPI from './identifications'
|
|
|
5
5
|
import WebchatsAPI from './webchats'
|
|
6
6
|
import AcksAPI from './acks'
|
|
7
7
|
|
|
8
|
+
// Browsers keep `fetch(..., { keepalive: true })` requests alive during page
|
|
9
|
+
// unload/navigation, which is exactly the failure mode for analytics events
|
|
10
|
+
// fired from checkout redirects, form submissions, tab closes, and pixel
|
|
11
|
+
// lifecycles. The platform also caps keepalive request bodies around 64KB, and
|
|
12
|
+
// browsers can reject or drop oversized requests instead of making delivery
|
|
13
|
+
// more reliable. Keep the limit below that ceiling so the SDK only opts in when
|
|
14
|
+
// the payload is small enough for the keepalive transport contract.
|
|
15
|
+
const KEEPALIVE_BODY_LIMIT = 60000
|
|
16
|
+
|
|
17
|
+
function keepaliveFor(body) {
|
|
18
|
+
const serializedBody = JSON.stringify(body)
|
|
19
|
+
|
|
20
|
+
// Use byte size when the runtime exposes Blob because non-ASCII JSON can be
|
|
21
|
+
// larger on the wire than its JavaScript string length. The string-length
|
|
22
|
+
// fallback keeps older/non-browser runtimes conservative without pulling in
|
|
23
|
+
// another encoder just for this transport hint.
|
|
24
|
+
if (typeof Blob === 'undefined') {
|
|
25
|
+
return serializedBody.length < KEEPALIVE_BODY_LIMIT
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new Blob([serializedBody]).size < KEEPALIVE_BODY_LIMIT
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
export default class API {
|
|
9
32
|
static get businesses() {
|
|
10
33
|
return BusinessesAPI
|
|
@@ -32,3 +55,4 @@ export default class API {
|
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
export { Response } from './response'
|
|
58
|
+
export { keepaliveFor }
|
package/src/hellotext.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { Configuration, Event } from './core'
|
|
2
2
|
|
|
3
|
-
import API, { Response } from './api'
|
|
4
|
-
import {
|
|
3
|
+
import API, { Response, keepaliveFor } from './api'
|
|
4
|
+
import {
|
|
5
|
+
Business,
|
|
6
|
+
Fingerprint,
|
|
7
|
+
FormCollection,
|
|
8
|
+
Page,
|
|
9
|
+
Query,
|
|
10
|
+
Session,
|
|
11
|
+
User,
|
|
12
|
+
Webchat,
|
|
13
|
+
} from './models'
|
|
5
14
|
|
|
6
15
|
import { NotInitializedError } from './errors'
|
|
7
16
|
|
|
@@ -18,25 +27,28 @@ class Hellotext {
|
|
|
18
27
|
*/
|
|
19
28
|
static async initialize(business, config = {}) {
|
|
20
29
|
this.business = new Business(business)
|
|
30
|
+
this.page = new Page()
|
|
21
31
|
|
|
22
32
|
Configuration.assign(config)
|
|
23
|
-
Session.initialize()
|
|
33
|
+
Session.initialize(this.page)
|
|
24
34
|
|
|
25
|
-
this.page = new Page()
|
|
26
35
|
this.forms = new FormCollection()
|
|
27
36
|
|
|
28
37
|
this.query = new Query()
|
|
29
38
|
|
|
30
39
|
const businessData = await this.business.hydrate()
|
|
31
|
-
const webchatConfig =
|
|
32
|
-
false
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
const webchatConfig =
|
|
41
|
+
config.webchat === false
|
|
42
|
+
? false
|
|
43
|
+
: this.mergeWebchatConfig(
|
|
44
|
+
(businessData && businessData.webchat) || {},
|
|
45
|
+
config.webchat || {},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const hasExplicitBehaviourOverride =
|
|
36
49
|
config.webchat &&
|
|
37
50
|
config.webchat !== false &&
|
|
38
51
|
Object.prototype.hasOwnProperty.call(config.webchat, 'behaviour')
|
|
39
|
-
)
|
|
40
52
|
Configuration.webchat.behaviourOverride = hasExplicitBehaviourOverride
|
|
41
53
|
|
|
42
54
|
if (webchatConfig && webchatConfig.id) {
|
|
@@ -108,6 +120,11 @@ class Hellotext {
|
|
|
108
120
|
return await API.events.create({
|
|
109
121
|
headers,
|
|
110
122
|
body,
|
|
123
|
+
// Track is the SDK's unload-sensitive analytics path. Keepalive belongs
|
|
124
|
+
// here rather than on identify/forms/webchat calls because event tracking
|
|
125
|
+
// is allowed to be fire-and-navigate, while those other calls have
|
|
126
|
+
// stronger request/response or interaction contracts.
|
|
127
|
+
keepalive: keepaliveFor(body),
|
|
111
128
|
})
|
|
112
129
|
}
|
|
113
130
|
|
|
@@ -142,11 +159,7 @@ class Hellotext {
|
|
|
142
159
|
})
|
|
143
160
|
|
|
144
161
|
if (response.succeeded) {
|
|
145
|
-
User.remember(
|
|
146
|
-
externalId,
|
|
147
|
-
options.source,
|
|
148
|
-
fingerprint,
|
|
149
|
-
)
|
|
162
|
+
User.remember(externalId, options.source, fingerprint)
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
return response
|
package/src/models/session.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { Configuration } from '../core'
|
|
2
2
|
import { Cookies } from './cookies'
|
|
3
|
+
import { Page } from './page'
|
|
3
4
|
import { Query } from './query'
|
|
4
5
|
import API from '../api'
|
|
5
6
|
|
|
6
7
|
class Session {
|
|
7
8
|
static #session
|
|
8
9
|
static #query
|
|
10
|
+
static #page
|
|
9
11
|
|
|
10
12
|
static get session() {
|
|
11
13
|
return this.#session
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
static get ackPayload() {
|
|
17
|
+
return {
|
|
18
|
+
utm_params: this.#page?.utmParams || {},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
static set session(value) {
|
|
15
23
|
const oldSession = Cookies.get('hello_session')
|
|
16
24
|
|
|
@@ -22,14 +30,15 @@ class Session {
|
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
if (!Cookies.get('hello_session_ack_at')) {
|
|
25
|
-
API.acks.send()
|
|
33
|
+
API.acks.send(this.ackPayload)
|
|
26
34
|
Cookies.set('hello_session_ack_at', new Date().toISOString())
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
return this.#session
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
static initialize() {
|
|
40
|
+
static initialize(page = new Page()) {
|
|
41
|
+
this.#page = page
|
|
33
42
|
this.#query = new Query()
|
|
34
43
|
|
|
35
44
|
this.session = this.#query.session || Configuration.session || Cookies.get('hello_session')
|