@hellotext/hellotext 2.5.6 → 2.5.7

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.
@@ -0,0 +1,412 @@
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; }
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); } }
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); }); }; }
6
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
7
+ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
8
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
9
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
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
+ import API from '../api';
12
+ import { Configuration } from '../core';
13
+
14
+ /**
15
+ * Manages browser Push subscriptions for Hellotext.
16
+ *
17
+ * @property {Promise<void>|null} ready - Initialization promise, available after initialize().
18
+ */
19
+ var Push = /*#__PURE__*/function () {
20
+ /**
21
+ * @param {Object} data - Push configuration from the business response.
22
+ * @param {String} data.public_key - Base64url-encoded VAPID public key.
23
+ */
24
+ function Push(data) {
25
+ _classCallCheck(this, Push);
26
+ this.publicKey = data.public_key;
27
+ this.serviceWorkerUrl = Configuration.push.serviceWorkerUrl;
28
+ this.channelId = Configuration.push.channelId;
29
+ this.ready = null;
30
+ this.registrationPromise = null;
31
+ this.subscribePromise = null;
32
+ this.unsubscribePromise = null;
33
+ this.syncPromise = null;
34
+ this.subscription = null;
35
+ this.retryTimeout = null;
36
+ this.retryAttempts = 0;
37
+ this.disposed = false;
38
+ }
39
+
40
+ /**
41
+ * Prepares the service worker and restores an existing subscription.
42
+ *
43
+ * @returns {Promise<void>}
44
+ */
45
+ _createClass(Push, [{
46
+ key: "initialize",
47
+ value: function initialize() {
48
+ this.ready = this.restoreSubscription();
49
+ return this.ready;
50
+ }
51
+
52
+ /**
53
+ * Registers an existing Hellotext subscription with the server without prompting.
54
+ *
55
+ * @private
56
+ * @returns {Promise<void>}
57
+ */
58
+ }, {
59
+ key: "restoreSubscription",
60
+ value: function () {
61
+ var _restoreSubscription = _asyncToGenerator(function* () {
62
+ var registration = yield this.getRegistration();
63
+ var subscription = yield registration.pushManager.getSubscription();
64
+ if (this.disposed || this.unsubscribePromise || !subscription || !this.owns(subscription)) {
65
+ return;
66
+ }
67
+ this.subscription = subscription;
68
+ yield this.sync();
69
+ });
70
+ function restoreSubscription() {
71
+ return _restoreSubscription.apply(this, arguments);
72
+ }
73
+ return restoreSubscription;
74
+ }()
75
+ /**
76
+ * Creates or reuses a Push subscription. Call from a user click handler.
77
+ * Concurrent calls share the same pending request.
78
+ *
79
+ * @returns {Promise<import('../api/response').Response|void>} Server registration response,
80
+ * or no value when disposed.
81
+ * Rejects when permission is denied or a browser or network operation fails.
82
+ */
83
+ }, {
84
+ key: "subscribe",
85
+ value: function subscribe() {
86
+ if (this.disposed) return Promise.resolve();
87
+ if (this.unsubscribePromise) return Promise.reject(new Error('Push unsubscribe is in progress'));
88
+ if (this.subscribePromise) return this.subscribePromise;
89
+
90
+ // Request permission before awaiting worker readiness to retain the click's user activation.
91
+ var permission = Notification.permission === 'default' ? Notification.requestPermission() : Promise.resolve(Notification.permission);
92
+ this.subscribePromise = this.createSubscription(permission).finally(() => {
93
+ this.subscribePromise = null;
94
+ });
95
+ return this.subscribePromise;
96
+ }
97
+
98
+ /**
99
+ * Waits for permission and worker readiness, then subscribes and registers with Hellotext.
100
+ *
101
+ * @private
102
+ * @param {Promise<NotificationPermission>} permission - Pending notification permission result.
103
+ * @returns {Promise<import('../api/response').Response|void>}
104
+ */
105
+ }, {
106
+ key: "createSubscription",
107
+ value: function () {
108
+ var _createSubscription = _asyncToGenerator(function* (permission) {
109
+ if ((yield permission) !== 'granted') throw new Error('Push permission was not granted');
110
+ var registration = yield this.getRegistration();
111
+ if (this.disposed) return;
112
+ var subscription = yield registration.pushManager.getSubscription();
113
+ if (this.disposed) return;
114
+ if (subscription && !this.owns(subscription)) {
115
+ throw new Error('The existing Push subscription belongs to a different application');
116
+ }
117
+ subscription || (subscription = yield registration.pushManager.subscribe({
118
+ userVisibleOnly: true,
119
+ applicationServerKey: this.applicationServerKey
120
+ }));
121
+ if (this.disposed) return;
122
+ this.subscription = subscription;
123
+ return this.sync();
124
+ });
125
+ function createSubscription(_x) {
126
+ return _createSubscription.apply(this, arguments);
127
+ }
128
+ return createSubscription;
129
+ }()
130
+ /**
131
+ * Disables the server identity and removes the browser subscription.
132
+ * Keeps the subscription when the server request fails so the caller can retry.
133
+ *
134
+ * @returns {Promise<import('../api/response').Response|null|void>} Server response, null when
135
+ * no subscription exists, or no value when disposed. Rejects when a browser or network
136
+ * operation fails.
137
+ */
138
+ }, {
139
+ key: "unsubscribe",
140
+ value: function unsubscribe() {
141
+ if (this.disposed) return Promise.resolve();
142
+ if (this.unsubscribePromise) return this.unsubscribePromise;
143
+ this.clearRetryTimeout();
144
+ this.unsubscribePromise = this.removeSubscription().finally(() => {
145
+ this.unsubscribePromise = null;
146
+ });
147
+ return this.unsubscribePromise;
148
+ }
149
+
150
+ /**
151
+ * Finishes pending registration before disabling and removing the subscription.
152
+ *
153
+ * @private
154
+ * @returns {Promise<import('../api/response').Response|null|void>}
155
+ */
156
+ }, {
157
+ key: "removeSubscription",
158
+ value: function () {
159
+ var _removeSubscription = _asyncToGenerator(function* () {
160
+ var _this$ready, _this$subscribePromis, _this$syncPromise;
161
+ yield (_this$ready = this.ready) === null || _this$ready === void 0 ? void 0 : _this$ready.catch(() => {});
162
+ yield (_this$subscribePromis = this.subscribePromise) === null || _this$subscribePromis === void 0 ? void 0 : _this$subscribePromis.catch(() => {});
163
+ yield (_this$syncPromise = this.syncPromise) === null || _this$syncPromise === void 0 ? void 0 : _this$syncPromise.catch(() => {});
164
+ var registration = yield this.getRegistration();
165
+ var subscription = (yield registration.pushManager.getSubscription()) || this.subscription;
166
+ if (this.disposed) return;
167
+ if (!subscription) return null;
168
+ if (!this.owns(subscription)) {
169
+ throw new Error('The existing Push subscription belongs to a different application');
170
+ }
171
+ this.subscription = subscription;
172
+ var response = yield API.pushIdentities.destroy({
173
+ subscription: subscription.toJSON()
174
+ });
175
+ if (response.failed) return response;
176
+ if (this.disposed) return;
177
+ yield subscription.unsubscribe();
178
+ this.subscription = null;
179
+ this.clearRetryTimeout();
180
+ return response;
181
+ });
182
+ function removeSubscription() {
183
+ return _removeSubscription.apply(this, arguments);
184
+ }
185
+ return removeSubscription;
186
+ }()
187
+ /**
188
+ * Registers the current subscription, sharing any request already in progress.
189
+ *
190
+ * @private
191
+ * @returns {Promise<import('../api/response').Response|void>}
192
+ */
193
+ }, {
194
+ key: "sync",
195
+ value: function sync() {
196
+ if (this.disposed) return Promise.resolve();
197
+ if (this.syncPromise) return this.syncPromise;
198
+ this.clearRetryTimeout();
199
+ this.syncPromise = this.registerIdentity().finally(() => {
200
+ this.syncPromise = null;
201
+ });
202
+ return this.syncPromise;
203
+ }
204
+
205
+ /**
206
+ * Sends the subscription to Hellotext and schedules a retry on failure.
207
+ *
208
+ * @private
209
+ * @returns {Promise<import('../api/response').Response>}
210
+ */
211
+ }, {
212
+ key: "registerIdentity",
213
+ value: function () {
214
+ var _registerIdentity = _asyncToGenerator(function* () {
215
+ try {
216
+ var response = yield API.pushIdentities.create(_objectSpread({
217
+ subscription: this.subscription.toJSON()
218
+ }, this.channelId ? {
219
+ channel_id: this.channelId
220
+ } : {}));
221
+ if (response.succeeded) {
222
+ this.retryAttempts = 0;
223
+ } else {
224
+ this.scheduleRetry();
225
+ }
226
+ return response;
227
+ } catch (error) {
228
+ this.scheduleRetry();
229
+ throw error;
230
+ }
231
+ });
232
+ function registerIdentity() {
233
+ return _registerIdentity.apply(this, arguments);
234
+ }
235
+ return registerIdentity;
236
+ }()
237
+ /**
238
+ * Schedules up to three registration retries with increasing delays.
239
+ *
240
+ * @private
241
+ * @returns {void}
242
+ */
243
+ }, {
244
+ key: "scheduleRetry",
245
+ value: function scheduleRetry() {
246
+ if (this.disposed || this.unsubscribePromise || this.retryTimeout || this.retryAttempts >= 3) return;
247
+ this.retryTimeout = setTimeout(() => {
248
+ this.retryTimeout = null;
249
+ this.sync().catch(() => {});
250
+ }, 1000 * 2 ** this.retryAttempts);
251
+ this.retryAttempts += 1;
252
+ }
253
+
254
+ /**
255
+ * Cancels a pending registration retry.
256
+ *
257
+ * @private
258
+ * @returns {void}
259
+ */
260
+ }, {
261
+ key: "clearRetryTimeout",
262
+ value: function clearRetryTimeout() {
263
+ clearTimeout(this.retryTimeout);
264
+ this.retryTimeout = null;
265
+ }
266
+
267
+ /**
268
+ * Stops registration retries and prevents further use of this instance.
269
+ *
270
+ * @returns {void}
271
+ */
272
+ }, {
273
+ key: "dispose",
274
+ value: function dispose() {
275
+ this.disposed = true;
276
+ this.clearRetryTimeout();
277
+ }
278
+
279
+ /**
280
+ * Whether this instance has a browser subscription, regardless of server registration.
281
+ *
282
+ * @returns {Boolean}
283
+ */
284
+ }, {
285
+ key: "subscribed",
286
+ get: function get() {
287
+ return !!this.subscription;
288
+ }
289
+
290
+ /**
291
+ * Decodes the public key into the bytes expected by PushManager.subscribe().
292
+ *
293
+ * @private
294
+ * @returns {Uint8Array}
295
+ */
296
+ }, {
297
+ key: "applicationServerKey",
298
+ get: function get() {
299
+ var base64 = this.publicKey.replace(/-/g, '+').replace(/_/g, '/');
300
+ return Uint8Array.from(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '=')), char => char.charCodeAt(0));
301
+ }
302
+
303
+ /**
304
+ * Checks whether a subscription uses this instance's public key.
305
+ *
306
+ * @private
307
+ * @param {PushSubscription} subscription - Browser subscription to check.
308
+ * @returns {Boolean}
309
+ */
310
+ }, {
311
+ key: "owns",
312
+ value: function owns(subscription) {
313
+ var _subscription$options;
314
+ var key = (_subscription$options = subscription.options) === null || _subscription$options === void 0 ? void 0 : _subscription$options.applicationServerKey;
315
+ if (!key) return false;
316
+ var actual = new Uint8Array(key);
317
+ var expected = this.applicationServerKey;
318
+ return actual.length === expected.length && actual.every((value, index) => value === expected[index]);
319
+ }
320
+
321
+ /**
322
+ * Gets the worker registration, sharing any pending lookup.
323
+ * Failed lookups can be retried by a later call.
324
+ *
325
+ * @private
326
+ * @returns {Promise<ServiceWorkerRegistration>}
327
+ */
328
+ }, {
329
+ key: "getRegistration",
330
+ value: function getRegistration() {
331
+ if (!this.registrationPromise) {
332
+ this.registrationPromise = this.loadRegistration().catch(error => {
333
+ this.registrationPromise = null;
334
+ throw error;
335
+ });
336
+ }
337
+ return this.registrationPromise;
338
+ }
339
+
340
+ /**
341
+ * Registers the configured worker or waits for the page's existing registration.
342
+ *
343
+ * @private
344
+ * @returns {Promise<ServiceWorkerRegistration>} An active registration.
345
+ * Rejects if registration fails or the worker readiness wait times out.
346
+ */
347
+ }, {
348
+ key: "loadRegistration",
349
+ value: function () {
350
+ var _loadRegistration = _asyncToGenerator(function* () {
351
+ if (this.serviceWorkerUrl) {
352
+ var registration = yield navigator.serviceWorker.register(this.serviceWorkerUrl);
353
+ if (registration.active && !registration.installing && !registration.waiting) {
354
+ // A returning visitor may have an older worker that does not include our Push handlers.
355
+ // Calling register() with the same URL can return that existing registration without
356
+ // checking whether the script served at that URL has changed since their last visit.
357
+ //
358
+ // If no replacement is already installing or waiting, explicitly check for an update
359
+ // before treating the active worker as ready. Awaiting update() completes the update
360
+ // check, but does not wait for a replacement worker to activate. The code below selects
361
+ // that replacement, if one was found, and waits for its activation before subscribing.
362
+ yield registration.update();
363
+ }
364
+ var worker = registration.installing || registration.waiting || registration.active;
365
+ if ((worker === null || worker === void 0 ? void 0 : worker.state) === 'activated') return registration;
366
+ return new Promise((resolve, reject) => {
367
+ var timeout = setTimeout(() => finish(new Error('Push service worker did not become active')), 10000);
368
+ var finish = error => {
369
+ clearTimeout(timeout);
370
+ worker === null || worker === void 0 ? void 0 : worker.removeEventListener('statechange', changed);
371
+ error ? reject(error) : resolve(registration);
372
+ };
373
+ var changed = () => {
374
+ if ((worker === null || worker === void 0 ? void 0 : worker.state) === 'activated') finish();
375
+ if ((worker === null || worker === void 0 ? void 0 : worker.state) === 'redundant') finish(new Error('Push service worker installation failed'));
376
+ };
377
+ worker === null || worker === void 0 ? void 0 : worker.addEventListener('statechange', changed);
378
+ changed();
379
+ });
380
+ }
381
+
382
+ // Reuse the service worker already registered for this page.
383
+ return new Promise((resolve, reject) => {
384
+ var timeout = setTimeout(() => reject(new Error('Push service worker is not available')), 10000);
385
+ navigator.serviceWorker.ready.then(registration => {
386
+ clearTimeout(timeout);
387
+ resolve(registration);
388
+ }, error => {
389
+ clearTimeout(timeout);
390
+ reject(error);
391
+ });
392
+ });
393
+ });
394
+ function loadRegistration() {
395
+ return _loadRegistration.apply(this, arguments);
396
+ }
397
+ return loadRegistration;
398
+ }()
399
+ /**
400
+ * Whether the current page provides the browser APIs required for Push.
401
+ *
402
+ * @returns {Boolean}
403
+ */
404
+ }], [{
405
+ key: "supported",
406
+ get: function get() {
407
+ return typeof window !== 'undefined' && window.isSecureContext === true && typeof navigator !== 'undefined' && 'serviceWorker' in navigator && typeof PushManager !== 'undefined' && typeof Notification !== 'undefined';
408
+ }
409
+ }]);
410
+ return Push;
411
+ }();
412
+ export { Push };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hellotext/hellotext",
3
- "version": "2.5.6",
3
+ "version": "2.5.7",
4
4
  "description": "Hellotext JavaScript Client",
