UNPKG

@braze/web-sdk

Version:

Braze SDK for web sites and other JS platforms.

553 lines (552 loc) 16.2 kB
import t from "../common/base-provider.js"; import r from "../managers/braze-instance.js"; import { STORAGE_KEYS as s } from "../managers/storage-manager.js"; import f from "../managers/subscription-manager.js"; import l from "../util/net.js"; import h from "../util/request-header-utils.js"; import { logger as b } from "../../shared-lib/index.js"; import { REQUEST_BACKOFF_MAX_SLEEP_MS_DEFAULT as c, REQUEST_BACKOFF_MIN_SLEEP_MS_DEFAULT as d, REQUEST_BACKOFF_SCALE_FACTOR_DEFAULT as m, } from "../common/constants.js"; import { randomInclusive as a } from "../util/math.js"; import { CONNECTION_VALID_THRESHOLD_MS as ii, MAX_RETRIES as ti, buildSseUrl as si, } from "./dust-connection-core.js"; import { DustWorkerBridge as ei, isSharedWorkerSupported as ni, } from "./dust-worker-bridge.js"; export default class sr extends t { constructor(i, t, s, e, n = !0) { super(), (this.B = i), (this.j = t), (this.h = s), (this.B = i), (this.j = t), (this.h = s), (this.mite = null), (this.Wi = null), (this.Gi = null), (this.Hi = null), (this.Oi = e || null), (this.Ki = null), (this.Vi = null), (this.T = null), (this._i = 0), (this.Qi = ti), (this.Yi = null), (this.Zi = null), (this.we = !0), (this.Se = null), (this.ke = null), (this.ye = null), (this.Re = null), (this.De = !1), (this.Me = new Map()), (this.xe = null), (this.We = null), (this.Fe = null), (this.Ue = this.Ne(n)), (this.Be = !1), (this.Te = 0), (this.ze = null), this.Ae(); } Ne(i) { return i ? ni() ? "sharedworker" : (b.info( "SharedWorker not supported, using direct EventSource (multi-tab will gracefully degrade)", ), "direct") : (b.info("Shared connection disabled, using direct EventSource"), "direct"); } Ge() { this.Fe = new ei({ Pe: (i) => { this.qe(i); }, He: () => { this.Je(), (this.ze = new Date().valueOf()); }, Le: () => { this.ze = null; }, Oe: (i) => { b.error(`Real-time messaging SharedWorker error: ${i}`); }, }); this.Fe.initialize() || (b.info( "SharedWorker initialization failed, falling back to direct EventSource", ), (this.Ue = "direct"), (this.Fe = null)); } Je() { (this._i = 0), (this.Se = null), (this.we = !0), (this.Be = !1), (this.Te = 0); } Ie() { var i; return ( !( !("sharedworker" === this.Ue ? Boolean(null === (i = this.Fe) || void 0 === i ? void 0 : i.Ke()) : Boolean(this.Vi)) || null === this.ze ) && new Date().valueOf() - this.ze >= ii ); } qe(i) { const t = i.type; if (!t) return void b.warn( `Received real-time message without type: ${JSON.stringify(i)}`, ); const s = "sharedworker" === this.Ue ? "SharedWorker" : "Direct"; b.info(`Received real-time message of type '${t}' via ${s}`); const e = this.Me.get(t); if (e && e.Ve() > 0) try { e.A(i); } catch (i) { b.error(`Error invoking subscription for message type '${t}': ${i}`); } else b.info(`No subscribers for real-time message type '${t}'`); } _e() { return "sharedworker" === this.Ue; } Qe() { return this.Ue; } Ae() { if (this.j) { const i = this.j.St(s.It.Xe), t = this.j.St(s.It.Ye), e = this.j.St(s.It.Ze), n = this.j.St(s.It.sn); i && t ? ((this.mite = i), (this.Wi = t), (this.Gi = e), (this.Hi = n), b.info("Restored real-time messaging configuration from storage")) : (i || t) && (b.warn( "Incomplete real-time messaging configuration in storage, clearing", ), this.en()); } } Hr() { this.xe || this.We || ((this.xe = this.on("ddr", (i) => { var t, s, e; const n = i.body; if (!n) return; const r = n.r_ms; if ("number" != typeof r) return; const o = n.e, h = (null === (t = this.h) || void 0 === t ? void 0 : t.vt()) || d, l = (null === (s = this.h) || void 0 === s ? void 0 : s.gt()) || m, u = (null === (e = this.h) || void 0 === e ? void 0 : e.bt()) || c, g = Math.min(u, a(h, h * l)), f = Math.round(r + g), p = o ? ` (${o})` : ""; b.info( `Admin requested disconnect${p}, reconnecting in ${f}ms (r_ms=${r} + backoff=${g})`, ), this.hn(), setTimeout(() => this.an(), f); })), (this.We = this.on("ttl", (i) => { const t = i.body; if (!t) return; const s = t.t_ms; if ("number" != typeof s) return; const e = s; if ( (b.info(`Time to live set to ${e}ms, will reconnect when expired`), "sharedworker" === this.Ue) ) return; const n = t.rcs; "string" == typeof n && (this.Ki = n), null !== this.Zi && window.clearTimeout(this.Zi), (this.Zi = window.setTimeout(() => { (this.Zi = null), b.info("Time to live expired, performing gapless reconnection"), this.ln(); }, e)); }))); } cn() { this.ke || this.ye || ((this.Re = () => { (this.De = !0), (this.we = !1); }), window.addEventListener("beforeunload", this.Re), (this.ke = () => { var i; this.De = !0; (this.Vi || (null === (i = this.Fe) || void 0 === i ? void 0 : i.Ke())) && (b.info("Page unloading, closing real-time connection gracefully"), (this.we = !1), this.hn()); }), window.addEventListener("pagehide", this.ke), (this.ye = (i) => { var t; const s = this.Vi || (null === (t = this.Fe) || void 0 === t ? void 0 : t.Ke()); i.persisted && this.un() && !s && (b.info("Page restored from bfcache, reconnecting"), (this.De = !1), this.Je(), this.an()); }), window.addEventListener("pageshow", this.ye)); } Et() { return this.T; } Lt(i) { this.T = i; } on(i, t) { if ("function" != typeof t) return null; let s = this.Me.get(i); return s || ((s = new f()), this.Me.set(i, s), r.S(s)), s.Ut(t); } dn(i, t) { const s = this.Me.get(i); s && s.removeSubscription(t); } un() { return Boolean(this.mite && this.Wi); } gn() { if (!this.Hi) return !1; return Math.floor(new Date().valueOf() / 1e3) >= this.Hi; } en() { (this.mite = null), (this.Wi = null), (this.Gi = null), (this.Hi = null), (this.Ki = null), this.j && (this.j.Vt(s.It.Xe), this.j.Vt(s.It.Ye), this.j.Vt(s.It.Ze), this.j.Vt(s.It.sn)); } mn(i, t) { const e = () => { "function" == typeof t && t(); }, n = this.B, r = this.j; if (!n || !r) return ( b.error("NetworkManager or StorageManager not available"), void e() ); if (!this.h || !this.h.fn()) return ( b.info("Real-time messaging is not enabled, skipping refresh"), void e() ); this.un() ? b.info("Refreshing real-time messaging configuration") : b.info("Fetching initial real-time messaging configuration"); const o = n.Z({}, !0), a = n.tt(o, h.it.pn), c = new Date().valueOf(); h.nt(r, h.it.pn, c), l.ot({ url: `${n.ht()}/dust/config`, headers: a, data: o, lt: (t) => { if (!n.ut(o, t, a)) return ( b.error( "Failed to validate server response for real-time messaging configuration", ), void e() ); n.ct(), t.mite && t.host ? ((this.mite = t.mite), (this.Wi = t.host), (this.Gi = t.auth || null), (this.Hi = t.expiration || null), b.info( "Received real-time messaging configuration from server", ), r.Pt(s.It.Xe, t.mite), r.Pt(s.It.Ye, t.host), t.auth ? r.Pt(s.It.Ze, t.auth) : r.Vt(s.It.Ze), t.expiration ? r.Pt(s.It.sn, t.expiration) : r.Vt(s.It.sn), this.an(), "function" == typeof i && i()) : (b.info( "Real-time messaging configuration not available - this SDK version may not be supported", ), this.en(), e()); }, error: (i) => { n.dt(i, "retrieving DUST config"), e(); }, }); } an() { if (!this.h || !this.h.fn()) return; if (!this.un()) return void b.error( "Cannot start real-time subscription without configuration", ); if (this.gn()) return ( b.info( "Real-time messaging auth token has expired, refreshing configuration", ), void this.mn( () => { this.an(); }, () => { b.error( "Failed to refresh expired real-time messaging configuration", ); }, ) ); const i = this.mite, t = this.Oi || this.Wi; if (i && t) switch (this.Ue) { case "sharedworker": this.vn(i, t); break; case "direct": this.Vi && (b.info( "Real-time connection already exists, closing before starting new subscription", ), this.hn()), this.wn(); } } bn() { const i = this.mite, t = this.Oi || this.Wi; return i && t ? si(t, i, this._i, this.Gi || void 0, this.Ki || void 0) : null; } vn(i, t) { var s, e, n; if ((this.Fe || this.Ge(), !this.Fe)) return ( b.info( "SharedWorker initialization failed, falling back to direct EventSource", ), (this.Ue = "direct"), void this.Sn() ); this.Oi && b.info(`Using custom real-time messaging host: ${this.Oi}`), b.info("Starting real-time subscription via SharedWorker"); const r = (null === (s = this.h) || void 0 === s ? void 0 : s.vt()) || d, o = (null === (e = this.h) || void 0 === e ? void 0 : e.bt()) || c, h = (null === (n = this.h) || void 0 === n ? void 0 : n.gt()) || m; this.Fe.connect({ mite: i, dustHost: t, auth: this.Gi || void 0, backoff: { minSleepMs: r, maxSleepMs: o, scaleFactor: h }, }); } Sn() { this.Vi && (b.info( "Real-time connection already exists, closing before starting new subscription", ), this.kn()), this.wn(); } wn(i) { const t = this.bn(); if (t) { this.Oi && b.info(`Using custom real-time messaging host: ${this.Oi}`); try { const s = new EventSource(t); (s.onopen = () => { i ? (b.info( "Gapless reconnection: new connection established, closing old connection", ), i.close()) : b.info("Real-time messaging connection established"), (this.Vi = s), (this._i = 0), (this.Se = null), (this.we = !0), (this.Be = !0), (this.ze = new Date().valueOf()); }), s.addEventListener("msg", (i) => { this.yn(i.data); }), (s.onerror = () => { const t = s.readyState; return ( this.De || (0 === t ? b.info("Real-time messaging failed to connect") : b.info("Real-time messaging connection lost")), i && this.Vi !== s ? (b.info( "Gapless reconnection failed, keeping old connection", ), s.close(), void (this.we && this._i < this.Qi && this.$n())) : (this.kn(), this.Be && (this.Te++, this.Te > 1) ? (b.info( "Real-time messaging connection lost twice after successful connect (likely multi-tab conflict), yielding to other tab", ), void (this.we = !1)) : void (this.we && this._i < this.Qi ? this.$n() : (this._i >= this.Qi && b.error( `Max retry attempts (${this.Qi}) reached for real-time messaging, giving up for current session`, ), (this.we = !1)))) ); }); } catch (i) { b.error( `Failed to create real-time messaging connection: ${ i instanceof Error ? i.message : String(i) }`, ); } } } $n() { var i, t, s; this._i++; const e = (null === (i = this.h) || void 0 === i ? void 0 : i.vt()) || d, n = (null === (t = this.h) || void 0 === t ? void 0 : t.gt()) || m, r = (null === (s = this.h) || void 0 === s ? void 0 : s.bt()) || c; let o = this.Se; (null == o || o < e) && (o = e); const h = Math.min(r, a(e, o * n)); (this.Se = h), b.info( `Retrying real-time messaging connection in ${h}ms (attempt ${this._i}/${this.Qi})`, ), (this.Yi = window.setTimeout(() => { (this.Yi = null), this.an(); }, h)); } ln() { if (this.un()) { if (this.gn()) return ( b.info( "Auth token expired during gapless reconnection, falling back to regular reconnection", ), this.hn(), void this.an() ); null !== this.Zi && (window.clearTimeout(this.Zi), (this.Zi = null)), this.wn(this.Vi || void 0); } else b.error("Cannot perform gapless reconnection without configuration"); } hn() { var i; switch (((this.ze = null), this.Ue)) { case "sharedworker": null === (i = this.Fe) || void 0 === i || i.disconnect(); break; case "direct": this.kn(); } } kn() { null !== this.Yi && (window.clearTimeout(this.Yi), (this.Yi = null)), null !== this.Zi && (window.clearTimeout(this.Zi), (this.Zi = null)), (this.ze = null), this.Vi && (this.Vi.close(), (this.Vi = null), b.info("Real-time messaging connection closed")); } yn(i) { try { const t = JSON.parse(i); this.qe(t); } catch (i) { b.warn( `Failed to parse real-time message: ${ i instanceof Error ? i.message : String(i) }`, ); } } changeUser(i = !1) { this.hn(), i || (this.un() && b.info( "Clearing cached real-time messaging configuration for user change", ), this.en()), this.Je(); } clearData(i = !1) { (this.we = !1), this.hn(), i && (this.un() && b.info( "Clearing cached real-time messaging configuration (wipeData)", ), this.en()), this.Je(); } destroy() { (this.we = !1), this.hn(), this.en(), this.Fe && (this.Fe.destroy(), (this.Fe = null)), this.xe && (this.dn("ddr", this.xe), (this.xe = null)), this.We && (this.dn("ttl", this.We), (this.We = null)), this.Re && (window.removeEventListener("beforeunload", this.Re), (this.Re = null)), this.ke && (window.removeEventListener("pagehide", this.ke), (this.ke = null)), this.ye && (window.removeEventListener("pageshow", this.ye), (this.ye = null)), this.T && (r.removeSubscription(this.T), (this.T = null)); } }