@braze/web-sdk
Version:
Braze SDK for web sites and other JS platforms.
521 lines (520 loc) • 15.3 kB
JavaScript
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));
}
}