5
5
  "source": "src/index.js",
6
6
  "main": "lib/index.cjs",
@@ -94,7 +94,7 @@
94
94
  "@emoji-mart/data": "^1.2.1",
95
95
  "@floating-ui/dom": "^1.7.3",
96
96
  "@hotwired/stimulus": "^3.0.0",
97
- "dompurify": "^3.4.13",
97
+ "dompurify": "^3.4.14",
98
98
  "emoji-mart": "^5.6.0"
99
99
  },
100
100
  "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610",
package/src/api/index.js CHANGED
@@ -5,6 +5,7 @@ import IdentificationsAPI from './identifications'
5
5
  import WebchatsAPI from './webchats'
6
6
  import WhatsAppWidgetsAPI from './whatsapp_widgets'
7
7
  import AcksAPI from './acks'
8
+ import PushIdentitiesAPI from './push/identities'
8
9
 
9
10
  // Browsers keep `fetch(..., { keepalive: true })` requests alive during page
10
11
  // unload/navigation, which is exactly the failure mode for analytics events
@@ -57,6 +58,10 @@ export default class API {
57
58
  static get acks() {
58
59
  return AcksAPI
59
60
  }
61
+
62
+ static get pushIdentities() {
63
+ return PushIdentitiesAPI
64
+ }
60
65
  }
