UNPKG

@braze/web-sdk

Version:

Braze SDK for web sites and other JS platforms.

521 lines (520 loc) 15.3 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 E } 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 { MAX_RETRIES as ii, buildSseUrl as ti, } from "./dust-connection-core.js"; import { DustWorkerBridge as si, isSharedWorkerSupported as ei, } from "./dust-worker-bridge.js"; export default class nr extends t { constructor(i, t, s, e, n = !0) { super(), (this.B = i), (this.C = t), (this.h = s), (this.B = i), (this.C = t), (this.h = s), (this.mite = null), (this.Wi = null), (this.Gi = null), (this.Hi = null), (this.Oi = e || null), (this.Ki = null), (this.Qi = null), (this.T = null), (this.Vi = 0), (this.Xi = ii), (this.Yi = null), (this.Zi = null), (this._i = !0), (this.we = null), (this.Se = null), (this.ke = null), (this.ye = null), (this.Re = !1), (this.Me = new Map()), (this.xe = null), (this.De = null), (this.We = null), (this.Fe = this.Ue(n)), (this.Ne = !1), (this.Te = 0), this.ze(); } Ue(i) { return i ? ei() ? "sharedworker" : (E.info( "SharedWorker not supported, using direct EventSource (multi-tab will gracefully degrade)", ), "direct") : (E.info("Shared connection disabled, using direct EventSource"), "direct"); } Ae() { this.We = new si({ Be: (i) => { this.Ge(i); }, Pe: () => { this.qe(); }, He: () => {}, Je: (i) => { E.error(`Real-time messaging SharedWorker error: ${i}`); }, }); this.We.initialize() || (E.info( "SharedWorker initialization failed, falling back to direct EventSource", ), (this.Fe = "direct"), (this.We = null)); } qe() { (this.Vi = 0), (this.we = null), (this._i = !0), (this.Ne = !1), (this.Te = 0); } Ge(i) { if (!i.type) return void E.warn( `Received real-time message without type: ${JSON.stringify(i)}`, ); const t = "sharedworker" === this.Fe ? "SharedWorker" : "Direct"; E.info(`Received real-time message of type '${i.type}' via ${t}`); const s = this.Me.get(i.type); if (s && s.Le() > 0) try { s.A(i); } catch (t) { E.error( `Error invoking subscription for message type '${i.type}': ${t}`, ); } else E.info(`No subscribers for real-time message type '${i.type}'`); } Oe() { return "sharedworker" === this.Fe; } Ie() { return this.Fe; } ze() { if (this.C) { const i = this.C.Rt(s.Tt.Ke), t = this.C.Rt(s.Tt.Qe), e = this.C.Rt(s.Tt.Ve), n = this.C.Rt(s.Tt.Xe); i && t ? ((this.mite = i), (this.Wi = t), (this.Gi = e), (this.Hi = n), E.info("Restored real-time messaging configuration from storage")) : (i || t) && (E.warn( "Incomplete real-time messaging configuration in storage, clearing", ), this.Ye()); } } Hr() { this.xe || this.De || ((this.xe = this.Ze("ddr", (i) => { if (!i.body || "number" != typeof i.body._e) return; const t = Math.random() * i.body._e * 0.3, s = Math.round(i.body._e + t), e = i.body.e ? ` (${i.body.e})` : ""; E.info(`Admin requested disconnect${e}, reconnecting in ${s}ms`), this.sn(), setTimeout(() => this.en(), s); })), (this.De = this.Ze("ttl", (i) => { if (!i.body || "number" != typeof i.body.on) return; const t = i.body.on; E.info(`Time to live set to ${t}ms, will reconnect when expired`), "sharedworker" !== this.Fe && ("string" == typeof i.body.hn && (this.Ki = i.body.hn), null !== this.Zi && window.clearTimeout(this.Zi), (this.Zi = window.setTimeout(() => { (this.Zi = null), E.info("Time to live expired, performing gapless reconnection"), this.an(); }, t))); }))); } ln() { this.Se || this.ke || ((this.ye = () => { (this.Re = !0), (this._i = !1); }), window.addEventListener("beforeunload", this.ye), (this.Se = () => { var i; this.Re = !0; (this.Qi || (null === (i = this.We) || void 0 === i ? void 0 : i.cn())) && (E.info("Page unloading, closing real-time connection gracefully"), (this._i = !1), this.sn()); }), window.addEventListener("pagehide", this.Se), (this.ke = (i) => { var t; const s = this.Qi || (null === (t = this.We) || void 0 === t ? void 0 : t.cn()); i.persisted && this.un() && !s && (E.info("Page restored from bfcache, reconnecting"), (this.Re = !1), this.qe(), this.en()); }), window.addEventListener("pageshow", this.ke)); } Lt() { return this.T; } $t(i) { this.T = i; } Ze(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.Kt(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; } Ye() { (this.mite = null), (this.Wi = null), (this.Gi = null), (this.Hi = null), (this.Ki = null), this.C && (this.C.Qt(s.Tt.Ke), this.C.Qt(s.Tt.Qe), this.C.Qt(s.Tt.Ve), this.C.Qt(s.Tt.Xe)); } mn(i, t) { const e = () => { "function" == typeof t && t(); }, n = this.B, r = this.C; if (!n || !r) return ( E.error("NetworkManager or StorageManager not available"), void e() ); if (!this.h || !this.h.fn()) return ( E.info("Real-time messaging is not enabled, skipping refresh"), void e() ); this.un() ? E.info("Refreshing real-time messaging configuration") : E.info("Fetching initial real-time messaging configuration"); const o = n.Z({}, !0), a = n.tt(o, h.it.pn, !1), 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 ( E.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), E.info( "Received real-time messaging configuration from server", ), r.It(s.Tt.Ke, t.mite), r.It(s.Tt.Qe, t.host), t.auth ? r.It(s.Tt.Ve, t.auth) : r.Qt(s.Tt.Ve), t.expiration ? r.It(s.Tt.Xe, t.expiration) : r.Qt(s.Tt.Xe), this.en(), "function" == typeof i && i()) : (E.info( "Real-time messaging configuration not available - this SDK version may not be supported", ), this.Ye(), e()); }, error: (i) => { n.dt(i, "retrieving DUST config"), e(); }, }); } en() { if (!this.h || !this.h.fn()) return; if (!this.un()) return void E.error( "Cannot start real-time subscription without configuration", ); if (this.gn()) return ( E.info( "Real-time messaging auth token has expired, refreshing configuration", ), void this.mn( () => { this.en(); }, () => { E.error( "Failed to refresh expired real-time messaging configuration", ); }, ) ); const i = this.mite, t = this.Oi || this.Wi; if (i && t) switch (this.Fe) { case "sharedworker": this.vn(i, t); break; case "direct": this.Qi && (E.info( "Real-time connection already exists, closing before starting new subscription", ), this.sn()), this.wn(); } } bn() { const i = this.mite, t = this.Oi || this.Wi; return i && t ? ti(t, i, this.Vi, this.Gi || void 0, this.Ki || void 0) : null; } vn(i, t) { var s, e, n; if ((this.We || this.Ae(), !this.We)) return ( E.info( "SharedWorker initialization failed, falling back to direct EventSource", ), (this.Fe = "direct"), void this.Sn() ); this.Oi && E.info(`Using custom real-time messaging host: ${this.Oi}`), E.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.We.connect({ mite: i, Wi: t, auth: this.Gi || void 0, kn: { yn: r, $n: o, Rn: h }, }); } Sn() { this.Qi && (E.info( "Real-time connection already exists, closing before starting new subscription", ), this.Cn()), this.wn(); } wn(i) { const t = this.bn(); if (t) { this.Oi && E.info(`Using custom real-time messaging host: ${this.Oi}`); try { const s = new EventSource(t); (s.onopen = () => { i ? (E.info( "Gapless reconnection: new connection established, closing old connection", ), i.close()) : E.info("Real-time messaging connection established"), (this.Qi = s), (this.Vi = 0), (this.we = null), (this._i = !0), (this.Ne = !0); }), s.addEventListener("msg", (i) => { this.Mn(i.data); }), (s.onerror = () => { const t = s.readyState; return ( this.Re || (0 === t ? E.info("Real-time messaging failed to connect") : E.info("Real-time messaging connection lost")), i && this.Qi !== s ? (E.info( "Gapless reconnection failed, keeping old connection", ), s.close(), void (this._i && this.Vi < this.Xi && this.xn())) : (this.Cn(), this.Ne && (this.Te++, this.Te > 1) ? (E.info( "Real-time messaging connection lost twice after successful connect (likely multi-tab conflict), yielding to other tab", ), void (this._i = !1)) : void (this._i && this.Vi < this.Xi ? this.xn() : (this.Vi >= this.Xi && E.error( `Max retry attempts (${this.Xi}) reached for real-time messaging, giving up for current session`, ), (this._i = !1)))) ); }); } catch (i) { E.error( `Failed to create real-time messaging connection: ${ i instanceof Error ? i.message : String(i) }`, ); } } } xn() { var i, t, s; this.Vi++; 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.we; (null == o || o < e) && (o = e); const h = Math.min(r, a(e, o * n)); (this.we = h), E.info( `Retrying real-time messaging connection in ${h}ms (attempt ${this.Vi}/${this.Xi})`, ), (this.Yi = window.setTimeout(() => { (this.Yi = null), this.en(); }, h)); } an() { if (this.un()) { if (this.gn()) return ( E.info( "Auth token expired during gapless reconnection, falling back to regular reconnection", ), this.sn(), void this.en() ); null !== this.Zi && (window.clearTimeout(this.Zi), (this.Zi = null)), this.wn(this.Qi || void 0); } else E.error("Cannot perform gapless reconnection without configuration"); } sn() { var i; switch (this.Fe) { case "sharedworker": null === (i = this.We) || void 0 === i || i.disconnect(); break; case "direct": this.Cn(); } } Cn() { null !== this.Yi && (window.clearTimeout(this.Yi), (this.Yi = null)), null !== this.Zi && (window.clearTimeout(this.Zi), (this.Zi = null)), this.Qi && (this.Qi.close(), (this.Qi = null), E.info("Real-time messaging connection closed")); } Mn(i) { try { const t = JSON.parse(i); this.Ge(t); } catch (i) { E.warn( `Failed to parse real-time message: ${ i instanceof Error ? i.message : String(i) }`, ); } } changeUser(i = !1) { this.sn(), i || (this.un() && E.info( "Clearing cached real-time messaging configuration for user change", ), this.Ye()), this.qe(); } clearData(i = !1) { (this._i = !1), this.sn(), i && (this.un() && E.info( "Clearing cached real-time messaging configuration (wipeData)", ), this.Ye()), this.qe(); } destroy() { (this._i = !1), this.sn(), this.Ye(), this.We && (this.We.destroy(), (this.We = null)), this.xe && (this.dn("ddr", this.xe), (this.xe = null)), this.De && (this.dn("ttl", this.De), (this.De = null)), this.ye && (window.removeEventListener("beforeunload", this.ye), (this.ye = null)), this.Se && (window.removeEventListener("pagehide", this.Se), (this.Se = null)), this.ke && (window.removeEventListener("pageshow", this.ke), (this.ke = null)), this.T && (r.removeSubscription(this.T), (this.T = null)); } }