laravel-echo
Version:
Laravel Echo library for beautiful Pusher, Socket.IO, and Mercure integration
1,030 lines (1,029 loc) • 37.5 kB
JavaScript
// src/channel/channel.ts
var e = class {
constructor() {
this.notificationCreatedEvent = ".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated";
}
listenForWhisper(e, t) {
return this.listen(".client-" + e, t);
}
notification(e) {
return this.listen(this.notificationCreatedEvent, e);
}
stopListeningForNotification(e) {
return this.stopListening(this.notificationCreatedEvent, e);
}
stopListeningForWhisper(e, t) {
return this.stopListening(".client-" + e, t);
}
}, t = class {
constructor(e) {
this.namespace = e;
}
format(e) {
return [".", "\\"].includes(e.charAt(0)) ? e.substring(1) : (this.namespace && (e = this.namespace + "." + e), e.replace(/\./g, "\\"));
}
setNamespace(e) {
this.namespace = e;
}
};
// src/util/index.ts
function n(e) {
try {
return Reflect.construct(String, [], e), !0;
} catch {
return !1;
}
}
// src/channel/mercure-channel.ts
var r = class extends e {
constructor(e, n, r) {
super(), this.listeners = /* @__PURE__ */ new Map(), this.subscribedCallbacks = [], this.errorCallbacks = [], this.name = e, this.options = n, this.whisperer = r, this.eventFormatter = new t(this.options.namespace);
}
listen(e, t) {
let n = this.eventFormatter.format(e);
return this.listeners.set(n, [...this.listeners.get(n) ?? [], t]), this;
}
stopListening(e, t) {
let n = this.eventFormatter.format(e);
if (!t) this.listeners.delete(n);
else {
let e = (this.listeners.get(n) ?? []).filter((e) => e !== t);
e.length === 0 ? this.listeners.delete(n) : this.listeners.set(n, e);
}
return this;
}
subscribed(e) {
return this.subscribedCallbacks.push(e), this;
}
error(e) {
return this.errorCallbacks.push(e), this;
}
whisper(e, t) {
throw Error("Public Mercure channels do not support whisper(): use a private or presence channel, whose members are granted a whisper topic.");
}
listenForWhisper(e, t) {
return console.warn(`listenForWhisper("${e}"): public Mercure channels cannot receive whispers, so this listener will never fire.`), super.listenForWhisper(e, t);
}
dispatch(e, t) {
(this.listeners.get(e) ?? []).forEach((e) => e(t));
}
notifySubscribed() {
this.subscribedCallbacks.forEach((e) => e());
}
notifyError(e) {
this.errorCallbacks.forEach((t) => t(e));
}
}, i = class extends r {
whisper(e, t) {
if (!this.whisperer) throw Error(`whisper("${e}"): this channel was constructed without a connector to publish through.`);
return this.whisperer.whisper(this.name, e, t), this;
}
listenForWhisper(e, t) {
return this.whisperer?.listenForWhispers(this.name), this.listen(".client-" + e, t);
}
}, a = class extends i {}, o = class extends i {
constructor(...e) {
super(...e), this.hereCallbacks = [], this.joiningCallbacks = [], this.leavingCallbacks = [], this.members = /* @__PURE__ */ new Map(), this.memberCounts = /* @__PURE__ */ new Map(), this.seeded = !1;
}
here(e) {
return this.hereCallbacks.push(e), this.seeded && this.invoke(e, this.uniqueMembers()), this;
}
joining(e) {
return this.joiningCallbacks.push(e), this;
}
leaving(e) {
return this.leavingCallbacks.push(e), this;
}
setInitialMembers(e) {
this.members = new Map(e.map(([e, t]) => [e, s(t)])), this.memberCounts = /* @__PURE__ */ new Map();
for (let e of this.members.values()) this.memberCounts.set(e.key, (this.memberCounts.get(e.key) ?? 0) + 1);
this.seeded = !0, this.hereCallbacks.forEach((e) => this.invoke(e, this.uniqueMembers()));
}
applySubscriptionEvent(e, t, n) {
if (t) {
if (this.members.has(e)) return;
let t = s(n ?? {}), r = (this.memberCounts.get(t.key) ?? 0) + 1;
this.members.set(e, t), this.memberCounts.set(t.key, r), r === 1 && this.joiningCallbacks.forEach((e) => this.invoke(e, t.info));
} else {
let t = this.members.get(e);
if (!t) return;
let n = (this.memberCounts.get(t.key) ?? 1) - 1;
this.members.delete(e), n <= 0 ? (this.memberCounts.delete(t.key), this.leavingCallbacks.forEach((e) => this.invoke(e, t.info))) : this.memberCounts.set(t.key, n);
}
}
uniqueMembers() {
let e = /* @__PURE__ */ new Set(), t = [];
for (let n of this.members.values()) e.has(n.key) || (e.add(n.key), t.push(n.info));
return t;
}
invoke(e, t) {
try {
e(t);
} catch (e) {
console.error(`A "${this.name}" presence callback threw:`, e);
}
}
};
function s(e) {
if (typeof e == "object" && e && "user_id" in e && typeof e.user_id == "string") {
let t = e;
return {
key: "user:" + t.user_id,
info: t.user_info
};
}
return {
key: "payload:" + (JSON.stringify(e) ?? "undefined"),
info: e
};
}
// src/channel/pusher-channel.ts
var c = class extends e {
constructor(e, n, r) {
super(), this.name = n, this.pusher = e, this.options = r, this.eventFormatter = new t(this.options.namespace), this.subscribe();
}
subscribe() {
this.subscription = this.pusher.subscribe(this.name);
}
unsubscribe() {
this.pusher.unsubscribe(this.name);
}
listen(e, t) {
return this.on(this.eventFormatter.format(e), t), this;
}
listenToAll(e) {
return this.subscription.bind_global((t, n) => {
if (t.startsWith("pusher:")) return;
let r = String(this.options.namespace ?? "").replace(/\./g, "\\");
e(t.startsWith(r) ? t.substring(r.length + 1) : "." + t, n);
}), this;
}
stopListening(e, t) {
return t ? this.subscription.unbind(this.eventFormatter.format(e), t) : this.subscription.unbind(this.eventFormatter.format(e)), this;
}
stopListeningToAll(e) {
return e ? this.subscription.unbind_global(e) : this.subscription.unbind_global(), this;
}
subscribed(e) {
return this.on("pusher:subscription_succeeded", () => {
e();
}), this;
}
error(e) {
return this.on("pusher:subscription_error", (t) => {
e(t);
}), this;
}
on(e, t) {
return this.subscription.bind(e, t), this;
}
}, l = class extends c {
whisper(e, t) {
return this.pusher.channels.channels[this.name].trigger(`client-${e}`, t), this;
}
}, u = class extends c {
whisper(e, t) {
return this.pusher.channels.channels[this.name].trigger(`client-${e}`, t), this;
}
}, d = class extends l {
here(e) {
return this.on("pusher:subscription_succeeded", (t) => {
e(Object.keys(t.members).map((e) => t.members[e]));
}), this;
}
joining(e) {
return this.on("pusher:member_added", (t) => {
e(t.info);
}), this;
}
whisper(e, t) {
return this.pusher.channels.channels[this.name].trigger(`client-${e}`, t), this;
}
leaving(e) {
return this.on("pusher:member_removed", (t) => {
e(t.info);
}), this;
}
}, f = class extends e {
constructor(e, n, r) {
super(), this.events = {}, this.listeners = {}, this.name = n, this.socket = e, this.options = r, this.eventFormatter = new t(this.options.namespace), this.subscribe();
}
subscribe() {
this.socket.emit("subscribe", {
channel: this.name,
auth: this.options.auth || {}
});
}
unsubscribe() {
this.unbind(), this.socket.emit("unsubscribe", {
channel: this.name,
auth: this.options.auth || {}
});
}
listen(e, t) {
return this.on(this.eventFormatter.format(e), t), this;
}
stopListening(e, t) {
return this.unbindEvent(this.eventFormatter.format(e), t), this;
}
subscribed(e) {
return this.on("connect", (t) => {
e(t);
}), this;
}
error(e) {
return this;
}
on(e, t) {
return this.listeners[e] = this.listeners[e] || [], this.events[e] || (this.events[e] = (t, n) => {
this.name === t && this.listeners[e] && this.listeners[e].forEach((e) => e(n));
}, this.socket.on(e, this.events[e])), this.listeners[e].push(t), this;
}
unbind() {
Object.keys(this.events).forEach((e) => {
this.unbindEvent(e);
});
}
unbindEvent(e, t) {
this.listeners[e] = this.listeners[e] || [], t && (this.listeners[e] = this.listeners[e].filter((e) => e !== t)), t && this.listeners[e].length !== 0 || (this.events[e] && (this.socket.removeListener(e, this.events[e]), delete this.events[e]), delete this.listeners[e]);
}
}, p = class extends f {
whisper(e, t) {
return this.socket.emit("client event", {
channel: this.name,
event: `client-${e}`,
data: t
}), this;
}
}, m = class extends p {
here(e) {
return this.on("presence:subscribed", (t) => {
e(t.map((e) => e.user_info));
}), this;
}
joining(e) {
return this.on("presence:joining", (t) => e(t.user_info)), this;
}
whisper(e, t) {
return this.socket.emit("client event", {
channel: this.name,
event: `client-${e}`,
data: t
}), this;
}
leaving(e) {
return this.on("presence:leaving", (t) => e(t.user_info)), this;
}
}, h = class extends e {
subscribe() {}
unsubscribe() {}
listen(e, t) {
return this;
}
listenToAll(e) {
return this;
}
stopListening(e, t) {
return this;
}
subscribed(e) {
return this;
}
error(e) {
return this;
}
on(e, t) {
return this;
}
}, g = class extends h {
whisper(e, t) {
return this;
}
}, _ = class extends h {
whisper(e, t) {
return this;
}
}, v = class extends g {
here(e) {
return this;
}
joining(e) {
return this;
}
whisper(e, t) {
return this;
}
leaving(e) {
return this;
}
}, y = class e {
static {
this._defaultOptions = {
auth: { headers: {} },
authEndpoint: "/broadcasting/auth",
userAuthentication: {
endpoint: "/broadcasting/user-auth",
headers: {}
},
csrfToken: null,
bearerToken: null,
host: null,
key: null,
namespace: "App.Events"
};
}
constructor(e) {
this.setOptions(e), this.connect();
}
setOptions(t) {
this.options = {
...e._defaultOptions,
...t,
broadcaster: t.broadcaster
};
let n = this.csrfToken();
n && (this.options.auth.headers["X-CSRF-TOKEN"] = n, this.options.userAuthentication.headers["X-CSRF-TOKEN"] = n), n = this.options.bearerToken, n && (this.options.auth.headers.Authorization = "Bearer " + n, this.options.userAuthentication.headers.Authorization = "Bearer " + n);
}
csrfToken() {
return typeof window < "u" && window.Laravel?.csrfToken ? window.Laravel.csrfToken : this.options.csrfToken ? this.options.csrfToken : typeof document < "u" && typeof document.querySelector == "function" ? document.querySelector("meta[name=\"csrf-token\"]")?.getAttribute("content") ?? null : null;
}
}, b = "/.well-known/mercure", x = 3e4, S = 100, C = "mercure", w = "private-encrypted-", T = "client-", E = class extends y {
constructor(...e) {
super(...e), this.channels = Object.create(null), this.eventSource = null, this.lastEventId = "", this.whisperEventSources = /* @__PURE__ */ new Map(), this.whisperLastEventIds = /* @__PURE__ */ new Map(), this.whisperListening = /* @__PURE__ */ new Set(), this.clientEventsEnabled = !1, this.topicPrefix = "", this.whisperQueue = Promise.resolve(), this.id = I(), this.status = "disconnected", this.statusListeners = [], this.refreshing = !1, this.refreshPending = !1, this.authorizedChannels = /* @__PURE__ */ new Set(), this.subscribedNotified = /* @__PURE__ */ new Set(), this.seededPresenceChannels = /* @__PURE__ */ new Set(), this.pendingSubscriptionEvents = /* @__PURE__ */ new Map(), this.channelJwks = /* @__PURE__ */ new Map(), this.importedKeys = /* @__PURE__ */ new Map(), this.dispatchQueue = Promise.resolve(), this.epoch = 0, this.reconnectDelay = 1e3, this.reconnectTimer = null, this.tokenTtl = null, this.lastAuthAt = 0, this.tokenRefreshTimer = null;
}
connect() {
let e = typeof window < "u";
if (this.options.host == null && !e) throw Error("Mercure connector: the \"host\" option (the hub URL) is required outside a browser.");
try {
this.url = new URL(this.options.host ?? b, e ? window.location.origin : void 0);
} catch {
throw Error(`Mercure connector: the "host" option ("${String(this.options.host)}") is not a valid hub URL.`);
}
}
eventSourceClass() {
let e = this.options.eventSource ?? globalThis.EventSource;
if (e === void 0) throw Error("Mercure connector: EventSource is not available globally; pass an implementation via the \"eventSource\" option.");
return e;
}
subscriptionsPath() {
return `${this.url.pathname.replace(/\/$/, "")}/subscriptions`;
}
channelTopic(e) {
return this.topicPrefix === "" ? e : `${this.topicPrefix}channel/${F(e)}`;
}
whisperTopic(e) {
return `${this.topicPrefix}whisper/${F(e)}`;
}
channelNameFromTopic(e) {
if (this.topicPrefix === "") return e;
let t = `${this.topicPrefix}channel/`;
if (!e.startsWith(t)) return null;
try {
return decodeURIComponent(e.slice(t.length));
} catch {
return null;
}
}
listen(e, t, n) {
return this.channel(e).listen(t, n);
}
channel(e) {
return this.channels[e] || (this.channels[e] = new r(e, this.options, this), this.refresh()), this.channels[e];
}
privateChannel(e) {
let t = "private-" + e;
return this.channels[t] || (this.channels[t] = new i(t, this.options, this), this.refresh()), this.channels[t];
}
encryptedPrivateChannel(e) {
let t = w + e;
return this.channels[t] || (this.channels[t] = new a(t, this.options, this), this.refresh()), this.channels[t];
}
presenceChannel(e) {
let t = "presence-" + e;
return this.channels[t] || (this.channels[t] = new o(t, this.options, this), this.refresh()), this.channels[t];
}
leave(e) {
[
"",
"private-",
w,
"presence-"
].forEach((t) => this.leaveChannel(t + e));
}
leaveChannel(e) {
this.channels[e] && (delete this.channels[e], this.forgetChannelState(e), this.refresh());
}
socketId() {
return this.id;
}
whisper(e, t, n) {
let r = this.channels[e];
r && (this.clientEventsEnabled ? this.whisperQueue = this.whisperQueue.then(() => this.publishWhisper(e, T + t, n).catch((t) => {
this.channels[e]?.notifyError(t);
})) : r.notifyError(/* @__PURE__ */ Error(`whisper("${t}"): the server did not grant client events; enable the "client_events" option of the Mercure broadcasting connection.`)));
}
listenForWhispers(e) {
!this.whisperListening.has(e) && this.channels[e] && (this.whisperListening.add(e), this.syncWhisperEventSources());
}
async publishWhisper(e, t, n) {
let r;
r = e.startsWith(w) ? JSON.stringify({
channels: [e],
data: await this.encrypt(e, JSON.stringify({
event: t,
payload: n,
socket: this.id
}))
}) : JSON.stringify({
channels: [e],
event: t,
payload: n,
socket: this.id
});
let i = () => fetch(`${this.url.origin}${this.url.pathname}`, {
method: "POST",
credentials: "include",
body: new URLSearchParams({
topic: this.whisperTopic(e),
data: r,
private: "on"
})
}), a = await i();
if (a.status === 401 && this.channels[e]) {
let t = await this.authenticate(Object.keys(this.channels));
t.ok && (t.denied.length > 0 && (this.evict(t.denied, /* @__PURE__ */ Error(`The broadcasting auth request denied access to [${t.denied.join(", ")}].`)), this.refresh()), this.channels[e] && (a = await i()));
}
if (!a.ok) throw Error(`The whisper publish on "${e}" failed with HTTP ${a.status}.`);
}
connectionStatus() {
return this.status;
}
onConnectionChange(e) {
return this.statusListeners.push(e), () => {
this.statusListeners = this.statusListeners.filter((t) => t !== e);
};
}
disconnect() {
this.epoch++, this.channels = Object.create(null), this.authorizedChannels.clear(), this.subscribedNotified.clear(), this.seededPresenceChannels.clear(), this.pendingSubscriptionEvents.clear(), this.channelJwks.clear(), this.importedKeys.clear(), this.lastEventId = "", this.whisperLastEventIds.clear(), this.whisperListening.clear(), this.clientEventsEnabled = !1, this.topicPrefix = "", this.reconnectTimer &&= (clearTimeout(this.reconnectTimer), null), this.reconnectDelay = 1e3, this.tokenRefreshTimer &&= (clearTimeout(this.tokenRefreshTimer), null), this.eventSource?.close(), this.eventSource = null, this.closeWhisperEventSources(), this.setStatus("disconnected");
}
setStatus(e) {
this.status = e, this.statusListeners.forEach((t) => t(e));
}
forgetChannelState(e) {
this.authorizedChannels.delete(e), this.subscribedNotified.delete(e), this.seededPresenceChannels.delete(e), this.pendingSubscriptionEvents.delete(e), this.channelJwks.delete(e), this.importedKeys.delete(e), this.whisperEventSources.get(e)?.close(), this.whisperEventSources.delete(e), this.whisperLastEventIds.delete(e), this.whisperListening.delete(e);
}
evict(e, t) {
e.forEach((e) => {
let n = this.channels[e];
n && (delete this.channels[e], this.forgetChannelState(e), n.notifyError(t));
});
}
notifyAllError(e) {
Object.values(this.channels).forEach((t) => t.notifyError(e));
}
guardedChannelNames() {
return Object.keys(this.channels).filter((e) => e.startsWith("private-") || e.startsWith("presence-"));
}
refresh() {
this.runExclusive(() => this.doRefresh());
}
runExclusive(e) {
this.refreshing ? this.refreshPending = !0 : (this.refreshing = !0, e().catch((e) => {
this.notifyAllError(e), Object.keys(this.channels).length > 0 && (this.setStatus("reconnecting"), this.scheduleReconnect());
}).finally(() => {
this.refreshing = !1, this.refreshPending && (this.refreshPending = !1, this.refresh());
}));
}
scheduleTokenRefresh() {
if (this.tokenTtl === null) return;
this.tokenRefreshTimer && clearTimeout(this.tokenRefreshTimer);
let e = this.epoch;
this.tokenRefreshTimer = setTimeout(() => {
this.tokenRefreshTimer = null, e === this.epoch && Object.keys(this.channels).length !== 0 && (this.tokenTtl !== null && Date.now() - this.lastAuthAt >= this.tokenTtl * 1e3 ? this.refresh() : this.runExclusive(async () => {
let t = await this.authenticate(Object.keys(this.channels));
e === this.epoch && (t.ok ? (t.denied.length > 0 && (this.evict(t.denied, /* @__PURE__ */ Error(`The broadcasting auth request denied access to [${t.denied.join(", ")}].`)), this.refresh()), this.scheduleTokenRefresh()) : (this.notifyAllError(t.error), this.scheduleReconnect()));
}));
}, Math.max(this.tokenTtl * .8 * 1e3, 5e3));
}
scheduleReconnect() {
if (this.reconnectTimer) return;
let e = this.epoch;
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null, e === this.epoch && Object.keys(this.channels).length !== 0 && this.refresh();
}, this.reconnectDelay), this.reconnectDelay = Math.min(this.reconnectDelay * 2, x);
}
async doRefresh() {
let e = this.epoch;
if (await Promise.resolve(), e !== this.epoch) return;
this.refreshPending = !1;
let t = Object.keys(this.channels);
if (t.length === 0) {
this.eventSource?.close(), this.eventSource = null, this.closeWhisperEventSources(), this.authorizedChannels.clear(), this.setStatus("disconnected");
return;
}
t.length > S && (this.evict(t.slice(S), /* @__PURE__ */ Error(`Mercure broadcasting authorizes at most ${S} channels per connection.`)), t = t.slice(0, S)), this.eventSource || this.setStatus("connecting");
let n = await this.authenticate(t);
if (e !== this.epoch) return;
if (!n.ok) {
let e = t.filter((e) => !this.authorizedChannels.has(e) && this.channels[e]);
if (n.rejected && e.length > 0) {
this.evict(e, /* @__PURE__ */ Error(`The broadcasting auth request was denied (HTTP ${String(n.status)}) after joining [${e.join(", ")}].`)), this.refreshPending = !0;
return;
}
this.notifyAllError(n.error), this.setStatus("reconnecting"), this.scheduleReconnect();
return;
}
if (this.reconnectTimer &&= (clearTimeout(this.reconnectTimer), null), n.denied.length > 0 && (this.evict(n.denied, /* @__PURE__ */ Error(`The broadcasting auth request denied access to [${n.denied.join(", ")}].`)), t = t.filter((e) => this.channels[e]), t.length === 0)) {
this.eventSource?.close(), this.eventSource = null, this.closeWhisperEventSources(), this.authorizedChannels.clear(), this.setStatus("disconnected");
return;
}
this.authorizedChannels = new Set(t), this.scheduleTokenRefresh(), t.filter((e) => this.channels[e] instanceof o && !this.seededPresenceChannels.has(e) && !this.pendingSubscriptionEvents.has(e)).forEach((e) => this.pendingSubscriptionEvents.set(e, []));
let r = this.buildSubscribeUrl(t.map((e) => this.channelTopic(e)), t.filter((e) => this.channels[e] instanceof o).map((e) => this.subscriptionEventsPattern(e)), this.lastEventId), i = this.eventSourceClass();
this.eventSource?.close();
let a = new i(r.toString(), { withCredentials: !0 });
this.eventSource = a, a.onopen = () => {
this.eventSource === a && (this.reconnectDelay = 1e3, this.setStatus("connected"), t.forEach((e) => {
let t = this.channels[e];
t && !this.subscribedNotified.has(e) && (this.subscribedNotified.add(e), t.notifySubscribed());
}), this.seedPresenceChannels(e));
}, a.onerror = (e) => {
this.eventSource === a && (this.notifyAllError(e), this.setStatus("reconnecting"), a.readyState === i.CLOSED && (this.subscribedNotified.clear(), this.seededPresenceChannels.clear(), this.pendingSubscriptionEvents.clear(), this.scheduleReconnect()));
}, a.onmessage = (e) => {
this.eventSource === a && this.handleMessage(e);
}, a.addEventListener(C, (e) => {
this.eventSource === a && this.handleSubscriptionEvent(e);
}), this.syncWhisperEventSources();
}
buildSubscribeUrl(e, t, n) {
let r = new URL(this.url);
return r.search = "", e.forEach((e) => r.searchParams.append("match", e)), t.forEach((e) => r.searchParams.append("match_urlpattern", e)), n && r.searchParams.set("last_event_id", n), r;
}
closeWhisperEventSources() {
this.whisperEventSources.forEach((e) => e.close()), this.whisperEventSources.clear();
}
syncWhisperEventSources() {
let e = this.eventSourceClass().CLOSED, t = new Set(this.clientEventsEnabled ? this.guardedChannelNames().filter((e) => this.authorizedChannels.has(e) && this.whisperListening.has(e)) : []);
this.whisperEventSources.forEach((n, r) => {
t.has(r) && n.readyState !== e || (n.close(), this.whisperEventSources.delete(r));
}), this.clientEventsEnabled ? t.forEach((e) => {
this.whisperEventSources.has(e) || this.openWhisperEventSource(e);
}) : this.whisperLastEventIds.clear();
}
openWhisperEventSource(e) {
let t = this.eventSourceClass(), n = new t(this.buildSubscribeUrl([this.whisperTopic(e)], [], this.whisperLastEventIds.get(e) ?? "").toString(), { withCredentials: !0 });
this.whisperEventSources.set(e, n), n.onmessage = (t) => {
this.whisperEventSources.get(e) === n && (this.whisperLastEventIds.set(e, t.lastEventId), this.handleWhisperMessage(t, e));
}, n.onerror = () => {
this.whisperEventSources.get(e) === n && n.readyState === t.CLOSED && this.scheduleReconnect();
};
}
async seedPresenceChannels(e) {
await Promise.all([...this.pendingSubscriptionEvents.keys()].map(async (t) => {
let n = this.channels[t];
if (!(n instanceof o)) this.pendingSubscriptionEvents.delete(t);
else try {
let r = await this.fetchPresenceSnapshot(t);
if (e !== this.epoch) return;
this.seededPresenceChannels.add(t), n.setInitialMembers(r), (this.pendingSubscriptionEvents.get(t) ?? []).forEach(([e, t, r]) => n.applySubscriptionEvent(e, t, r));
} catch (e) {
n.notifyError(e);
} finally {
this.pendingSubscriptionEvents.delete(t);
}
}));
}
handleMessage(e) {
this.lastEventId = e.lastEventId;
let t = O(e.data);
if (!t || !Array.isArray(t.channels)) return;
let n = t.channels.filter((e) => typeof e == "string");
if (typeof t.data == "string") {
let e = t.data;
n.filter((e) => e.startsWith(w) && D(this.channels, e)).forEach((t) => {
this.dispatchQueue = this.dispatchQueue.then(() => this.dispatchEncrypted(t, e));
});
} else t.socket && t.socket === this.id || typeof t.event == "string" && n.forEach((e) => {
!e.startsWith(w) && D(this.channels, e) && this.channels[e].dispatch(t.event, t.payload);
});
}
handleWhisperMessage(e, t) {
let n = O(e.data);
if (n && Array.isArray(n.channels) && n.channels.length === 1 && n.channels[0] === t && D(this.channels, t)) {
if (typeof n.data == "string") {
let e = n.data;
t.startsWith(w) && (this.dispatchQueue = this.dispatchQueue.then(() => this.dispatchEncrypted(t, e, !0)));
} else n.socket && n.socket === this.id || typeof n.event == "string" && n.event.startsWith(T) && !t.startsWith(w) && this.channels[t].dispatch(n.event, n.payload);
}
}
async dispatchEncrypted(e, t, n = !1) {
let r = this.channels[e];
if (r) try {
let i = O(await this.decrypt(e, t));
if (!i || typeof i.event != "string") throw Error(`The decrypted update on "${e}" carries no broadcast envelope.`);
if (n && !i.event.startsWith(T) || i.socket && i.socket === this.id) return;
r.dispatch(i.event, i.payload);
} catch (e) {
r.notifyError(e);
}
}
async decrypt(e, t) {
let n = t.split(".");
if (n.length !== 5 || n[1] !== "") throw Error(`The update on "${e}" is not a direct-encryption compact JWE.`);
let [r, , i, a, o] = n, s = O(j.decode(N(r)));
if (s?.alg !== "dir" || s?.enc !== "A256GCM") throw Error(`The update on "${e}" uses an unsupported JWE algorithm.`);
let c = N(a), l = N(o), u = new Uint8Array(c.length + l.length);
u.set(c), u.set(l, c.length);
let d = await crypto.subtle.decrypt({
name: "AES-GCM",
iv: N(i),
additionalData: A.encode(r),
tagLength: 128
}, await this.channelKey(e), u);
return j.decode(d);
}
async encrypt(e, t) {
let n = M, r = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(12)), i = new Uint8Array(await crypto.subtle.encrypt({
name: "AES-GCM",
iv: r,
additionalData: A.encode(n),
tagLength: 128
}, await this.channelKey(e), A.encode(t))), a = i.slice(0, -16), o = i.slice(-16);
return [
n,
"",
P(r),
P(a),
P(o)
].join(".");
}
channelKey(e) {
let t = this.channelJwks.get(e);
if (!t || typeof t.k != "string") return Promise.reject(/* @__PURE__ */ Error(`No decryption key is known for "${e}": the auth endpoint returned none. Is "encryption_key" configured server-side?`));
let n = this.importedKeys.get(e);
if (n && n.k === t.k) return n.key;
let r = crypto.subtle.importKey("jwk", t, { name: "AES-GCM" }, !1, ["encrypt", "decrypt"]);
return this.importedKeys.set(e, {
k: t.k,
key: r
}), r;
}
handleSubscriptionEvent(e) {
this.lastEventId = e.lastEventId;
let t = O(e.data);
if (!t || typeof t.match != "string" || typeof t.subscriber != "string" || typeof t.active != "boolean" || t.match_type !== "exact" || t.active && t.payload == null) return;
let n = this.channelNameFromTopic(t.match);
if (n === null) return;
let r = this.pendingSubscriptionEvents.get(n);
if (r) {
r.push([
t.subscriber,
t.active,
t.payload
]);
return;
}
if (!D(this.channels, n)) return;
let i = this.channels[n];
i instanceof o && i.applySubscriptionEvent(t.subscriber, t.active, t.payload);
}
async fetchPresenceSnapshot(e) {
let t = await fetch(`${this.url.origin}${this.subscriptionsPath()}/exact/${F(this.channelTopic(e))}`, { credentials: "include" });
if (!t.ok) throw Error(`The subscription snapshot request failed with HTTP ${t.status}.`);
return ((await t.json()).subscriptions ?? []).filter((e) => e.active && e.payload != null).map((e) => [e.subscriber, e.payload]);
}
async authenticate(e) {
let t, n = null;
try {
let r = await fetch(this.options.authEndpoint, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
...this.options.auth.headers
},
body: JSON.stringify({ channel_names: e })
});
if (r.ok) return this.lastAuthAt = Date.now(), {
ok: !0,
rejected: !1,
status: r.status,
denied: this.readAuthResponse(await k(r))
};
n = r.status, t = /* @__PURE__ */ Error(`The broadcasting auth request to "${this.options.authEndpoint}" failed with HTTP ${r.status}.`);
} catch (e) {
t = /* @__PURE__ */ Error(`The broadcasting auth request to "${this.options.authEndpoint}" failed: ${e instanceof Error ? e.message : String(e)}.`);
}
return {
ok: !1,
rejected: n !== null && n < 500,
status: n,
denied: [],
error: t
};
}
readAuthResponse(e) {
let t = e ?? {}, n = Number(t.expires_in);
this.tokenTtl = Number.isFinite(n) && n > 0 ? n : null, this.topicPrefix = typeof t.topic_prefix == "string" ? t.topic_prefix : "", this.clientEventsEnabled = t.client_events === !0, this.channelJwks.clear();
let r = [];
return Array.isArray(t.channel_names) && t.channel_names.forEach((e) => {
let t = e ?? {};
typeof t.name == "string" && (t.denied === !0 ? r.push(t.name) : t.jwk !== null && typeof t.jwk == "object" && this.channelJwks.set(t.name, t.jwk));
}), r;
}
subscriptionEventsPattern(e) {
return `${this.subscriptionsPath()}/:match_type/${F(this.channelTopic(e))}/:subscriber`;
}
};
function D(e, t) {
return Object.prototype.hasOwnProperty.call(e, t);
}
function O(e) {
try {
return JSON.parse(e);
} catch {
return null;
}
}
async function k(e) {
try {
return await e.json();
} catch {
return null;
}
}
var A = new TextEncoder(), j = new TextDecoder(), M = P(A.encode(JSON.stringify({
alg: "dir",
enc: "A256GCM"
})));
function N(e) {
let t = e.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - e.length % 4) % 4);
return Uint8Array.from(atob(t), (e) => e.charCodeAt(0));
}
function P(e) {
return btoa(String.fromCharCode(...e)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function F(e) {
return encodeURIComponent(e).replace(/[!'()*]/g, (e) => "%" + e.charCodeAt(0).toString(16).toUpperCase());
}
function I() {
return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2) + Date.now().toString(36);
}
// src/connector/pusher-connector.ts
var L = class extends y {
constructor(...e) {
super(...e), this.channels = {};
}
connect() {
if (this.options.client !== void 0) this.pusher = this.options.client;
else if (this.options.Pusher) this.pusher = new this.options.Pusher(this.options.key, this.options);
else if (typeof window < "u" && window.Pusher !== void 0) this.pusher = new window.Pusher(this.options.key, this.options);
else throw Error("Pusher client not found. Should be globally available or passed via options.client");
}
signin() {
this.pusher.signin();
}
listen(e, t, n) {
return this.channel(e).listen(t, n);
}
channel(e) {
return this.channels[e] || (this.channels[e] = new c(this.pusher, e, this.options)), this.channels[e];
}
privateChannel(e) {
return this.channels["private-" + e] || (this.channels["private-" + e] = new l(this.pusher, "private-" + e, this.options)), this.channels["private-" + e];
}
encryptedPrivateChannel(e) {
return this.channels["private-encrypted-" + e] || (this.channels["private-encrypted-" + e] = new u(this.pusher, "private-encrypted-" + e, this.options)), this.channels["private-encrypted-" + e];
}
presenceChannel(e) {
return this.channels["presence-" + e] || (this.channels["presence-" + e] = new d(this.pusher, "presence-" + e, this.options)), this.channels["presence-" + e];
}
leave(e) {
[
e,
"private-" + e,
"private-encrypted-" + e,
"presence-" + e
].forEach((e) => {
this.leaveChannel(e);
});
}
leaveChannel(e) {
this.channels[e] && (this.channels[e].unsubscribe(), delete this.channels[e]);
}
socketId() {
return this.pusher.connection.socket_id;
}
connectionStatus() {
let e = this.pusher.connection.state;
switch (e) {
case "connected":
case "connecting": return e;
case "failed":
case "unavailable": return "failed";
default: return "disconnected";
}
}
onConnectionChange(e) {
let t = () => {
e(this.connectionStatus());
}, n = [
"state_change",
"connected",
"disconnected"
];
return n.forEach((e) => {
this.pusher.connection.bind(e, t);
}), () => {
n.forEach((e) => {
this.pusher.connection.unbind(e, t);
});
};
}
disconnect() {
this.pusher.disconnect();
}
}, R = class extends y {
constructor(...e) {
super(...e), this.channels = {};
}
connect() {
let e = this.getSocketIO();
this.socket = e(this.options.host ?? void 0, this.options), this.socket.io.on("reconnect", () => {
Object.values(this.channels).forEach((e) => {
e.subscribe();
});
});
}
getSocketIO() {
if (this.options.client !== void 0) return this.options.client;
if (typeof window < "u" && window.io !== void 0) return window.io;
throw Error("Socket.io client not found. Should be globally available or passed via options.client");
}
listen(e, t, n) {
return this.channel(e).listen(t, n);
}
channel(e) {
return this.channels[e] || (this.channels[e] = new f(this.socket, e, this.options)), this.channels[e];
}
privateChannel(e) {
return this.channels["private-" + e] || (this.channels["private-" + e] = new p(this.socket, "private-" + e, this.options)), this.channels["private-" + e];
}
presenceChannel(e) {
return this.channels["presence-" + e] || (this.channels["presence-" + e] = new m(this.socket, "presence-" + e, this.options)), this.channels["presence-" + e];
}
leave(e) {
[
e,
"private-" + e,
"presence-" + e
].forEach((e) => {
this.leaveChannel(e);
});
}
leaveChannel(e) {
this.channels[e] && (this.channels[e].unsubscribe(), delete this.channels[e]);
}
socketId() {
return this.socket.id;
}
connectionStatus() {
return this.socket.connected ? "connected" : this.socket.io._reconnecting ? "reconnecting" : this.socket.id === void 0 ? "connecting" : "disconnected";
}
onConnectionChange(e) {
let t = () => {
e(this.connectionStatus());
}, n = [
"connect",
"disconnect",
"connect_error",
"reconnect_attempt",
"reconnect",
"reconnect_error",
"reconnect_failed"
];
return n.forEach((e) => {
this.socket.on(e, t);
}), () => {
n.forEach((e) => {
this.socket.off(e, t);
});
};
}
disconnect() {
this.socket.disconnect();
}
}, z = class extends y {
constructor(...e) {
super(...e), this.channels = {};
}
connect() {}
listen(e, t, n) {
return new h();
}
channel(e) {
return new h();
}
privateChannel(e) {
return new g();
}
encryptedPrivateChannel(e) {
return new _();
}
presenceChannel(e) {
return new v();
}
leave(e) {}
leaveChannel(e) {}
socketId() {
return "fake-socket-id";
}
connectionStatus() {
return "connected";
}
onConnectionChange(e) {
return () => {};
}
disconnect() {}
}, B = class {
constructor(e) {
this.options = e, this.connect(), this.options.withoutInterceptors || this.registerInterceptors();
}
channel(e) {
return this.connector.channel(e);
}
connect() {
if (this.options.broadcaster === "reverb") this.connector = new L({
...this.options,
cluster: ""
});
else if (this.options.broadcaster === "pusher") this.connector = new L(this.options);
else if (this.options.broadcaster === "ably") this.connector = new L({
...this.options,
cluster: "",
broadcaster: "pusher"
});
else if (this.options.broadcaster === "socket.io") this.connector = new R(this.options);
else if (this.options.broadcaster === "mercure") this.connector = new E(this.options);
else if (this.options.broadcaster === "null") this.connector = new z(this.options);
else if (typeof this.options.broadcaster == "function" && n(this.options.broadcaster)) this.connector = new this.options.broadcaster(this.options);
else throw Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`);
}
disconnect() {
this.connector.disconnect();
}
join(e) {
return this.connector.presenceChannel(e);
}
leave(e) {
this.connector.leave(e);
}
leaveChannel(e) {
this.connector.leaveChannel(e);
}
leaveAllChannels() {
for (let e in this.connector.channels) this.leaveChannel(e);
}
listen(e, t, n) {
return this.connector.listen(e, t, n);
}
private(e) {
return this.connector.privateChannel(e);
}
encryptedPrivate(e) {
if (this.connectorSupportsEncryptedPrivateChannels(this.connector)) return this.connector.encryptedPrivateChannel(e);
throw Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`);
}
connectorSupportsEncryptedPrivateChannels(e) {
return e instanceof L || e instanceof E || e instanceof z;
}
socketId() {
return this.connector.socketId();
}
connectionStatus() {
return this.connector.connectionStatus();
}
registerInterceptors() {
typeof Vue < "u" && Vue?.http && this.registerVueRequestInterceptor(), typeof axios == "function" && this.registerAxiosRequestInterceptor(), typeof jQuery == "function" && this.registerjQueryAjaxSetup(), typeof Turbo == "object" && this.registerTurboRequestInterceptor();
}
registerVueRequestInterceptor() {
Vue.http.interceptors.push((e, t) => {
this.socketId() && e.headers.set("X-Socket-ID", this.socketId()), t();
});
}
registerAxiosRequestInterceptor() {
axios.interceptors.request.use((e) => (this.socketId() && (e.headers["X-Socket-Id"] = this.socketId()), e));
}
registerjQueryAjaxSetup() {
jQuery.ajax !== void 0 && jQuery.ajaxPrefilter((e, t, n) => {
this.socketId() && n.setRequestHeader("X-Socket-Id", this.socketId());
});
}
registerTurboRequestInterceptor() {
document.addEventListener("turbo:before-fetch-request", (e) => {
e.detail.fetchOptions.headers["X-Socket-Id"] = this.socketId();
});
}
};
export { e as Channel, y as Connector, t as EventFormatter, B as default };
//# sourceMappingURL=echo.js.map