61
66
 
62
67
  export { Response } from './response'
@@ -0,0 +1,42 @@
1
+ import Hellotext from '../../hellotext'
2
+
3
+ import { Configuration } from '../../core'
4
+ import { Response } from '../response'
5
+
6
+ class PushIdentitiesAPI {
7
+ static get endpoint() {
8
+ return Configuration.endpoint('public/push/identities')
9
+ }
10
+
11
+ static async create(data = {}) {
12
+ const response = await fetch(this.endpoint, {
13
+ method: 'POST',
14
+ keepalive: true,
15
+ headers: Hellotext.headers,
16
+ body: JSON.stringify({
17
+ ...data,
18
+ session: Hellotext.session,
19
+ origin: window.location.origin,
20
+ }),
21
+ })
22
+
23
+ return new Response(response.ok, response)
24
+ }
25
+
26
+ static async destroy(data = {}) {
27
+ const response = await fetch(this.endpoint, {
28
+ method: 'DELETE',
29
+ keepalive: true,
30
+ headers: Hellotext.headers,
31
+ body: JSON.stringify({
32
+ ...data,
33
+ session: Hellotext.session,
34
+ origin: window.location.origin,
35
+ }),
36
+ })
37
+
38
+ return new Response(response.ok, response)
39
+ }
40
+ }
41
+
42
+ export default PushIdentitiesAPI
@@ -0,0 +1,12 @@
1
+ class Push {
2
+ static serviceWorkerUrl = null
3
+ static channelId = null
4
+
5
+ static assign(props) {
6
+ this.serviceWorkerUrl = props?.serviceWorkerUrl || null
7
+ this.channelId = props?.channelId || null
8
+ return this
9
+ }
10
+ }
11
+
12
+ export { Push }
@@ -2,6 +2,7 @@ import { Forms } from './configuration/forms'
2
2
  import { Locale } from './configuration/locale'
