@hellotext/hellotext 1.1.3 → 1.3.1
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/.babelrc +17 -1
- package/__tests__/event_emitter_test.js +1 -1
- package/__tests__/event_test.js +1 -1
- package/__tests__/hellotext_test.js +32 -3
- package/__tests__/query_test.js +13 -0
- package/lib/errors/invalidEvent.js +9 -4
- package/lib/errors/notInitializedError.js +9 -6
- package/lib/event.js +11 -7
- package/lib/eventEmitter.js +22 -15
- package/lib/hellotext.js +159 -90
- package/lib/index.js +10 -0
- package/lib/query.js +21 -0
- package/lib/response.js +26 -12
- package/package.json +17 -7
- package/src/errors/invalidEvent.js +8 -0
- package/src/errors/notInitializedError.js +10 -0
- package/src/event.js +15 -0
- package/src/eventEmitter.js +28 -0
- package/src/hellotext.js +154 -0
- package/src/index.js +3 -0
- package/src/query.js +21 -0
- package/src/response.js +21 -0
- package/webpack.config.js +18 -0
- package/index.js +0 -3
package/.babelrc
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
"presets": [
|
|
2
|
+
"presets": [
|
|
3
|
+
[
|
|
4
|
+
"@babel/preset-env",
|
|
5
|
+
{
|
|
6
|
+
"targets": {
|
|
7
|
+
"esmodules": true
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
],
|
|
12
|
+
"plugins": [
|
|
13
|
+
["@babel/plugin-proposal-private-methods", {
|
|
14
|
+
"loose": true
|
|
15
|
+
}],
|
|
16
|
+
["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
|
|
17
|
+
["@babel/plugin-proposal-class-properties", { "loose": true }]
|
|
18
|
+
]
|
|
3
19
|
}
|
package/__tests__/event_test.js
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
* @jest-environment jsdom
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import Hellotext from "../
|
|
5
|
+
import Hellotext from "../src/hellotext";
|
|
6
6
|
|
|
7
7
|
const getCookieValue = name => document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop()
|
|
8
8
|
|
|
9
|
+
const expireSession = () => {
|
|
10
|
+
document.cookie = "hello_session=;expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
9
17
|
describe("when trying to call methods before initializing the class", () => {
|
|
10
18
|
it("raises an error when Hellotext.session is called", () => {
|
|
11
19
|
expect(() => Hellotext.session).toThrowError()
|
|
@@ -142,8 +150,6 @@ describe("when session is stored in the cookie", function () {
|
|
|
142
150
|
|
|
143
151
|
|
|
144
152
|
describe(".removeEventListener", () => {
|
|
145
|
-
const business_id = "xy76ks"
|
|
146
|
-
|
|
147
153
|
beforeAll(() => {
|
|
148
154
|
const windowMock = {location: { search: "?hello_session=123" },}
|
|
149
155
|
jest.spyOn(global, 'window', 'get').mockImplementation(() => windowMock)
|
|
@@ -166,3 +172,26 @@ describe(".removeEventListener", () => {
|
|
|
166
172
|
expect(callback).toHaveBeenCalledTimes(0)
|
|
167
173
|
});
|
|
168
174
|
})
|
|
175
|
+
|
|
176
|
+
describe("when hello_preview query parameter is present", () => {
|
|
177
|
+
beforeAll(() => {
|
|
178
|
+
const windowMock = {location: { search: "?hello_preview" },}
|
|
179
|
+
jest.spyOn(global, 'window', 'get').mockImplementation(() => windowMock)
|
|
180
|
+
|
|
181
|
+
expireSession()
|
|
182
|
+
Hellotext.initialize(123)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe(".initialize", () => {
|
|
186
|
+
it("does not attempt to set the session", () => {
|
|
187
|
+
expect(getCookieValue("hello_session")).toEqual(undefined)
|
|
188
|
+
});
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe(".track", () => {
|
|
192
|
+
it("returns a success response without interacting with the API", async () => {
|
|
193
|
+
const response = await Hellotext.track("page.viewed")
|
|
194
|
+
expect(response.succeeded).toEqual(true)
|
|
195
|
+
});
|
|
196
|
+
})
|
|
197
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Query from "../src/query";
|
|
6
|
+
|
|
7
|
+
describe("#toHellotextParameter", () => {
|
|
8
|
+
it("prefixes the argument with hello_", () => {
|
|
9
|
+
const query = new Query()
|
|
10
|
+
|
|
11
|
+
expect(query.toHellotextParam("preview")).toEqual("hello_preview")
|
|
12
|
+
});
|
|
13
|
+
})
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.InvalidEvent = void 0;
|
|
1
7
|
class InvalidEvent extends Error {
|
|
2
8
|
constructor(event) {
|
|
3
|
-
super(
|
|
4
|
-
this.name = 'InvalidEvent'
|
|
9
|
+
super("".concat(event, " is not valid. Please provide a valid event name"));
|
|
10
|
+
this.name = 'InvalidEvent';
|
|
5
11
|
}
|
|
6
12
|
}
|
|
7
|
-
|
|
8
|
-
export { InvalidEvent }
|
|
13
|
+
exports.InvalidEvent = InvalidEvent;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NotInitializedError = void 0;
|
|
1
7
|
class NotInitializedError extends Error {
|
|
2
8
|
constructor() {
|
|
3
|
-
super(
|
|
4
|
-
|
|
5
|
-
)
|
|
6
|
-
this.name = 'NotInitializedError'
|
|
9
|
+
super('You need to initialize before tracking events. Call Hellotext.initialize and pass your public business id');
|
|
10
|
+
this.name = 'NotInitializedError';
|
|
7
11
|
}
|
|
8
12
|
}
|
|
9
|
-
|
|
10
|
-
export { NotInitializedError }
|
|
13
|
+
exports.NotInitializedError = NotInitializedError;
|
package/lib/event.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
static events = ["session-set"]
|
|
1
|
+
"use strict";
|
|
3
2
|
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
class Event {
|
|
4
8
|
static valid(name) {
|
|
5
|
-
return Event.exists(name)
|
|
9
|
+
return Event.exists(name);
|
|
6
10
|
}
|
|
7
|
-
|
|
8
11
|
static invalid(name) {
|
|
9
|
-
return !this.valid(name)
|
|
12
|
+
return !this.valid(name);
|
|
10
13
|
}
|
|
11
|
-
|
|
12
14
|
static exists(name) {
|
|
13
|
-
return this.events.find(
|
|
15
|
+
return this.events.find(eventName => eventName === name) !== undefined;
|
|
14
16
|
}
|
|
15
17
|
}
|
|
18
|
+
exports.default = Event;
|
|
19
|
+
Event.events = ["session-set"];
|
package/lib/eventEmitter.js
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
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; }
|
|
8
|
+
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; }
|
|
9
|
+
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; }
|
|
10
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
11
|
+
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); }
|
|
12
|
+
class EventEmitter {
|
|
2
13
|
constructor() {
|
|
3
|
-
this.subscribers = {}
|
|
14
|
+
this.subscribers = {};
|
|
4
15
|
}
|
|
5
|
-
|
|
6
16
|
addSubscriber(eventName, callback) {
|
|
7
|
-
this.subscribers = {
|
|
8
|
-
...this.subscribers,
|
|
17
|
+
this.subscribers = _objectSpread(_objectSpread({}, this.subscribers), {}, {
|
|
9
18
|
[eventName]: this.subscribers[eventName] ? [...this.subscribers[eventName], callback] : [callback]
|
|
10
|
-
}
|
|
19
|
+
});
|
|
11
20
|
}
|
|
12
|
-
|
|
13
21
|
removeSubscriber(eventName, callback) {
|
|
14
|
-
if(this.subscribers[eventName]) {
|
|
15
|
-
this.subscribers[eventName] = this.subscribers[eventName].filter(
|
|
22
|
+
if (this.subscribers[eventName]) {
|
|
23
|
+
this.subscribers[eventName] = this.subscribers[eventName].filter(cb => cb !== callback);
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
|
-
|
|
19
26
|
emit(eventName, data) {
|
|
20
|
-
this.subscribers[eventName].forEach(
|
|
21
|
-
subscriber(data)
|
|
22
|
-
})
|
|
27
|
+
this.subscribers[eventName].forEach(subscriber => {
|
|
28
|
+
subscriber(data);
|
|
29
|
+
});
|
|
23
30
|
}
|
|
24
|
-
|
|
25
31
|
get listeners() {
|
|
26
|
-
return Object.keys(this.subscribers).length !== 0
|
|
32
|
+
return Object.keys(this.subscribers).length !== 0;
|
|
27
33
|
}
|
|
28
34
|
}
|
|
35
|
+
exports.default = EventEmitter;
|
package/lib/hellotext.js
CHANGED
|
@@ -1,36 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _event = _interopRequireDefault(require("./event"));
|
|
8
|
+
var _eventEmitter2 = _interopRequireDefault(require("./eventEmitter"));
|
|
9
|
+
var _response = _interopRequireDefault(require("./response"));
|
|
10
|
+
var _query2 = _interopRequireDefault(require("./query"));
|
|
11
|
+
var _notInitializedError = require("./errors/notInitializedError");
|
|
12
|
+
var _invalidEvent = require("./errors/invalidEvent");
|
|
13
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
14
|
+
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; }
|
|
15
|
+
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; }
|
|
16
|
+
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; }
|
|
17
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
18
|
+
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); }
|
|
19
|
+
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); } }
|
|
20
|
+
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); }); }; }
|
|
21
|
+
function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
|
|
22
|
+
var id = 0;
|
|
23
|
+
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
|
24
|
+
var apiUrl = 'https://api.hellotext.com/v1/';
|
|
25
|
+
var _session = /*#__PURE__*/_classPrivateFieldLooseKey("session");
|
|
26
|
+
var _business = /*#__PURE__*/_classPrivateFieldLooseKey("business");
|
|
27
|
+
var _eventEmitter = /*#__PURE__*/_classPrivateFieldLooseKey("eventEmitter");
|
|
28
|
+
var _query = /*#__PURE__*/_classPrivateFieldLooseKey("query");
|
|
29
|
+
var _notInitialized = /*#__PURE__*/_classPrivateFieldLooseKey("notInitialized");
|
|
30
|
+
var _mintAnonymousSession = /*#__PURE__*/_classPrivateFieldLooseKey("mintAnonymousSession");
|
|
31
|
+
var _headers = /*#__PURE__*/_classPrivateFieldLooseKey("headers");
|
|
32
|
+
var _setSessionCookie = /*#__PURE__*/_classPrivateFieldLooseKey("setSessionCookie");
|
|
33
|
+
var _cookie = /*#__PURE__*/_classPrivateFieldLooseKey("cookie");
|
|
10
34
|
class Hellotext {
|
|
11
|
-
static #session
|
|
12
|
-
static #business
|
|
13
|
-
static #eventEmitter = new EventEmitter()
|
|
14
|
-
|
|
15
35
|
/**
|
|
16
36
|
* initialize the module.
|
|
17
37
|
* @param business public business id
|
|
18
38
|
*/
|
|
19
39
|
static initialize(business) {
|
|
20
|
-
this
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
_classPrivateFieldLooseBase(this, _business)[_business] = business;
|
|
41
|
+
_classPrivateFieldLooseBase(this, _query)[_query] = new _query2.default(window.location.search);
|
|
42
|
+
if (_classPrivateFieldLooseBase(this, _query)[_query].has("preview")) return;
|
|
43
|
+
var session = _classPrivateFieldLooseBase(this, _query)[_query].get("session") || _classPrivateFieldLooseBase(this, _cookie)[_cookie];
|
|
25
44
|
if (session && session !== "undefined" && session !== "null") {
|
|
26
|
-
this
|
|
27
|
-
this
|
|
45
|
+
_classPrivateFieldLooseBase(this, _session)[_session] = session;
|
|
46
|
+
_classPrivateFieldLooseBase(this, _setSessionCookie)[_setSessionCookie]();
|
|
28
47
|
} else {
|
|
29
|
-
this
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
48
|
+
_classPrivateFieldLooseBase(this, _mintAnonymousSession)[_mintAnonymousSession]().then(response => {
|
|
49
|
+
_classPrivateFieldLooseBase(this, _session)[_session] = response.id;
|
|
50
|
+
_classPrivateFieldLooseBase(this, _setSessionCookie)[_setSessionCookie]();
|
|
51
|
+
});
|
|
34
52
|
}
|
|
35
53
|
}
|
|
36
54
|
|
|
@@ -39,23 +57,33 @@ class Hellotext {
|
|
|
39
57
|
*
|
|
40
58
|
* @param { String } action a valid action name
|
|
41
59
|
* @param { Object } params
|
|
42
|
-
* @returns {Promise}
|
|
60
|
+
* @returns {Promise<Response>}
|
|
43
61
|
*/
|
|
44
|
-
static
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
static track(action) {
|
|
63
|
+
var _arguments = arguments,
|
|
64
|
+
_this = this;
|
|
65
|
+
return _asyncToGenerator(function* () {
|
|
66
|
+
var params = _arguments.length > 1 && _arguments[1] !== undefined ? _arguments[1] : {};
|
|
67
|
+
if (_classPrivateFieldLooseBase(_this, _notInitialized)[_notInitialized]) {
|
|
68
|
+
throw new _notInitializedError.NotInitializedError();
|
|
69
|
+
}
|
|
70
|
+
if (_classPrivateFieldLooseBase(_this, _query)[_query].has("preview")) {
|
|
71
|
+
return new _response.default(true, {
|
|
72
|
+
received: true
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
var response = yield fetch(apiUrl + 'track/events', {
|
|
76
|
+
headers: _classPrivateFieldLooseBase(_this, _headers)[_headers],
|
|
77
|
+
method: 'post',
|
|
78
|
+
body: JSON.stringify(_objectSpread(_objectSpread({
|
|
79
|
+
session: _this.session,
|
|
80
|
+
action
|
|
81
|
+
}, params), {}, {
|
|
82
|
+
url: params && params.url || window.location.href
|
|
83
|
+
}))
|
|
84
|
+
});
|
|
85
|
+
return new _response.default(response.status === 200, yield response.json());
|
|
86
|
+
})();
|
|
59
87
|
}
|
|
60
88
|
|
|
61
89
|
/**
|
|
@@ -64,9 +92,10 @@ class Hellotext {
|
|
|
64
92
|
* @param callback the callback. This method will be called with the payload
|
|
65
93
|
*/
|
|
66
94
|
static on(event, callback) {
|
|
67
|
-
if(
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
if (_event.default.invalid(event)) {
|
|
96
|
+
throw new _invalidEvent.InvalidEvent(event);
|
|
97
|
+
}
|
|
98
|
+
_classPrivateFieldLooseBase(this, _eventEmitter)[_eventEmitter].addSubscriber(event, callback);
|
|
70
99
|
}
|
|
71
100
|
|
|
72
101
|
/**
|
|
@@ -75,9 +104,10 @@ class Hellotext {
|
|
|
75
104
|
* @param callback the callback to remove
|
|
76
105
|
*/
|
|
77
106
|
static removeEventListener(event, callback) {
|
|
78
|
-
if(
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
if (_event.default.invalid(event)) {
|
|
108
|
+
throw new _invalidEvent.InvalidEvent(event);
|
|
109
|
+
}
|
|
110
|
+
_classPrivateFieldLooseBase(this, _eventEmitter)[_eventEmitter].removeSubscriber(event, callback);
|
|
81
111
|
}
|
|
82
112
|
|
|
83
113
|
/**
|
|
@@ -85,9 +115,10 @@ class Hellotext {
|
|
|
85
115
|
* @returns {String}
|
|
86
116
|
*/
|
|
87
117
|
static get session() {
|
|
88
|
-
if (this
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
if (_classPrivateFieldLooseBase(this, _notInitialized)[_notInitialized]) {
|
|
119
|
+
throw new _notInitializedError.NotInitializedError();
|
|
120
|
+
}
|
|
121
|
+
return _classPrivateFieldLooseBase(this, _session)[_session];
|
|
91
122
|
}
|
|
92
123
|
|
|
93
124
|
/**
|
|
@@ -95,51 +126,89 @@ class Hellotext {
|
|
|
95
126
|
* @returns {boolean}
|
|
96
127
|
*/
|
|
97
128
|
static get isInitialized() {
|
|
98
|
-
return this
|
|
129
|
+
return _classPrivateFieldLooseBase(this, _session)[_session] !== undefined;
|
|
99
130
|
}
|
|
100
131
|
|
|
101
132
|
// private
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
method: 'post',
|
|
114
|
-
headers: { Authorization: `Bearer ${this.#business}` },
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
return this.mintingPromise.json()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
static get #headers() {
|
|
121
|
-
if (this.#notInitialized) { throw new NotInitializedError() }
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
Authorization: `Bearer ${this.#business}`,
|
|
125
|
-
Accept: 'application.json',
|
|
126
|
-
'Content-Type': 'application/json',
|
|
133
|
+
}
|
|
134
|
+
function _get_notInitialized() {
|
|
135
|
+
return _classPrivateFieldLooseBase(this, _business)[_business] === undefined;
|
|
136
|
+
}
|
|
137
|
+
function _mintAnonymousSession2() {
|
|
138
|
+
return _mintAnonymousSession3.apply(this, arguments);
|
|
139
|
+
}
|
|
140
|
+
function _mintAnonymousSession3() {
|
|
141
|
+
_mintAnonymousSession3 = _asyncToGenerator(function* () {
|
|
142
|
+
if (_classPrivateFieldLooseBase(this, _notInitialized)[_notInitialized]) {
|
|
143
|
+
throw new _notInitializedError.NotInitializedError();
|
|
127
144
|
}
|
|
145
|
+
var trackingUrl = apiUrl + 'track/sessions';
|
|
146
|
+
this.mintingPromise = yield fetch(trackingUrl, {
|
|
147
|
+
method: 'post',
|
|
148
|
+
headers: {
|
|
149
|
+
Authorization: "Bearer ".concat(_classPrivateFieldLooseBase(this, _business)[_business])
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return this.mintingPromise.json();
|
|
153
|
+
});
|
|
154
|
+
return _mintAnonymousSession3.apply(this, arguments);
|
|
155
|
+
}
|
|
156
|
+
function _get_headers() {
|
|
157
|
+
if (_classPrivateFieldLooseBase(this, _notInitialized)[_notInitialized]) {
|
|
158
|
+
throw new _notInitializedError.NotInitializedError();
|
|
128
159
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
160
|
+
return {
|
|
161
|
+
Authorization: "Bearer ".concat(_classPrivateFieldLooseBase(this, _business)[_business]),
|
|
162
|
+
Accept: 'application.json',
|
|
163
|
+
'Content-Type': 'application/json'
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function _setSessionCookie2() {
|
|
167
|
+
if (_classPrivateFieldLooseBase(this, _notInitialized)[_notInitialized]) {
|
|
168
|
+
throw new _notInitializedError.NotInitializedError();
|
|
138
169
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return document.cookie.match('(^|;)\\s*' + 'hello_session' + '\\s*=\\s*([^;]+)')?.pop()
|
|
170
|
+
if (_classPrivateFieldLooseBase(this, _eventEmitter)[_eventEmitter].listeners) {
|
|
171
|
+
_classPrivateFieldLooseBase(this, _eventEmitter)[_eventEmitter].emit("session-set", _classPrivateFieldLooseBase(this, _session)[_session]);
|
|
142
172
|
}
|
|
173
|
+
document.cookie = "hello_session=".concat(_classPrivateFieldLooseBase(this, _session)[_session]);
|
|
143
174
|
}
|
|
144
|
-
|
|
145
|
-
|
|
175
|
+
function _get_cookie() {
|
|
176
|
+
var _document$cookie$matc;
|
|
177
|
+
return (_document$cookie$matc = document.cookie.match('(^|;)\\s*' + 'hello_session' + '\\s*=\\s*([^;]+)')) === null || _document$cookie$matc === void 0 ? void 0 : _document$cookie$matc.pop();
|
|
178
|
+
}
|
|
179
|
+
Object.defineProperty(Hellotext, _cookie, {
|
|
180
|
+
get: _get_cookie,
|
|
181
|
+
set: void 0
|
|
182
|
+
});
|
|
183
|
+
Object.defineProperty(Hellotext, _setSessionCookie, {
|
|
184
|
+
value: _setSessionCookie2
|
|
185
|
+
});
|
|
186
|
+
Object.defineProperty(Hellotext, _headers, {
|
|
187
|
+
get: _get_headers,
|
|
188
|
+
set: void 0
|
|
189
|
+
});
|
|
190
|
+
Object.defineProperty(Hellotext, _mintAnonymousSession, {
|
|
191
|
+
value: _mintAnonymousSession2
|
|
192
|
+
});
|
|
193
|
+
Object.defineProperty(Hellotext, _notInitialized, {
|
|
194
|
+
get: _get_notInitialized,
|
|
195
|
+
set: void 0
|
|
196
|
+
});
|
|
197
|
+
Object.defineProperty(Hellotext, _session, {
|
|
198
|
+
writable: true,
|
|
199
|
+
value: void 0
|
|
200
|
+
});
|
|
201
|
+
Object.defineProperty(Hellotext, _business, {
|
|
202
|
+
writable: true,
|
|
203
|
+
value: void 0
|
|
204
|
+
});
|
|
205
|
+
Object.defineProperty(Hellotext, _eventEmitter, {
|
|
206
|
+
writable: true,
|
|
207
|
+
value: new _eventEmitter2.default()
|
|
208
|
+
});
|
|
209
|
+
Object.defineProperty(Hellotext, _query, {
|
|
210
|
+
writable: true,
|
|
211
|
+
value: void 0
|
|
212
|
+
});
|
|
213
|
+
var _default = Hellotext;
|
|
214
|
+
exports.default = _default;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _hellotext = _interopRequireDefault(require("./hellotext"));
|
|
8
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
9
|
+
var _default = _hellotext.default;
|
|
10
|
+
exports.default = _default;
|
package/lib/query.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
class Query {
|
|
8
|
+
constructor(urlQueries) {
|
|
9
|
+
this.urlSearchParams = new URLSearchParams(urlQueries);
|
|
10
|
+
}
|
|
11
|
+
get(param) {
|
|
12
|
+
return this.urlSearchParams.get(this.toHellotextParam(param));
|
|
13
|
+
}
|
|
14
|
+
has(param) {
|
|
15
|
+
return this.urlSearchParams.has(this.toHellotextParam(param));
|
|
16
|
+
}
|
|
17
|
+
toHellotextParam(param) {
|
|
18
|
+
return "hello_".concat(param);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = Query;
|
package/lib/response.js
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
#success
|
|
3
|
-
#response
|
|
1
|
+
"use strict";
|
|
4
2
|
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
|
|
8
|
+
var id = 0;
|
|
9
|
+
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
|
10
|
+
var _success = /*#__PURE__*/_classPrivateFieldLooseKey("success");
|
|
11
|
+
var _response = /*#__PURE__*/_classPrivateFieldLooseKey("response");
|
|
12
|
+
class Response {
|
|
5
13
|
constructor(success, response) {
|
|
6
|
-
this
|
|
7
|
-
|
|
14
|
+
Object.defineProperty(this, _success, {
|
|
15
|
+
writable: true,
|
|
16
|
+
value: void 0
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(this, _response, {
|
|
19
|
+
writable: true,
|
|
20
|
+
value: void 0
|
|
21
|
+
});
|
|
22
|
+
_classPrivateFieldLooseBase(this, _success)[_success] = success;
|
|
23
|
+
_classPrivateFieldLooseBase(this, _response)[_response] = response;
|
|
8
24
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return this.#response
|
|
25
|
+
get data() {
|
|
26
|
+
return _classPrivateFieldLooseBase(this, _response)[_response];
|
|
12
27
|
}
|
|
13
|
-
|
|
14
28
|
get failed() {
|
|
15
|
-
return this
|
|
29
|
+
return _classPrivateFieldLooseBase(this, _success)[_success] === false;
|
|
16
30
|
}
|
|
17
|
-
|
|
18
31
|
get succeeded() {
|
|
19
|
-
return this
|
|
32
|
+
return _classPrivateFieldLooseBase(this, _success)[_success] === true;
|
|
20
33
|
}
|
|
21
34
|
}
|
|
35
|
+
exports.default = Response;
|
package/package.json
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hellotext/hellotext",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Hellotext JavaScript Client",
|
|
5
|
-
"
|
|
5
|
+
"source": "src/index.js",
|
|
6
|
+
"main": "lib/index.js",
|
|
6
7
|
"author": "Hellotext",
|
|
7
8
|
"license": "MIT",
|
|
8
9
|
"homepage": "https://github.com/hellotext/hellotext.js",
|
|
9
10
|
"devDependencies": {
|
|
11
|
+
"@babel/cli": "^7.20.7",
|
|
12
|
+
"@babel/core": "^7.20.12",
|
|
13
|
+
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
|
10
14
|
"@babel/preset-env": "^7.20.2",
|
|
11
15
|
"@testing-library/jest-dom": "^5.16.5",
|
|
16
|
+
"babel-loader": "^9.1.2",
|
|
12
17
|
"jest": "^29.3.1",
|
|
13
18
|
"jest-environment-jsdom": "^29.3.1",
|
|
14
|
-
"prettier": "2.8.2"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"lib": "lib"
|
|
19
|
+
"prettier": "2.8.2",
|
|
20
|
+
"webpack": "^5.75.0",
|
|
21
|
+
"webpack-cli": "^5.0.1"
|
|
18
22
|
},
|
|
19
23
|
"repository": {
|
|
20
24
|
"type": "git",
|
|
@@ -25,9 +29,15 @@
|
|
|
25
29
|
"javascript"
|
|
26
30
|
],
|
|
27
31
|
"scripts": {
|
|
28
|
-
"test": "NODE_ENV=test jest"
|
|
32
|
+
"test": "NODE_ENV=test jest",
|
|
33
|
+
"build:babel": "babel src -d lib",
|
|
34
|
+
"build:webpack": "webpack"
|
|
29
35
|
},
|
|
30
36
|
"bugs": {
|
|
31
37
|
"url": "https://github.com/hellotext/hellotext.js/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"core-js": "3.27.2",
|
|
41
|
+
"whatwg-fetch": "^3.6.2"
|
|
32
42
|
}
|
|
33
43
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class NotInitializedError extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super(
|
|
4
|
+
'You need to initialize before tracking events. Call Hellotext.initialize and pass your public business id',
|
|
5
|
+
)
|
|
6
|
+
this.name = 'NotInitializedError'
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { NotInitializedError }
|
package/src/event.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default class Event {
|
|
2
|
+
static events = ["session-set"]
|
|
3
|
+
|
|
4
|
+
static valid(name) {
|
|
5
|
+
return Event.exists(name)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static invalid(name) {
|
|
9
|
+
return !this.valid(name)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static exists(name) {
|
|
13
|
+
return this.events.find((eventName) => eventName === name) !== undefined
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default class EventEmitter {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.subscribers = {}
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
addSubscriber(eventName, callback) {
|
|
7
|
+
this.subscribers = {
|
|
8
|
+
...this.subscribers,
|
|
9
|
+
[eventName]: this.subscribers[eventName] ? [...this.subscribers[eventName], callback] : [callback]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
removeSubscriber(eventName, callback) {
|
|
14
|
+
if(this.subscribers[eventName]) {
|
|
15
|
+
this.subscribers[eventName] = this.subscribers[eventName].filter((cb) => cb !== callback)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
emit(eventName, data) {
|
|
20
|
+
this.subscribers[eventName].forEach((subscriber) => {
|
|
21
|
+
subscriber(data)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get listeners() {
|
|
26
|
+
return Object.keys(this.subscribers).length !== 0
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/hellotext.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import Event from "./event"
|
|
2
|
+
import EventEmitter from "./eventEmitter"
|
|
3
|
+
import Response from "./response";
|
|
4
|
+
import Query from "./query";
|
|
5
|
+
|
|
6
|
+
import { NotInitializedError } from './errors/notInitializedError'
|
|
7
|
+
import { InvalidEvent } from "./errors/invalidEvent"
|
|
8
|
+
|
|
9
|
+
const apiUrl = 'https://api.hellotext.com/v1/'
|
|
10
|
+
|
|
11
|
+
class Hellotext {
|
|
12
|
+
static #session
|
|
13
|
+
static #business
|
|
14
|
+
static #eventEmitter = new EventEmitter()
|
|
15
|
+
static #query;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* initialize the module.
|
|
19
|
+
* @param business public business id
|
|
20
|
+
*/
|
|
21
|
+
static initialize(business) {
|
|
22
|
+
this.#business = business
|
|
23
|
+
|
|
24
|
+
this.#query = new Query(window.location.search)
|
|
25
|
+
|
|
26
|
+
if(this.#query.has("preview")) return
|
|
27
|
+
|
|
28
|
+
const session = this.#query.get("session") || this.#cookie
|
|
29
|
+
|
|
30
|
+
if (session && session !== "undefined" && session !== "null") {
|
|
31
|
+
this.#session = session
|
|
32
|
+
this.#setSessionCookie()
|
|
33
|
+
} else {
|
|
34
|
+
this.#mintAnonymousSession()
|
|
35
|
+
.then(response => {
|
|
36
|
+
this.#session = response.id
|
|
37
|
+
this.#setSessionCookie()
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tracks an action that has happened on the page
|
|
44
|
+
*
|
|
45
|
+
* @param { String } action a valid action name
|
|
46
|
+
* @param { Object } params
|
|
47
|
+
* @returns {Promise<Response>}
|
|
48
|
+
*/
|
|
49
|
+
static async track(action, params = {}) {
|
|
50
|
+
if (this.#notInitialized) { throw new NotInitializedError() }
|
|
51
|
+
|
|
52
|
+
if(this.#query.has("preview")) {
|
|
53
|
+
return new Response(true, { received: true })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const response = await fetch(apiUrl + 'track/events', {
|
|
57
|
+
headers: this.#headers,
|
|
58
|
+
method: 'post',
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
session: this.session,
|
|
61
|
+
action,
|
|
62
|
+
...params,
|
|
63
|
+
url: (params && params.url) || window.location.href
|
|
64
|
+
}),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return new Response(response.status === 200, await response.json())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Registers an event listener
|
|
72
|
+
* @param event the name of the event to listen to
|
|
73
|
+
* @param callback the callback. This method will be called with the payload
|
|
74
|
+
*/
|
|
75
|
+
static on(event, callback) {
|
|
76
|
+
if(Event.invalid(event)) { throw new InvalidEvent(event) }
|
|
77
|
+
|
|
78
|
+
this.#eventEmitter.addSubscriber(event, callback)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Removes an event listener
|
|
83
|
+
* @param event the name of the event to remove
|
|
84
|
+
* @param callback the callback to remove
|
|
85
|
+
*/
|
|
86
|
+
static removeEventListener(event, callback) {
|
|
87
|
+
if(Event.invalid(event)) { throw new InvalidEvent(event) }
|
|
88
|
+
|
|
89
|
+
this.#eventEmitter.removeSubscriber(event, callback)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
*
|
|
94
|
+
* @returns {String}
|
|
95
|
+
*/
|
|
96
|
+
static get session() {
|
|
97
|
+
if (this.#notInitialized) { throw new NotInitializedError() }
|
|
98
|
+
|
|
99
|
+
return this.#session
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Determines if the session is set or not
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
static get isInitialized() {
|
|
107
|
+
return this.#session !== undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// private
|
|
111
|
+
|
|
112
|
+
static get #notInitialized() {
|
|
113
|
+
return this.#business === undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static async #mintAnonymousSession() {
|
|
117
|
+
if (this.#notInitialized) { throw new NotInitializedError() }
|
|
118
|
+
|
|
119
|
+
const trackingUrl = apiUrl + 'track/sessions'
|
|
120
|
+
|
|
121
|
+
this.mintingPromise = await fetch(trackingUrl, {
|
|
122
|
+
method: 'post',
|
|
123
|
+
headers: { Authorization: `Bearer ${this.#business}` },
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
return this.mintingPromise.json()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static get #headers() {
|
|
130
|
+
if (this.#notInitialized) { throw new NotInitializedError() }
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
Authorization: `Bearer ${this.#business}`,
|
|
134
|
+
Accept: 'application.json',
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static #setSessionCookie() {
|
|
140
|
+
if (this.#notInitialized) { throw new NotInitializedError() }
|
|
141
|
+
|
|
142
|
+
if(this.#eventEmitter.listeners) {
|
|
143
|
+
this.#eventEmitter.emit("session-set", this.#session)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
document.cookie = `hello_session=${this.#session}`
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static get #cookie() {
|
|
150
|
+
return document.cookie.match('(^|;)\\s*' + 'hello_session' + '\\s*=\\s*([^;]+)')?.pop()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default Hellotext
|
package/src/index.js
ADDED
package/src/query.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default class Query {
|
|
2
|
+
constructor(urlQueries) {
|
|
3
|
+
this.urlSearchParams = new URLSearchParams(urlQueries)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
get(param) {
|
|
7
|
+
return this.urlSearchParams.get(
|
|
8
|
+
this.toHellotextParam(param)
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
has(param) {
|
|
13
|
+
return this.urlSearchParams.has(
|
|
14
|
+
this.toHellotextParam(param)
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toHellotextParam(param) {
|
|
19
|
+
return `hello_${param}`
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/response.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default class Response {
|
|
2
|
+
#success
|
|
3
|
+
#response
|
|
4
|
+
|
|
5
|
+
constructor(success, response) {
|
|
6
|
+
this.#success = success
|
|
7
|
+
this.#response = response
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get data() {
|
|
11
|
+
return this.#response
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get failed() {
|
|
15
|
+
return this.#success === false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get succeeded() {
|
|
19
|
+
return this.#success === true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
mode: 'development',
|
|
3
|
+
entry: ['whatwg-fetch', './lib/hellotext.js'],
|
|
4
|
+
output: {
|
|
5
|
+
filename: 'hellotext.js'
|
|
6
|
+
},
|
|
7
|
+
module: {
|
|
8
|
+
rules: [
|
|
9
|
+
{
|
|
10
|
+
test: /\.m?js$/,
|
|
11
|
+
exclude: [/node_modules/],
|
|
12
|
+
use: [{
|
|
13
|
+
loader: 'babel-loader',
|
|
14
|
+
}]
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
};
|
package/index.js
DELETED