UNPKG

laravel-echo

Version:

Laravel Echo library for beautiful Pusher, Socket.IO, and Mercure integration

1,030 lines (1,029 loc) • 37.5 kB
// 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