3
3
  import { Webchat } from './configuration/webchat'
4
4
  import { WhatsApp } from './configuration/whatsapp'
5
+ import { Push } from './configuration/push'
5
6
 
6
7
  /**
7
8
  * @class Configuration
@@ -12,6 +13,7 @@ import { WhatsApp } from './configuration/whatsapp'
12
13
  * @property {Forms} [forms] - form configuration
13
14
  * @property {Webchat} [webchat] - webchat configuration
14
15
  * @property {WhatsApp} [whatsappWidget] - WhatsApp widget configuration
16
+ * @property {Push} [push] - push subscription configuration
15
17
  * @property {Locale} [locale] - locale configuration
16
18
  */
17
19
  class Configuration {
@@ -24,6 +26,7 @@ class Configuration {
24
26
  static forms = Forms
25
27
  static webchat = Webchat
26
28
  static whatsapp = WhatsApp
29
+ static push = Push
27
30
 
28
31
  /**
29
32
  *
@@ -47,6 +50,8 @@ class Configuration {
47
50
  this.webchat = Webchat.assign(value)
48
51
  } else if (key === 'whatsappWidget') {
49
52
  this.whatsapp = WhatsApp.assign(value)
53
+ } else if (key === 'push') {
54
+ this.push = Push.assign(value)
50
55
  } else {
51
56
  this[key] = value
52
57
  }
package/src/hellotext.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  Fingerprint,
7
7
  FormCollection,
8
8
  Page,
9
+ Push,
9
10
  Query,
10
11
  Session,
11
12
  User,
@@ -21,6 +22,7 @@ class Hellotext {
21
22
  static business
22
23
  static webchat
23
24
  static whatsapp
25
+ static push
24
26
 
25
27
  /**
26
28
  * initialize the module.
@@ -28,10 +30,13 @@ class Hellotext {
28
30
  * @param { Configuration } config
29
31
  */
30
32
  static async initialize(business, config = {}) {
33
+ this.push?.dispose()
34
+ this.push = null
35
+
31
36
  this.business = new Business(business)
32
37
  this.page = new Page()
33
38
 
34
- Configuration.assign(config)
39
+ Configuration.assign({ push: {}, ...config })
35
40
  Session.initialize(this.page)
36
41
 
37
42
  this.forms = new FormCollection()
@@ -39,6 +44,15 @@ class Hellotext {
39
44
  this.query = new Query()
40
45
 
41
46
  const businessData = await this.business.hydrate()
47
+
48
+ if (config.push !== false && businessData?.push?.public_key && Push.supported) {
49
+ this.push = new Push(businessData.push)
50
+
51
+ this.push.initialize().catch(error => {
52
+ console.warn('Hellotext Push initialization failed:', error)
53
+ })
54
+ }
55
+
42
56
  const webchatConfig =
43
57
  config.webchat === false
44
58
  ? false
@@ -27,6 +27,7 @@ const stylesheetLoadTimeout = 10000
27
27
  * @property {String} [locale] - Default dashboard locale for the business.
28
28
  * @property {String} [style_url] - Stylesheet URL to inject for dashboard-managed surfaces.
29
29
  * @property {BusinessWebchat|null} [webchat] - Dashboard webchat defaults.
30
+ * @property {{public_key: String}|null} [push] - Public VAPID key for push subscriptions.
30
31
  * @property {String|Array<String>} [whitelist] - Domain whitelist configuration.
31
32
  * @property {String} [subscription] - Current business subscription tier.
32
33
  */
@@ -4,6 +4,7 @@ export { Fingerprint } from './fingerprint'
4
4
  export { Form } from './form'
5
5
  export { FormCollection } from './form_collection'
6
6
  export { Page } from './page'
7
+ export { Push } from './push'
7
8
  export { Query } from './query'
8
9
  export { Session } from './session'
9
10
  export { User } from './user'