diff --git a/.gitignore b/.gitignore index b2f6b67f73..3489659fe5 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ phoenix-*.ez .DS_Store /priv/templates/phx.gen.live/core_components.ex +/assets/dist/ diff --git a/assets/.prettierignore b/assets/.prettierignore new file mode 100644 index 0000000000..849ddff3b7 --- /dev/null +++ b/assets/.prettierignore @@ -0,0 +1 @@ +dist/ diff --git a/assets/.prettierrc b/assets/.prettierrc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/assets/js/phoenix/ajax.js b/assets/js/phoenix/ajax.js deleted file mode 100644 index 4393e090c6..0000000000 --- a/assets/js/phoenix/ajax.js +++ /dev/null @@ -1,116 +0,0 @@ -import { - global, - XHR_STATES -} from "./constants" - -export default class Ajax { - - static request(method, endPoint, headers, body, timeout, ontimeout, callback){ - if(global.XDomainRequest){ - let req = new global.XDomainRequest() // IE8, IE9 - return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) - } else if(global.XMLHttpRequest){ - let req = new global.XMLHttpRequest() // IE7+, Firefox, Chrome, Opera, Safari - return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) - } else if(global.fetch && global.AbortController){ - // Fetch with AbortController for modern browsers - return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) - } else { - throw new Error("No suitable XMLHttpRequest implementation found") - } - } - - static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback){ - let options = { - method, - headers, - body, - } - let controller = null - if(timeout){ - controller = new AbortController() - const _timeoutId = setTimeout(() => controller.abort(), timeout) - options.signal = controller.signal - } - global.fetch(endPoint, options) - .then(response => response.text()) - .then(data => this.parseJSON(data)) - .then(data => callback && callback(data)) - .catch(err => { - if(err.name === "AbortError" && ontimeout){ - ontimeout() - } else { - callback && callback(null) - } - }) - return controller - } - - static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback){ - req.timeout = timeout - req.open(method, endPoint) - req.onload = () => { - let response = this.parseJSON(req.responseText) - callback && callback(response) - } - if(ontimeout){ req.ontimeout = ontimeout } - - // Work around bug in IE9 that requires an attached onprogress handler - req.onprogress = () => { } - - req.send(body) - return req - } - - static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback){ - req.open(method, endPoint, true) - req.timeout = timeout - for(let [key, value] of Object.entries(headers)){ - req.setRequestHeader(key, value) - } - req.onerror = () => callback && callback(null) - req.onreadystatechange = () => { - if(req.readyState === XHR_STATES.complete && callback){ - let response = this.parseJSON(req.responseText) - callback(response) - } - } - if(ontimeout){ req.ontimeout = ontimeout } - - req.send(body) - return req - } - - static parseJSON(resp){ - if(!resp || resp === ""){ return null } - - try { - return JSON.parse(resp) - } catch { - console && console.log("failed to parse JSON response", resp) - return null - } - } - - static serialize(obj, parentKey){ - let queryStr = [] - for(var key in obj){ - if(!Object.prototype.hasOwnProperty.call(obj, key)){ continue } - let paramKey = parentKey ? `${parentKey}[${key}]` : key - let paramVal = obj[key] - if(typeof paramVal === "object"){ - queryStr.push(this.serialize(paramVal, paramKey)) - } else { - queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal)) - } - } - return queryStr.join("&") - } - - static appendParams(url, params){ - if(Object.keys(params).length === 0){ return url } - - let prefix = url.match(/\?/) ? "&" : "?" - return `${url}${prefix}${this.serialize(params)}` - } -} diff --git a/assets/js/phoenix/ajax.ts b/assets/js/phoenix/ajax.ts new file mode 100644 index 0000000000..bfb192971b --- /dev/null +++ b/assets/js/phoenix/ajax.ts @@ -0,0 +1,213 @@ +import { global, XHR_STATES } from "./constants"; + +type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; +type Headers = Record; +type RequestBody = string | null; +type AjaxCallback = (response: any) => void; +type TimeoutCallback = () => void; + +interface XDomainRequest { + timeout: number; + open(method: string, url: string): void; + send(body?: string | null): void; + onload: (() => void) | null; + ontimeout: (() => void) | null; + onprogress: (() => void) | null; + responseText: string; +} + +interface XMLHttpRequestLike { + open(method: string, url: string, async?: boolean): void; + send(body?: string | null): void; + setRequestHeader(name: string, value: string): void; + timeout: number; + readyState: number; + responseText: string; + onreadystatechange: ((ev?: any) => any) | null; + onerror: (() => void) | null; + ontimeout: (() => void) | null; +} + +export default class Ajax { + static request( + method: HttpMethod, + endPoint: string, + headers: Headers, + body: RequestBody, + timeout: number, + ontimeout: TimeoutCallback | null, + callback: AjaxCallback | null, + ): XMLHttpRequestLike | XDomainRequest | AbortController { + if ((global as any).XDomainRequest) { + const req = new (global as any).XDomainRequest(); // IE8, IE9 + return this.xdomainRequest( + req, + method, + endPoint, + body, + timeout, + ontimeout, + callback, + ); + } else if ((global as any).XMLHttpRequest) { + const req = new (global as any).XMLHttpRequest(); // IE7+, Firefox, Chrome, Opera, Safari + return this.xhrRequest( + req, + method, + endPoint, + headers, + body, + timeout, + ontimeout, + callback, + ); + } else if ( + typeof global.fetch === "function" && + typeof global.AbortController === "function" + ) { + // Fetch with AbortController for modern browsers + return this.fetchRequest( + method, + endPoint, + headers, + body, + timeout, + ontimeout, + callback, + ); + } else { + throw new Error("No suitable XMLHttpRequest implementation found"); + } + } + + static fetchRequest( + method: HttpMethod, + endPoint: string, + headers: Headers, + body: RequestBody, + timeout: number, + ontimeout: TimeoutCallback | null, + callback: AjaxCallback | null, + ): AbortController { + const options: RequestInit = { + method, + headers, + body, + }; + const controller = new AbortController(); + if (timeout) { + setTimeout(() => controller.abort(), timeout); + options.signal = controller.signal; + } + global + .fetch(endPoint, options) + .then((response) => response.text()) + .then((data) => this.parseJSON(data)) + .then((data) => callback && callback(data)) + .catch((err) => { + if (err.name === "AbortError" && ontimeout) { + ontimeout(); + } else { + callback && callback(null); + } + }); + return controller; + } + + static xdomainRequest( + req: XDomainRequest, + method: HttpMethod, + endPoint: string, + body: RequestBody, + timeout: number, + ontimeout: TimeoutCallback | null, + callback: AjaxCallback | null, + ): XDomainRequest { + req.timeout = timeout; + req.open(method, endPoint); + req.onload = () => { + const response = this.parseJSON(req.responseText); + callback && callback(response); + }; + if (ontimeout) { + req.ontimeout = ontimeout; + } + + // Work around bug in IE9 that requires an attached onprogress handler + req.onprogress = () => {}; + + req.send(body); + return req; + } + + static xhrRequest( + req: XMLHttpRequestLike, + method: HttpMethod, + endPoint: string, + headers: Headers, + body: RequestBody, + timeout: number, + ontimeout: TimeoutCallback | null, + callback: AjaxCallback | null, + ): XMLHttpRequestLike { + req.open(method, endPoint, true); + req.timeout = timeout; + for (const [key, value] of Object.entries(headers)) { + req.setRequestHeader(key, value); + } + req.onerror = () => callback && callback(null); + req.onreadystatechange = () => { + if (req.readyState === XHR_STATES.complete && callback) { + const response = this.parseJSON(req.responseText); + callback(response); + } + }; + if (ontimeout) { + req.ontimeout = ontimeout; + } + + req.send(body); + return req; + } + + static parseJSON(resp: string | null | undefined): any { + if (!resp || resp === "") { + return null; + } + + try { + return JSON.parse(resp); + } catch { + console && console.log("failed to parse JSON response", resp); + return null; + } + } + + static serialize(obj: Record, parentKey?: string): string { + const queryStr: string[] = []; + for (const key in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, key)) { + continue; + } + const paramKey = parentKey ? `${parentKey}[${key}]` : key; + const paramVal = obj[key]; + if (typeof paramVal === "object") { + queryStr.push(this.serialize(paramVal, paramKey)); + } else { + queryStr.push( + encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal), + ); + } + } + return queryStr.join("&"); + } + + static appendParams(url: string, params: Record): string { + if (Object.keys(params).length === 0) { + return url; + } + + const prefix = url.match(/\?/) ? "&" : "?"; + return `${url}${prefix}${this.serialize(params)}`; + } +} diff --git a/assets/js/phoenix/channel.js b/assets/js/phoenix/channel.js deleted file mode 100644 index 76bcb33285..0000000000 --- a/assets/js/phoenix/channel.js +++ /dev/null @@ -1,311 +0,0 @@ -import {closure} from "./utils" -import { - CHANNEL_EVENTS, - CHANNEL_STATES, -} from "./constants" - -import Push from "./push" -import Timer from "./timer" - -/** - * - * @param {string} topic - * @param {(Object|function)} params - * @param {Socket} socket - */ -export default class Channel { - constructor(topic, params, socket){ - this.state = CHANNEL_STATES.closed - this.topic = topic - this.params = closure(params || {}) - this.socket = socket - this.bindings = [] - this.bindingRef = 0 - this.timeout = this.socket.timeout - this.joinedOnce = false - this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout) - this.pushBuffer = [] - this.stateChangeRefs = [] - - this.rejoinTimer = new Timer(() => { - if(this.socket.isConnected()){ this.rejoin() } - }, this.socket.rejoinAfterMs) - this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset())) - this.stateChangeRefs.push(this.socket.onOpen(() => { - this.rejoinTimer.reset() - if(this.isErrored()){ this.rejoin() } - }) - ) - this.joinPush.receive("ok", () => { - this.state = CHANNEL_STATES.joined - this.rejoinTimer.reset() - this.pushBuffer.forEach(pushEvent => pushEvent.send()) - this.pushBuffer = [] - }) - this.joinPush.receive("error", () => { - this.state = CHANNEL_STATES.errored - if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() } - }) - this.onClose(() => { - this.rejoinTimer.reset() - if(this.socket.hasLogger()) this.socket.log("channel", `close ${this.topic} ${this.joinRef()}`) - this.state = CHANNEL_STATES.closed - this.socket.remove(this) - }) - this.onError(reason => { - if(this.socket.hasLogger()) this.socket.log("channel", `error ${this.topic}`, reason) - if(this.isJoining()){ this.joinPush.reset() } - this.state = CHANNEL_STATES.errored - if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() } - }) - this.joinPush.receive("timeout", () => { - if(this.socket.hasLogger()) this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout) - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout) - leavePush.send() - this.state = CHANNEL_STATES.errored - this.joinPush.reset() - if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() } - }) - this.on(CHANNEL_EVENTS.reply, (payload, ref) => { - this.trigger(this.replyEventName(ref), payload) - }) - } - - /** - * Join the channel - * @param {integer} timeout - * @returns {Push} - */ - join(timeout = this.timeout){ - if(this.joinedOnce){ - throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance") - } else { - this.timeout = timeout - this.joinedOnce = true - this.rejoin() - return this.joinPush - } - } - - /** - * Hook into channel close - * @param {Function} callback - */ - onClose(callback){ - this.on(CHANNEL_EVENTS.close, callback) - } - - /** - * Hook into channel errors - * @param {Function} callback - */ - onError(callback){ - return this.on(CHANNEL_EVENTS.error, reason => callback(reason)) - } - - /** - * Subscribes on channel events - * - * Subscription returns a ref counter, which can be used later to - * unsubscribe the exact event listener - * - * @example - * const ref1 = channel.on("event", do_stuff) - * const ref2 = channel.on("event", do_other_stuff) - * channel.off("event", ref1) - * // Since unsubscription, do_stuff won't fire, - * // while do_other_stuff will keep firing on the "event" - * - * @param {string} event - * @param {Function} callback - * @returns {integer} ref - */ - on(event, callback){ - let ref = this.bindingRef++ - this.bindings.push({event, ref, callback}) - return ref - } - - /** - * Unsubscribes off of channel events - * - * Use the ref returned from a channel.on() to unsubscribe one - * handler, or pass nothing for the ref to unsubscribe all - * handlers for the given event. - * - * @example - * // Unsubscribe the do_stuff handler - * const ref1 = channel.on("event", do_stuff) - * channel.off("event", ref1) - * - * // Unsubscribe all handlers from event - * channel.off("event") - * - * @param {string} event - * @param {integer} ref - */ - off(event, ref){ - this.bindings = this.bindings.filter((bind) => { - return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref)) - }) - } - - /** - * @private - */ - canPush(){ return this.socket.isConnected() && this.isJoined() } - - /** - * Sends a message `event` to phoenix with the payload `payload`. - * Phoenix receives this in the `handle_in(event, payload, socket)` - * function. if phoenix replies or it times out (default 10000ms), - * then optionally the reply can be received. - * - * @example - * channel.push("event") - * .receive("ok", payload => console.log("phoenix replied:", payload)) - * .receive("error", err => console.log("phoenix errored", err)) - * .receive("timeout", () => console.log("timed out pushing")) - * @param {string} event - * @param {Object} payload - * @param {number} [timeout] - * @returns {Push} - */ - push(event, payload, timeout = this.timeout){ - payload = payload || {} - if(!this.joinedOnce){ - throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`) - } - let pushEvent = new Push(this, event, function (){ return payload }, timeout) - if(this.canPush()){ - pushEvent.send() - } else { - pushEvent.startTimeout() - this.pushBuffer.push(pushEvent) - } - - return pushEvent - } - - /** Leaves the channel - * - * Unsubscribes from server events, and - * instructs channel to terminate on server - * - * Triggers onClose() hooks - * - * To receive leave acknowledgements, use the `receive` - * hook to bind to the server ack, ie: - * - * @example - * channel.leave().receive("ok", () => alert("left!") ) - * - * @param {integer} timeout - * @returns {Push} - */ - leave(timeout = this.timeout){ - this.rejoinTimer.reset() - this.joinPush.cancelTimeout() - - this.state = CHANNEL_STATES.leaving - let onClose = () => { - if(this.socket.hasLogger()) this.socket.log("channel", `leave ${this.topic}`) - this.trigger(CHANNEL_EVENTS.close, "leave") - } - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout) - leavePush.receive("ok", () => onClose()) - .receive("timeout", () => onClose()) - leavePush.send() - if(!this.canPush()){ leavePush.trigger("ok", {}) } - - return leavePush - } - - /** - * Overridable message hook - * - * Receives all events for specialized message handling - * before dispatching to the channel callbacks. - * - * Must return the payload, modified or unmodified - * @param {string} event - * @param {Object} payload - * @param {integer} ref - * @returns {Object} - */ - onMessage(_event, payload, _ref){ return payload } - - /** - * @private - */ - isMember(topic, event, payload, joinRef){ - if(this.topic !== topic){ return false } - - if(joinRef && joinRef !== this.joinRef()){ - if(this.socket.hasLogger()) this.socket.log("channel", "dropping outdated message", {topic, event, payload, joinRef}) - return false - } else { - return true - } - } - - /** - * @private - */ - joinRef(){ return this.joinPush.ref } - - /** - * @private - */ - rejoin(timeout = this.timeout){ - if(this.isLeaving()){ return } - this.socket.leaveOpenTopic(this.topic) - this.state = CHANNEL_STATES.joining - this.joinPush.resend(timeout) - } - - /** - * @private - */ - trigger(event, payload, ref, joinRef){ - let handledPayload = this.onMessage(event, payload, ref, joinRef) - if(payload && !handledPayload){ throw new Error("channel onMessage callbacks must return the payload, modified or unmodified") } - - let eventBindings = this.bindings.filter(bind => bind.event === event) - - for(let i = 0; i < eventBindings.length; i++){ - let bind = eventBindings[i] - bind.callback(handledPayload, ref, joinRef || this.joinRef()) - } - } - - /** - * @private - */ - replyEventName(ref){ return `chan_reply_${ref}` } - - /** - * @private - */ - isClosed(){ return this.state === CHANNEL_STATES.closed } - - /** - * @private - */ - isErrored(){ return this.state === CHANNEL_STATES.errored } - - /** - * @private - */ - isJoined(){ return this.state === CHANNEL_STATES.joined } - - /** - * @private - */ - isJoining(){ return this.state === CHANNEL_STATES.joining } - - /** - * @private - */ - isLeaving(){ return this.state === CHANNEL_STATES.leaving } -} diff --git a/assets/js/phoenix/channel.ts b/assets/js/phoenix/channel.ts new file mode 100644 index 0000000000..5578d82b07 --- /dev/null +++ b/assets/js/phoenix/channel.ts @@ -0,0 +1,438 @@ +import { closure } from "./utils"; +import { CHANNEL_EVENTS, CHANNEL_STATES, type ChannelState } from "./constants"; + +import Push from "./push"; +import Timer from "./timer"; +import type Socket from "./socket"; + +export interface ChannelBinding { + event: string; + ref: number; + callback: (payload: any, ref?: string, joinRef?: string) => void; +} + +/** + * Channel class for Phoenix WebSocket communication + */ +export default class Channel { + private bindings: ChannelBinding[]; + private bindingRef: number; + private joinedOnce: boolean; + private joinPush: Push; + private pushBuffer: Push[]; + private rejoinTimer: Timer; + + /** + * @internal + * @private + */ + state: ChannelState; + + /** + * @internal + * @private + */ + params: () => any; + + /** + * @internal + * @private + */ + timeout: number; + + /** + * @internal + */ + socket: Socket; + + /** + * @internal + */ + stateChangeRefs: string[]; + + /** + * @internal + */ + topic: string; + + constructor(topic: string, params: any | (() => any), socket: Socket) { + this.state = CHANNEL_STATES.closed; + this.topic = topic; + this.params = closure(params || {}); + this.socket = socket; + this.bindings = []; + this.bindingRef = 0; + this.timeout = this.socket.timeout; + this.joinedOnce = false; + this.joinPush = new Push( + this, + CHANNEL_EVENTS.join, + this.params, + this.timeout, + ); + this.pushBuffer = []; + this.stateChangeRefs = []; + + this.rejoinTimer = new Timer(() => { + if (this.socket.isConnected()) { + this.rejoin(); + } + }, this.socket.rejoinAfterMs); + this.stateChangeRefs.push( + this.socket.onError(() => this.rejoinTimer.reset()), + ); + this.stateChangeRefs.push( + this.socket.onOpen(() => { + this.rejoinTimer.reset(); + if (this.isErrored()) { + this.rejoin(); + } + }), + ); + this.joinPush.receive("ok", () => { + this.state = CHANNEL_STATES.joined; + this.rejoinTimer.reset(); + this.pushBuffer.forEach((pushEvent) => pushEvent.send()); + this.pushBuffer = []; + }); + this.joinPush.receive("error", () => { + this.state = CHANNEL_STATES.errored; + if (this.socket.isConnected()) { + this.rejoinTimer.scheduleTimeout(); + } + }); + this.onClose(() => { + this.rejoinTimer.reset(); + if (this.socket.hasLogger()) + this.socket.log("channel", `close ${this.topic} ${this.joinRef()}`); + this.state = CHANNEL_STATES.closed; + this.socket.remove(this); + }); + this.onError((reason: any) => { + if (this.socket.hasLogger()) + this.socket.log("channel", `error ${this.topic}`, reason); + if (this.isJoining()) { + this.joinPush.reset(); + } + this.state = CHANNEL_STATES.errored; + if (this.socket.isConnected()) { + this.rejoinTimer.scheduleTimeout(); + } + }); + this.joinPush.receive("timeout", () => { + if (this.socket.hasLogger()) + this.socket.log( + "channel", + `timeout ${this.topic} (${this.joinRef()})`, + this.joinPush.timeout, + ); + const leavePush = new Push( + this, + CHANNEL_EVENTS.leave, + closure({}), + this.timeout, + ); + leavePush.send(); + this.state = CHANNEL_STATES.errored; + this.joinPush.reset(); + if (this.socket.isConnected()) { + this.rejoinTimer.scheduleTimeout(); + } + }); + this.on(CHANNEL_EVENTS.reply, (payload: any, ref?: string) => { + this.trigger(this.replyEventName(ref!), payload); + }); + } + + /** + * Join the channel + */ + join(timeout: number = this.timeout): Push { + if (this.joinedOnce) { + throw new Error( + "tried to join multiple times. 'join' can only be called a single time per channel instance", + ); + } else { + this.timeout = timeout; + this.joinedOnce = true; + this.rejoin(); + return this.joinPush; + } + } + + /** + * Hook into channel close + */ + onClose( + callback: (payload?: any, ref?: string, joinRef?: string) => void, + ): number { + return this.on(CHANNEL_EVENTS.close, callback); + } + + /** + * Hook into channel errors + */ + onError( + callback: (reason: any, ref?: string, joinRef?: string) => void, + ): number { + return this.on(CHANNEL_EVENTS.error, (reason: any) => callback(reason)); + } + + /** + * Subscribes on channel events + * + * Subscription returns a ref counter, which can be used later to + * unsubscribe the exact event listener + * + * @example + * const ref1 = channel.on("event", do_stuff) + * const ref2 = channel.on("event", do_other_stuff) + * channel.off("event", ref1) + * // Since unsubscription, do_stuff won't fire, + * // while do_other_stuff will keep firing on the "event" + */ + on( + event: string, + callback: (payload: any, ref?: string, joinRef?: string) => void, + ): number { + const ref = this.bindingRef++; + this.bindings.push({ event, ref, callback }); + return ref; + } + + /** + * Unsubscribes off of channel events + * + * Use the ref returned from a channel.on() to unsubscribe one + * handler, or pass nothing for the ref to unsubscribe all + * handlers for the given event. + * + * @example + * // Unsubscribe the do_stuff handler + * const ref1 = channel.on("event", do_stuff) + * channel.off("event", ref1) + * + * // Unsubscribe all handlers from event + * channel.off("event") + */ + off(event: string, ref?: number): void { + this.bindings = this.bindings.filter((bind) => { + return !( + bind.event === event && + (typeof ref === "undefined" || ref === bind.ref) + ); + }); + } + + /** + * @internal + * @private + */ + canPush(): boolean { + return this.socket.isConnected() && this.isJoined(); + } + + /** + * Sends a message `event` to phoenix with the payload `payload`. + * Phoenix receives this in the `handle_in(event, payload, socket)` + * function. if phoenix replies or it times out (default 10000ms), + * then optionally the reply can be received. + * + * @example + * channel.push("event") + * .receive("ok", payload => console.log("phoenix replied:", payload)) + * .receive("error", err => console.log("phoenix errored", err)) + * .receive("timeout", () => console.log("timed out pushing")) + */ + push(event: string, payload: any = {}, timeout: number = this.timeout): Push { + if (!this.joinedOnce) { + throw new Error( + `tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`, + ); + } + const pushEvent = new Push(this, event, () => payload, timeout); + if (this.canPush()) { + pushEvent.send(); + } else { + pushEvent.startTimeout(); + this.pushBuffer.push(pushEvent); + } + + return pushEvent; + } + + /** + * Leaves the channel + * + * Unsubscribes from server events, and + * instructs channel to terminate on server + * + * Triggers onClose() hooks + * + * To receive leave acknowledgements, use the `receive` + * hook to bind to the server ack, ie: + * + * @example + * channel.leave().receive("ok", () => alert("left!") ) + */ + leave(timeout: number = this.timeout): Push { + this.rejoinTimer.reset(); + this.joinPush.cancelTimeout(); + + this.state = CHANNEL_STATES.leaving; + const onClose = () => { + if (this.socket.hasLogger()) + this.socket.log("channel", `leave ${this.topic}`); + this.trigger(CHANNEL_EVENTS.close, "leave"); + }; + const leavePush = new Push( + this, + CHANNEL_EVENTS.leave, + closure({}), + timeout, + ); + leavePush + .receive("ok", () => onClose()) + .receive("timeout", () => onClose()); + leavePush.send(); + if (!this.canPush()) { + leavePush.trigger("ok", {}); + } + + return leavePush; + } + + /** + * Overridable message hook + * + * Receives all events for specialized message handling + * before dispatching to the channel callbacks. + * + * Must return the payload, modified or unmodified + */ + onMessage( + _event: string, + payload: any, + _ref?: string, + _joinRef?: string, + ): any { + return payload; + } + + /** + * @internal + * @private + */ + isMember( + topic: string, + event: string, + payload: any, + joinRef?: string, + ): boolean { + if (this.topic !== topic) { + return false; + } + + if (joinRef && joinRef !== this.joinRef()) { + if (this.socket.hasLogger()) + this.socket.log("channel", "dropping outdated message", { + topic, + event, + payload, + joinRef, + }); + return false; + } else { + return true; + } + } + + /** + * @internal + * @private + */ + joinRef(): string | null { + return this.joinPush.ref; + } + + /** + * @internal + * @private + */ + rejoin(timeout: number = this.timeout): void { + if (this.isLeaving()) { + return; + } + this.socket.leaveOpenTopic(this.topic); + this.state = CHANNEL_STATES.joining; + this.joinPush.resend(timeout); + } + + /** + * @internal + * @private + */ + trigger(event: string, payload: any, ref?: string, joinRef?: string): void { + const handledPayload = this.onMessage(event, payload, ref, joinRef); + if (payload && !handledPayload) { + throw new Error( + "channel onMessage callbacks must return the payload, modified or unmodified", + ); + } + + const eventBindings = this.bindings.filter((bind) => bind.event === event); + + for (let i = 0; i < eventBindings.length; i++) { + const bind = eventBindings[i]!; + bind.callback(handledPayload, ref, joinRef || this.joinRef()); + } + } + + /** + * @internal + * @private + */ + replyEventName(ref: string): string { + return `chan_reply_${ref}`; + } + + /** + * @internal + * @private + */ + isClosed(): boolean { + return this.state === CHANNEL_STATES.closed; + } + + /** + * @internal + * @private + */ + isErrored(): boolean { + return this.state === CHANNEL_STATES.errored; + } + + /** + * @internal + * @private + */ + isJoined(): boolean { + return this.state === CHANNEL_STATES.joined; + } + + /** + * @internal + * @private + */ + isJoining(): boolean { + return this.state === CHANNEL_STATES.joining; + } + + /** + * @internal + * @private + */ + isLeaving(): boolean { + return this.state === CHANNEL_STATES.leaving; + } +} diff --git a/assets/js/phoenix/constants.js b/assets/js/phoenix/constants.js deleted file mode 100644 index a684e22ffd..0000000000 --- a/assets/js/phoenix/constants.js +++ /dev/null @@ -1,30 +0,0 @@ -export const globalSelf = typeof self !== "undefined" ? self : null -export const phxWindow = typeof window !== "undefined" ? window : null -export const global = globalSelf || phxWindow || globalThis -export const DEFAULT_VSN = "2.0.0" -export const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3} -export const DEFAULT_TIMEOUT = 10000 -export const WS_CLOSE_NORMAL = 1000 -export const CHANNEL_STATES = { - closed: "closed", - errored: "errored", - joined: "joined", - joining: "joining", - leaving: "leaving", -} -export const CHANNEL_EVENTS = { - close: "phx_close", - error: "phx_error", - join: "phx_join", - reply: "phx_reply", - leave: "phx_leave" -} - -export const TRANSPORTS = { - longpoll: "longpoll", - websocket: "websocket" -} -export const XHR_STATES = { - complete: 4 -} -export const AUTH_TOKEN_PREFIX = "base64url.bearer.phx." diff --git a/assets/js/phoenix/constants.ts b/assets/js/phoenix/constants.ts new file mode 100644 index 0000000000..4fc87d0f31 --- /dev/null +++ b/assets/js/phoenix/constants.ts @@ -0,0 +1,49 @@ +export const globalSelf = typeof self !== "undefined" ? self : null; +export const phxWindow = typeof window !== "undefined" ? window : null; +export const global = globalSelf || phxWindow || globalThis; +export const DEFAULT_VSN = "2.0.0"; + +export const SOCKET_STATES = { + connecting: 0, + open: 1, + closing: 2, + closed: 3, +} as const; + +export type SocketState = (typeof SOCKET_STATES)[keyof typeof SOCKET_STATES]; + +export const DEFAULT_TIMEOUT = 10000; +export const WS_CLOSE_NORMAL = 1000; + +export const CHANNEL_STATES = { + closed: "closed", + errored: "errored", + joined: "joined", + joining: "joining", + leaving: "leaving", +} as const; + +export type ChannelState = (typeof CHANNEL_STATES)[keyof typeof CHANNEL_STATES]; + +export const CHANNEL_EVENTS = { + close: "phx_close", + error: "phx_error", + join: "phx_join", + reply: "phx_reply", + leave: "phx_leave", +} as const; + +export type ChannelEvent = (typeof CHANNEL_EVENTS)[keyof typeof CHANNEL_EVENTS]; + +export const TRANSPORTS = { + longpoll: "longpoll", + websocket: "websocket", +} as const; + +export type Transport = (typeof TRANSPORTS)[keyof typeof TRANSPORTS]; + +export const XHR_STATES = { + complete: 4, +} as const; + +export const AUTH_TOKEN_PREFIX = "base64url.bearer.phx."; diff --git a/assets/js/phoenix/index.js b/assets/js/phoenix/index.ts similarity index 92% rename from assets/js/phoenix/index.js rename to assets/js/phoenix/index.ts index 1c93968260..b2b5e1fc65 100644 --- a/assets/js/phoenix/index.js +++ b/assets/js/phoenix/index.ts @@ -192,16 +192,39 @@ * @module phoenix */ -import Channel from "./channel" -import LongPoll from "./longpoll" -import Presence from "./presence" -import Serializer from "./serializer" -import Socket from "./socket" +import Channel from "./channel"; +import LongPoll from "./longpoll"; +import { + default as Presence, + PresenceState, + PresenceMeta, + PresenceMap, + PresenceDiff, + PresenceOptions, + PresenceCallback, + PresenceSyncCallback, + PresenceChooser, +} from "./presence"; +import Serializer from "./serializer"; +import { default as Socket, Message } from "./socket"; +import Push from "./push"; +import { SocketOptions } from "./socket"; export { Channel, LongPoll, Presence, Serializer, - Socket -} + Push, + Socket, + SocketOptions, + PresenceState, + PresenceMeta, + PresenceMap, + PresenceDiff, + PresenceOptions, + PresenceCallback, + PresenceSyncCallback, + PresenceChooser, + Message, +}; diff --git a/assets/js/phoenix/longpoll.js b/assets/js/phoenix/longpoll.js deleted file mode 100644 index 2ee739fac3..0000000000 --- a/assets/js/phoenix/longpoll.js +++ /dev/null @@ -1,185 +0,0 @@ -import { - SOCKET_STATES, - TRANSPORTS, - AUTH_TOKEN_PREFIX -} from "./constants" - -import Ajax from "./ajax" - -let arrayBufferToBase64 = (buffer) => { - let binary = "" - let bytes = new Uint8Array(buffer) - let len = bytes.byteLength - for(let i = 0; i < len; i++){ binary += String.fromCharCode(bytes[i]) } - return btoa(binary) -} - -export default class LongPoll { - - constructor(endPoint, protocols){ - // we only support subprotocols for authToken - // ["phoenix", "base64url.bearer.phx.BASE64_ENCODED_TOKEN"] - if(protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)){ - this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length)) - } - this.endPoint = null - this.token = null - this.skipHeartbeat = true - this.reqs = new Set() - this.awaitingBatchAck = false - this.currentBatch = null - this.currentBatchTimer = null - this.batchBuffer = [] - this.onopen = function (){ } // noop - this.onerror = function (){ } // noop - this.onmessage = function (){ } // noop - this.onclose = function (){ } // noop - this.pollEndpoint = this.normalizeEndpoint(endPoint) - this.readyState = SOCKET_STATES.connecting - // we must wait for the caller to finish setting up our callbacks and timeout properties - setTimeout(() => this.poll(), 0) - } - - normalizeEndpoint(endPoint){ - return (endPoint - .replace("ws://", "http://") - .replace("wss://", "https://") - .replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll)) - } - - endpointURL(){ - return Ajax.appendParams(this.pollEndpoint, {token: this.token}) - } - - closeAndRetry(code, reason, wasClean){ - this.close(code, reason, wasClean) - this.readyState = SOCKET_STATES.connecting - } - - ontimeout(){ - this.onerror("timeout") - this.closeAndRetry(1005, "timeout", false) - } - - isActive(){ return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting } - - poll(){ - const headers = {"Accept": "application/json"} - if(this.authToken){ - headers["X-Phoenix-AuthToken"] = this.authToken - } - this.ajax("GET", headers, null, () => this.ontimeout(), resp => { - if(resp){ - var {status, token, messages} = resp - this.token = token - } else { - status = 0 - } - - switch(status){ - case 200: - messages.forEach(msg => { - // Tasks are what things like event handlers, setTimeout callbacks, - // promise resolves and more are run within. - // In modern browsers, there are two different kinds of tasks, - // microtasks and macrotasks. - // Microtasks are mainly used for Promises, while macrotasks are - // used for everything else. - // Microtasks always have priority over macrotasks. If the JS engine - // is looking for a task to run, it will always try to empty the - // microtask queue before attempting to run anything from the - // macrotask queue. - // - // For the WebSocket transport, messages always arrive in their own - // event. This means that if any promises are resolved from within, - // their callbacks will always finish execution by the time the - // next message event handler is run. - // - // In order to emulate this behaviour, we need to make sure each - // onmessage handler is run within its own macrotask. - setTimeout(() => this.onmessage({data: msg}), 0) - }) - this.poll() - break - case 204: - this.poll() - break - case 410: - this.readyState = SOCKET_STATES.open - this.onopen({}) - this.poll() - break - case 403: - this.onerror(403) - this.close(1008, "forbidden", false) - break - case 0: - case 500: - this.onerror(500) - this.closeAndRetry(1011, "internal server error", 500) - break - default: throw new Error(`unhandled poll status ${status}`) - } - }) - } - - // we collect all pushes within the current event loop by - // setTimeout 0, which optimizes back-to-back procedural - // pushes against an empty buffer - - send(body){ - if(typeof(body) !== "string"){ body = arrayBufferToBase64(body) } - if(this.currentBatch){ - this.currentBatch.push(body) - } else if(this.awaitingBatchAck){ - this.batchBuffer.push(body) - } else { - this.currentBatch = [body] - this.currentBatchTimer = setTimeout(() => { - this.batchSend(this.currentBatch) - this.currentBatch = null - }, 0) - } - } - - batchSend(messages){ - this.awaitingBatchAck = true - this.ajax("POST", {"Content-Type": "application/x-ndjson"}, messages.join("\n"), () => this.onerror("timeout"), resp => { - this.awaitingBatchAck = false - if(!resp || resp.status !== 200){ - this.onerror(resp && resp.status) - this.closeAndRetry(1011, "internal server error", false) - } else if(this.batchBuffer.length > 0){ - this.batchSend(this.batchBuffer) - this.batchBuffer = [] - } - }) - } - - close(code, reason, wasClean){ - for(let req of this.reqs){ req.abort() } - this.readyState = SOCKET_STATES.closed - let opts = Object.assign({code: 1000, reason: undefined, wasClean: true}, {code, reason, wasClean}) - this.batchBuffer = [] - clearTimeout(this.currentBatchTimer) - this.currentBatchTimer = null - if(typeof(CloseEvent) !== "undefined"){ - this.onclose(new CloseEvent("close", opts)) - } else { - this.onclose(opts) - } - } - - ajax(method, headers, body, onCallerTimeout, callback){ - let req - let ontimeout = () => { - this.reqs.delete(req) - onCallerTimeout() - } - req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, resp => { - this.reqs.delete(req) - if(this.isActive()){ callback(resp) } - }) - this.reqs.add(req) - } -} diff --git a/assets/js/phoenix/longpoll.ts b/assets/js/phoenix/longpoll.ts new file mode 100644 index 0000000000..762257f837 --- /dev/null +++ b/assets/js/phoenix/longpoll.ts @@ -0,0 +1,298 @@ +import { + SOCKET_STATES, + TRANSPORTS, + AUTH_TOKEN_PREFIX, + type SocketState, +} from "./constants"; + +import Ajax from "./ajax"; + +function arrayBufferToBase64(buffer: ArrayBuffer): string { + let binary = ""; + const bytes = new Uint8Array(buffer); + const len = bytes.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]!); + } + return btoa(binary); +} + +interface LongPollResponse { + status: number; + token?: string; + messages?: string[]; +} + +interface MessageEvent { + data: string; +} + +interface CloseEventInit { + code?: number; + reason?: string; + wasClean?: boolean; +} + +/** + * @private + */ +export default class LongPoll { + private reqs: Set; + private awaitingBatchAck: boolean; + private currentBatch: string[] | null; + private currentBatchTimer: number | null; + private batchBuffer: string[]; + private pollEndpoint: string; + private authToken?: string; + private token: string | null; + + /** + * @internal + */ + public skipHeartbeat: boolean; + /** + * @internal + */ + public onopen: (event: any) => void; + /** + * @internal + */ + public onerror: (error: any) => void; + /** + * @internal + */ + public onmessage: (event: MessageEvent) => void; + /** + * @internal + */ + public onclose: (event: CloseEvent | CloseEventInit) => void; + /** + * @internal + */ + public readyState: SocketState; + /** + * @internal + */ + public timeout: number; + + constructor(endPoint: string, protocols?: string[]) { + // we only support subprotocols for authToken + // ["phoenix", "base64url.bearer.phx.BASE64_ENCODED_TOKEN"] + if ( + protocols && + protocols.length === 2 && + protocols[1]!.startsWith(AUTH_TOKEN_PREFIX) + ) { + this.authToken = atob(protocols[1]!.slice(AUTH_TOKEN_PREFIX.length)); + } + this.token = null; + this.skipHeartbeat = true; + this.reqs = new Set(); + this.awaitingBatchAck = false; + this.currentBatch = null; + this.currentBatchTimer = null; + this.batchBuffer = []; + this.onopen = function () {}; // noop + this.onerror = function () {}; // noop + this.onmessage = function () {}; // noop + this.onclose = function () {}; // noop + this.pollEndpoint = this.normalizeEndpoint(endPoint); + this.readyState = SOCKET_STATES.connecting; + this.timeout = 20000; // will be set by Socket + // we must wait for the caller to finish setting up our callbacks and timeout properties + setTimeout(() => this.poll(), 0); + } + + normalizeEndpoint(endPoint: string): string { + return endPoint + .replace("ws://", "http://") + .replace("wss://", "https://") + .replace( + new RegExp("(.*)/" + TRANSPORTS.websocket), + "$1/" + TRANSPORTS.longpoll, + ); + } + + endpointURL(): string { + return Ajax.appendParams(this.pollEndpoint, { token: this.token }); + } + + closeAndRetry(code: number, reason: string, wasClean: boolean): void { + this.close(code, reason, wasClean); + this.readyState = SOCKET_STATES.connecting; + } + + ontimeout(): void { + this.onerror("timeout"); + this.closeAndRetry(1005, "timeout", false); + } + + isActive(): boolean { + return ( + this.readyState === SOCKET_STATES.open || + this.readyState === SOCKET_STATES.connecting + ); + } + + poll(): void { + const headers: Record = { Accept: "application/json" }; + if (this.authToken) { + headers["X-Phoenix-AuthToken"] = this.authToken; + } + this.ajax( + "GET", + headers, + null, + () => this.ontimeout(), + (resp: LongPollResponse | null) => { + let status: number; + if (resp) { + const { status: respStatus, token } = resp; + status = respStatus; + this.token = token || null; + } else { + status = 0; + } + + switch (status) { + case 200: + resp!.messages!.forEach((msg) => { + // Tasks are what things like event handlers, setTimeout callbacks, + // promise resolves and more are run within. + // In modern browsers, there are two different kinds of tasks, + // microtasks and macrotasks. + // Microtasks are mainly used for Promises, while macrotasks are + // used for everything else. + // Microtasks always have priority over macrotasks. If the JS engine + // is looking for a task to run, it will always try to empty the + // microtask queue before attempting to run anything from the + // macrotask queue. + // + // For the WebSocket transport, messages always arrive in their own + // event. This means that if any promises are resolved from within, + // their callbacks will always finish execution by the time the + // next message event handler is run. + // + // In order to emulate this behaviour, we need to make sure each + // onmessage handler is run within its own macrotask. + setTimeout(() => this.onmessage({ data: msg }), 0); + }); + this.poll(); + break; + case 204: + this.poll(); + break; + case 410: + this.readyState = SOCKET_STATES.open; + this.onopen({}); + this.poll(); + break; + case 403: + this.onerror(403); + this.close(1008, "forbidden", false); + break; + case 0: + case 500: + this.onerror(500); + this.closeAndRetry(1011, "internal server error", false); + break; + default: + throw new Error(`unhandled poll status ${status}`); + } + }, + ); + } + + // we collect all pushes within the current event loop by + // setTimeout 0, which optimizes back-to-back procedural + // pushes against an empty buffer + + send(body: string | ArrayBuffer): void { + let bodyStr: string; + if (typeof body !== "string") { + bodyStr = arrayBufferToBase64(body); + } else { + bodyStr = body; + } + if (this.currentBatch) { + this.currentBatch.push(bodyStr); + } else if (this.awaitingBatchAck) { + this.batchBuffer.push(bodyStr); + } else { + this.currentBatch = [bodyStr]; + this.currentBatchTimer = setTimeout(() => { + this.batchSend(this.currentBatch!); + this.currentBatch = null; + }, 0) as any; + } + } + + batchSend(messages: string[]): void { + this.awaitingBatchAck = true; + this.ajax( + "POST", + { "Content-Type": "application/x-ndjson" }, + messages.join("\n"), + () => this.onerror("timeout"), + (resp: any) => { + this.awaitingBatchAck = false; + if (!resp || resp.status !== 200) { + this.onerror(resp && resp.status); + this.closeAndRetry(1011, "internal server error", false); + } else if (this.batchBuffer.length > 0) { + this.batchSend(this.batchBuffer); + this.batchBuffer = []; + } + }, + ); + } + + close(code?: number, reason?: string, wasClean?: boolean): void { + for (const req of this.reqs) { + req.abort(); + } + this.readyState = SOCKET_STATES.closed; + const opts = Object.assign( + { code: 1000, reason: undefined, wasClean: true }, + { code, reason, wasClean }, + ); + this.batchBuffer = []; + if (this.currentBatchTimer !== null) { + clearTimeout(this.currentBatchTimer); + this.currentBatchTimer = null; + } + if (typeof CloseEvent !== "undefined") { + this.onclose(new CloseEvent("close", opts)); + } else { + this.onclose(opts); + } + } + + ajax( + method: "GET" | "POST", + headers: Record, + body: string | null, + onCallerTimeout: () => void, + callback: (resp: any) => void, + ): void { + const ontimeout = () => { + this.reqs.delete(req); + onCallerTimeout(); + }; + const req = Ajax.request( + method, + this.endpointURL(), + headers, + body, + this.timeout, + ontimeout, + (resp: any) => { + this.reqs.delete(req); + if (this.isActive()) { + callback(resp); + } + }, + ); + this.reqs.add(req); + } +} diff --git a/assets/js/phoenix/presence.js b/assets/js/phoenix/presence.js deleted file mode 100644 index cfb5af6c08..0000000000 --- a/assets/js/phoenix/presence.js +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Initializes the Presence - * @param {Channel} channel - The Channel - * @param {Object} opts - The options, - * for example `{events: {state: "state", diff: "diff"}}` - */ -export default class Presence { - - constructor(channel, opts = {}){ - let events = opts.events || {state: "presence_state", diff: "presence_diff"} - this.state = {} - this.pendingDiffs = [] - this.channel = channel - this.joinRef = null - this.caller = { - onJoin: function (){ }, - onLeave: function (){ }, - onSync: function (){ } - } - - this.channel.on(events.state, newState => { - let {onJoin, onLeave, onSync} = this.caller - - this.joinRef = this.channel.joinRef() - this.state = Presence.syncState(this.state, newState, onJoin, onLeave) - - this.pendingDiffs.forEach(diff => { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave) - }) - this.pendingDiffs = [] - onSync() - }) - - this.channel.on(events.diff, diff => { - let {onJoin, onLeave, onSync} = this.caller - - if(this.inPendingSyncState()){ - this.pendingDiffs.push(diff) - } else { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave) - onSync() - } - }) - } - - onJoin(callback){ this.caller.onJoin = callback } - - onLeave(callback){ this.caller.onLeave = callback } - - onSync(callback){ this.caller.onSync = callback } - - list(by){ return Presence.list(this.state, by) } - - inPendingSyncState(){ - return !this.joinRef || (this.joinRef !== this.channel.joinRef()) - } - - // lower-level public static API - - /** - * Used to sync the list of presences on the server - * with the client's state. An optional `onJoin` and `onLeave` callback can - * be provided to react to changes in the client's local presences across - * disconnects and reconnects with the server. - * - * @returns {Presence} - */ - static syncState(currentState, newState, onJoin, onLeave){ - let state = this.clone(currentState) - let joins = {} - let leaves = {} - - this.map(state, (key, presence) => { - if(!newState[key]){ - leaves[key] = presence - } - }) - this.map(newState, (key, newPresence) => { - let currentPresence = state[key] - if(currentPresence){ - let newRefs = newPresence.metas.map(m => m.phx_ref) - let curRefs = currentPresence.metas.map(m => m.phx_ref) - let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0) - let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0) - if(joinedMetas.length > 0){ - joins[key] = newPresence - joins[key].metas = joinedMetas - } - if(leftMetas.length > 0){ - leaves[key] = this.clone(currentPresence) - leaves[key].metas = leftMetas - } - } else { - joins[key] = newPresence - } - }) - return this.syncDiff(state, {joins: joins, leaves: leaves}, onJoin, onLeave) - } - - /** - * - * Used to sync a diff of presence join and leave - * events from the server, as they happen. Like `syncState`, `syncDiff` - * accepts optional `onJoin` and `onLeave` callbacks to react to a user - * joining or leaving from a device. - * - * @returns {Presence} - */ - static syncDiff(state, diff, onJoin, onLeave){ - let {joins, leaves} = this.clone(diff) - if(!onJoin){ onJoin = function (){ } } - if(!onLeave){ onLeave = function (){ } } - - this.map(joins, (key, newPresence) => { - let currentPresence = state[key] - state[key] = this.clone(newPresence) - if(currentPresence){ - let joinedRefs = state[key].metas.map(m => m.phx_ref) - let curMetas = currentPresence.metas.filter(m => joinedRefs.indexOf(m.phx_ref) < 0) - state[key].metas.unshift(...curMetas) - } - onJoin(key, currentPresence, newPresence) - }) - this.map(leaves, (key, leftPresence) => { - let currentPresence = state[key] - if(!currentPresence){ return } - let refsToRemove = leftPresence.metas.map(m => m.phx_ref) - currentPresence.metas = currentPresence.metas.filter(p => { - return refsToRemove.indexOf(p.phx_ref) < 0 - }) - onLeave(key, currentPresence, leftPresence) - if(currentPresence.metas.length === 0){ - delete state[key] - } - }) - return state - } - - /** - * Returns the array of presences, with selected metadata. - * - * @param {Object} presences - * @param {Function} chooser - * - * @returns {Presence} - */ - static list(presences, chooser){ - if(!chooser){ chooser = function (key, pres){ return pres } } - - return this.map(presences, (key, presence) => { - return chooser(key, presence) - }) - } - - // private - - static map(obj, func){ - return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key])) - } - - static clone(obj){ return JSON.parse(JSON.stringify(obj)) } -} diff --git a/assets/js/phoenix/presence.ts b/assets/js/phoenix/presence.ts new file mode 100644 index 0000000000..6553f6ec59 --- /dev/null +++ b/assets/js/phoenix/presence.ts @@ -0,0 +1,269 @@ +import type Channel from "./channel"; + +export interface PresenceMeta { + phx_ref: string; + [key: string]: any; +} + +export interface PresenceState { + metas: PresenceMeta[]; + [key: string]: any; +} + +export interface PresenceMap { + [key: string]: PresenceState; +} + +export interface PresenceDiff { + joins: PresenceMap; + leaves: PresenceMap; +} + +export interface PresenceOptions { + events?: { + state: string; + diff: string; + }; +} + +export type PresenceCallback = ( + key: string, + current: PresenceState | undefined, + newPres: PresenceState, +) => void; +export type PresenceSyncCallback = () => void; +export type PresenceChooser = ( + key: string, + presence: PresenceState, +) => T; + +/** + * Initializes the Presence + * @param channel - The Channel + * @param opts - The options, for example `{events: {state: "state", diff: "diff"}}` + */ +export default class Presence { + private state: PresenceMap; + private pendingDiffs: PresenceDiff[]; + private channel: Channel; + private joinRef: string | null; + private caller: { + onJoin: PresenceCallback; + onLeave: PresenceCallback; + onSync: PresenceSyncCallback; + }; + + constructor(channel: Channel, opts: PresenceOptions = {}) { + const events = opts.events || { + state: "presence_state", + diff: "presence_diff", + }; + this.state = {}; + this.pendingDiffs = []; + this.channel = channel; + this.joinRef = null; + this.caller = { + onJoin: function () {}, + onLeave: function () {}, + onSync: function () {}, + }; + + this.channel.on(events.state, (newState: PresenceMap) => { + const { onJoin, onLeave, onSync } = this.caller; + + this.joinRef = this.channel.joinRef(); + this.state = Presence.syncState(this.state, newState, onJoin, onLeave); + + this.pendingDiffs.forEach((diff) => { + this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + }); + this.pendingDiffs = []; + onSync(); + }); + + this.channel.on(events.diff, (diff: PresenceDiff) => { + const { onJoin, onLeave, onSync } = this.caller; + + if (this.inPendingSyncState()) { + this.pendingDiffs.push(diff); + } else { + this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + onSync(); + } + }); + } + + /** + * @internal + * @private + */ + onJoin(callback: PresenceCallback): void { + this.caller.onJoin = callback; + } + + /** + * @internal + * @private + */ + onLeave(callback: PresenceCallback): void { + this.caller.onLeave = callback; + } + + /** + * @internal + * @private + */ + onSync(callback: PresenceSyncCallback): void { + this.caller.onSync = callback; + } + + /** + * @internal + * @private + */ + list(by?: PresenceChooser): T[] { + return Presence.list(this.state, by); + } + + /** + * @internal + * @private + */ + inPendingSyncState(): boolean { + return !this.joinRef || this.joinRef !== this.channel.joinRef(); + } + + // lower-level public static API + + /** + * Used to sync the list of presences on the server + * with the client's state. An optional `onJoin` and `onLeave` callback can + * be provided to react to changes in the client's local presences across + * disconnects and reconnects with the server. + */ + static syncState( + currentState: PresenceMap, + newState: PresenceMap, + onJoin?: PresenceCallback, + onLeave?: PresenceCallback, + ): PresenceMap { + const state = this.clone(currentState); + const joins: PresenceMap = {}; + const leaves: PresenceMap = {}; + + this.map(state, (key, presence) => { + if (!newState[key]) { + leaves[key] = presence; + } + }); + this.map(newState, (key, newPresence) => { + const currentPresence = state[key]; + if (currentPresence) { + const newRefs = newPresence.metas.map((m) => m.phx_ref); + const curRefs = currentPresence.metas.map((m) => m.phx_ref); + const joinedMetas = newPresence.metas.filter( + (m) => curRefs.indexOf(m.phx_ref) < 0, + ); + const leftMetas = currentPresence.metas.filter( + (m) => newRefs.indexOf(m.phx_ref) < 0, + ); + if (joinedMetas.length > 0) { + joins[key] = newPresence; + joins[key]!.metas = joinedMetas; + } + if (leftMetas.length > 0) { + leaves[key] = this.clone(currentPresence); + leaves[key]!.metas = leftMetas; + } + } else { + joins[key] = newPresence; + } + }); + return this.syncDiff( + state, + { joins: joins, leaves: leaves }, + onJoin, + onLeave, + ); + } + + /** + * Used to sync a diff of presence join and leave + * events from the server, as they happen. Like `syncState`, `syncDiff` + * accepts optional `onJoin` and `onLeave` callbacks to react to a user + * joining or leaving from a device. + */ + static syncDiff( + state: PresenceMap, + diff: PresenceDiff, + onJoin?: PresenceCallback, + onLeave?: PresenceCallback, + ): PresenceMap { + const { joins, leaves } = this.clone(diff); + if (!onJoin) { + onJoin = function () {}; + } + if (!onLeave) { + onLeave = function () {}; + } + + this.map(joins, (key, newPresence) => { + const currentPresence = state[key]; + state[key] = this.clone(newPresence); + if (currentPresence) { + const joinedRefs = state[key]!.metas.map((m) => m.phx_ref); + const curMetas = currentPresence.metas.filter( + (m) => joinedRefs.indexOf(m.phx_ref) < 0, + ); + state[key]!.metas.unshift(...curMetas); + } + onJoin!(key, currentPresence, newPresence); + }); + this.map(leaves, (key, leftPresence) => { + const currentPresence = state[key]; + if (!currentPresence) { + return; + } + const refsToRemove = leftPresence.metas.map((m) => m.phx_ref); + currentPresence.metas = currentPresence.metas.filter((p) => { + return refsToRemove.indexOf(p.phx_ref) < 0; + }); + onLeave!(key, currentPresence, leftPresence); + if (currentPresence.metas.length === 0) { + delete state[key]; + } + }); + return state; + } + + /** + * Returns the array of presences, with selected metadata. + */ + static list( + presences: PresenceMap, + chooser?: PresenceChooser, + ): T[] { + if (!chooser) { + chooser = function (key, pres) { + return pres as any; + }; + } + + return this.map(presences, (key, presence) => { + return chooser!(key, presence); + }); + } + + // private + + private static map( + obj: PresenceMap, + func: (key: string, presence: PresenceState) => T, + ): T[] { + return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]!)); + } + + private static clone(obj: T): T { + return JSON.parse(JSON.stringify(obj)); + } +} diff --git a/assets/js/phoenix/push.js b/assets/js/phoenix/push.js deleted file mode 100644 index 2e497a2dc6..0000000000 --- a/assets/js/phoenix/push.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Initializes the Push - * @param {Channel} channel - The Channel - * @param {string} event - The event, for example `"phx_join"` - * @param {Object} payload - The payload, for example `{user_id: 123}` - * @param {number} timeout - The push timeout in milliseconds - */ -export default class Push { - constructor(channel, event, payload, timeout){ - this.channel = channel - this.event = event - this.payload = payload || function (){ return {} } - this.receivedResp = null - this.timeout = timeout - this.timeoutTimer = null - this.recHooks = [] - this.sent = false - } - - /** - * - * @param {number} timeout - */ - resend(timeout){ - this.timeout = timeout - this.reset() - this.send() - } - - /** - * - */ - send(){ - if(this.hasReceived("timeout")){ return } - this.startTimeout() - this.sent = true - this.channel.socket.push({ - topic: this.channel.topic, - event: this.event, - payload: this.payload(), - ref: this.ref, - join_ref: this.channel.joinRef() - }) - } - - /** - * - * @param {*} status - * @param {*} callback - */ - receive(status, callback){ - if(this.hasReceived(status)){ - callback(this.receivedResp.response) - } - - this.recHooks.push({status, callback}) - return this - } - - /** - * @private - */ - reset(){ - this.cancelRefEvent() - this.ref = null - this.refEvent = null - this.receivedResp = null - this.sent = false - } - - /** - * @private - */ - matchReceive({status, response, _ref}){ - this.recHooks.filter(h => h.status === status) - .forEach(h => h.callback(response)) - } - - /** - * @private - */ - cancelRefEvent(){ - if(!this.refEvent){ return } - this.channel.off(this.refEvent) - } - - /** - * @private - */ - cancelTimeout(){ - clearTimeout(this.timeoutTimer) - this.timeoutTimer = null - } - - /** - * @private - */ - startTimeout(){ - if(this.timeoutTimer){ this.cancelTimeout() } - this.ref = this.channel.socket.makeRef() - this.refEvent = this.channel.replyEventName(this.ref) - - this.channel.on(this.refEvent, payload => { - this.cancelRefEvent() - this.cancelTimeout() - this.receivedResp = payload - this.matchReceive(payload) - }) - - this.timeoutTimer = setTimeout(() => { - this.trigger("timeout", {}) - }, this.timeout) - } - - /** - * @private - */ - hasReceived(status){ - return this.receivedResp && this.receivedResp.status === status - } - - /** - * @private - */ - trigger(status, response){ - this.channel.trigger(this.refEvent, {status, response}) - } -} diff --git a/assets/js/phoenix/push.ts b/assets/js/phoenix/push.ts new file mode 100644 index 0000000000..bbfcca5fa0 --- /dev/null +++ b/assets/js/phoenix/push.ts @@ -0,0 +1,191 @@ +import type Channel from "./channel"; + +export interface PushResponse { + status: string; + response: any; + _ref?: string; +} + +export interface ReceiveHook { + status: string; + callback: (response: any) => void; +} + +/** + * Initializes the Push. + * @param channel - The Channel + * @param event - The event, for example `"phx_join"` + * @param payload - The payload, for example `{user_id: 123}` + * @param timeout - The push timeout in milliseconds + * @internal + */ +export default class Push { + private receivedResp: PushResponse | null; + private timeoutTimer: number | null; + private recHooks: ReceiveHook[]; + private refEvent: string | null; + + /** + * @internal + * @private + */ + event: string; + + /** + * @internal + * @private + */ + timeout: number; + + /** + * @internal + * @private + */ + ref: string | null; + + /** + * @internal + * @private + */ + payload: () => any; + + /** + * @internal + * @private + */ + channel: Channel; + + /** + * @internal + */ + constructor( + channel: Channel, + event: string, + payload: any | (() => any), + timeout: number, + ) { + this.channel = channel; + this.event = event; + this.payload = + typeof payload === "function" ? payload : () => payload || {}; + this.receivedResp = null; + this.timeout = timeout; + this.timeoutTimer = null; + this.recHooks = []; + this.ref = null; + this.refEvent = null; + } + + /** + * Resend the push with a new timeout + */ + resend(timeout: number): void { + this.timeout = timeout; + this.reset(); + this.send(); + } + + /** + * Send the push + */ + send(): void { + if (this.hasReceived("timeout")) { + return; + } + this.startTimeout(); + this.channel.socket.push({ + topic: this.channel.topic, + event: this.event, + payload: this.payload(), + ref: this.ref, + join_ref: this.channel.joinRef(), + }); + } + + /** + * Register a callback for a specific response status + */ + receive(status: string, callback: (response: any) => void): Push { + if (this.hasReceived(status)) { + callback(this.receivedResp!.response); + } + + this.recHooks.push({ status, callback }); + return this; + } + + /** + * @private + */ + reset(): void { + this.cancelRefEvent(); + this.ref = null; + this.refEvent = null; + this.receivedResp = null; + } + + /** + * @private + */ + matchReceive({ status, response, _ref }: PushResponse): void { + this.recHooks + .filter((h) => h.status === status) + .forEach((h) => h.callback(response)); + } + + /** + * @private + */ + cancelRefEvent(): void { + if (!this.refEvent) { + return; + } + this.channel.off(this.refEvent); + } + + /** + * @private + */ + cancelTimeout(): void { + if (this.timeoutTimer !== null) { + clearTimeout(this.timeoutTimer); + this.timeoutTimer = null; + } + } + + /** + * @private + */ + startTimeout(): void { + if (this.timeoutTimer) { + this.cancelTimeout(); + } + this.ref = this.channel.socket.makeRef(); + this.refEvent = this.channel.replyEventName(this.ref); + + this.channel.on(this.refEvent, (payload: PushResponse) => { + this.cancelRefEvent(); + this.cancelTimeout(); + this.receivedResp = payload; + this.matchReceive(payload); + }); + + this.timeoutTimer = setTimeout(() => { + this.trigger("timeout", {}); + }, this.timeout) as any; + } + + /** + * @private + */ + hasReceived(status: string): boolean { + return this.receivedResp && this.receivedResp.status === status; + } + + /** + * @private + */ + trigger(status: string, response: any): void { + this.channel.trigger(this.refEvent!, { status, response }); + } +} diff --git a/assets/js/phoenix/serializer.js b/assets/js/phoenix/serializer.js deleted file mode 100644 index 3df8eb3ef7..0000000000 --- a/assets/js/phoenix/serializer.js +++ /dev/null @@ -1,112 +0,0 @@ -/* The default serializer for encoding and decoding messages */ -import { - CHANNEL_EVENTS -} from "./constants" - -export default { - HEADER_LENGTH: 1, - META_LENGTH: 4, - KINDS: {push: 0, reply: 1, broadcast: 2}, - - encode(msg, callback){ - if(msg.payload.constructor === ArrayBuffer){ - return callback(this.binaryEncode(msg)) - } else { - let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload] - return callback(JSON.stringify(payload)) - } - }, - - decode(rawPayload, callback){ - if(rawPayload.constructor === ArrayBuffer){ - return callback(this.binaryDecode(rawPayload)) - } else { - let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload) - return callback({join_ref, ref, topic, event, payload}) - } - }, - - // private - - binaryEncode(message){ - let {join_ref, ref, event, topic, payload} = message - let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length - let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength) - let view = new DataView(header) - let offset = 0 - - view.setUint8(offset++, this.KINDS.push) // kind - view.setUint8(offset++, join_ref.length) - view.setUint8(offset++, ref.length) - view.setUint8(offset++, topic.length) - view.setUint8(offset++, event.length) - Array.from(join_ref, char => view.setUint8(offset++, char.charCodeAt(0))) - Array.from(ref, char => view.setUint8(offset++, char.charCodeAt(0))) - Array.from(topic, char => view.setUint8(offset++, char.charCodeAt(0))) - Array.from(event, char => view.setUint8(offset++, char.charCodeAt(0))) - - var combined = new Uint8Array(header.byteLength + payload.byteLength) - combined.set(new Uint8Array(header), 0) - combined.set(new Uint8Array(payload), header.byteLength) - - return combined.buffer - }, - - binaryDecode(buffer){ - let view = new DataView(buffer) - let kind = view.getUint8(0) - let decoder = new TextDecoder() - switch(kind){ - case this.KINDS.push: return this.decodePush(buffer, view, decoder) - case this.KINDS.reply: return this.decodeReply(buffer, view, decoder) - case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder) - } - }, - - decodePush(buffer, view, decoder){ - let joinRefSize = view.getUint8(1) - let topicSize = view.getUint8(2) - let eventSize = view.getUint8(3) - let offset = this.HEADER_LENGTH + this.META_LENGTH - 1 // pushes have no ref - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)) - offset = offset + joinRefSize - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)) - offset = offset + topicSize - let event = decoder.decode(buffer.slice(offset, offset + eventSize)) - offset = offset + eventSize - let data = buffer.slice(offset, buffer.byteLength) - return {join_ref: joinRef, ref: null, topic: topic, event: event, payload: data} - }, - - decodeReply(buffer, view, decoder){ - let joinRefSize = view.getUint8(1) - let refSize = view.getUint8(2) - let topicSize = view.getUint8(3) - let eventSize = view.getUint8(4) - let offset = this.HEADER_LENGTH + this.META_LENGTH - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)) - offset = offset + joinRefSize - let ref = decoder.decode(buffer.slice(offset, offset + refSize)) - offset = offset + refSize - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)) - offset = offset + topicSize - let event = decoder.decode(buffer.slice(offset, offset + eventSize)) - offset = offset + eventSize - let data = buffer.slice(offset, buffer.byteLength) - let payload = {status: event, response: data} - return {join_ref: joinRef, ref: ref, topic: topic, event: CHANNEL_EVENTS.reply, payload: payload} - }, - - decodeBroadcast(buffer, view, decoder){ - let topicSize = view.getUint8(1) - let eventSize = view.getUint8(2) - let offset = this.HEADER_LENGTH + 2 - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)) - offset = offset + topicSize - let event = decoder.decode(buffer.slice(offset, offset + eventSize)) - offset = offset + eventSize - let data = buffer.slice(offset, buffer.byteLength) - - return {join_ref: null, ref: null, topic: topic, event: event, payload: data} - } -} diff --git a/assets/js/phoenix/serializer.ts b/assets/js/phoenix/serializer.ts new file mode 100644 index 0000000000..30a84dfc4c --- /dev/null +++ b/assets/js/phoenix/serializer.ts @@ -0,0 +1,187 @@ +/* The default serializer for encoding and decoding messages */ +import { CHANNEL_EVENTS } from "./constants"; + +export interface Message { + join_ref: string | null; + ref: string | null; + topic: string; + event: string; + payload: any; +} + +interface BinaryMessage extends Omit { + payload: ArrayBuffer; +} + +interface ReplyPayload { + status: string; + response: ArrayBuffer; +} + +/** + * @internal + * @private + */ +const Serializer = { + HEADER_LENGTH: 1, + META_LENGTH: 4, + KINDS: { push: 0, reply: 1, broadcast: 2 } as const, + + encode( + msg: Message | BinaryMessage, + callback: (encoded: string | ArrayBuffer) => void, + ): void { + if (msg.payload.constructor === ArrayBuffer) { + return callback(this.binaryEncode(msg as BinaryMessage)); + } else { + const payload = [ + msg.join_ref, + msg.ref, + msg.topic, + msg.event, + msg.payload, + ]; + return callback(JSON.stringify(payload)); + } + }, + + decode( + rawPayload: string | ArrayBuffer, + callback: (decoded: Message) => void, + ): void { + if (rawPayload.constructor === ArrayBuffer) { + return callback(this.binaryDecode(rawPayload as ArrayBuffer)); + } else { + const [join_ref, ref, topic, event, payload] = JSON.parse( + rawPayload as string, + ); + return callback({ join_ref, ref, topic, event, payload }); + } + }, + + // private + + binaryEncode(message: BinaryMessage): ArrayBuffer { + const { join_ref, ref, event, topic, payload } = message; + const metaLength = + this.META_LENGTH + + join_ref.length + + ref.length + + topic.length + + event.length; + const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); + const view = new DataView(header); + let offset = 0; + + view.setUint8(offset++, this.KINDS.push); // kind + view.setUint8(offset++, join_ref.length); + view.setUint8(offset++, ref.length); + view.setUint8(offset++, topic.length); + view.setUint8(offset++, event.length); + Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0))); + Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0))); + Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0))); + Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0))); + + const combined = new Uint8Array(header.byteLength + payload.byteLength); + combined.set(new Uint8Array(header), 0); + combined.set(new Uint8Array(payload), header.byteLength); + + return combined.buffer; + }, + + binaryDecode(buffer: ArrayBuffer): Message { + const view = new DataView(buffer); + const kind = view.getUint8(0); + const decoder = new TextDecoder(); + switch (kind) { + case this.KINDS.push: + return this.decodePush(buffer, view, decoder); + case this.KINDS.reply: + return this.decodeReply(buffer, view, decoder); + case this.KINDS.broadcast: + return this.decodeBroadcast(buffer, view, decoder); + default: + throw new Error(`Unknown message kind: ${kind}`); + } + }, + + decodePush( + buffer: ArrayBuffer, + view: DataView, + decoder: TextDecoder, + ): Message { + const joinRefSize = view.getUint8(1); + const topicSize = view.getUint8(2); + const eventSize = view.getUint8(3); + let offset = this.HEADER_LENGTH + this.META_LENGTH - 1; // pushes have no ref + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + offset = offset + joinRefSize; + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + offset = offset + topicSize; + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); + offset = offset + eventSize; + const data = buffer.slice(offset, buffer.byteLength); + return { + join_ref: joinRef, + ref: null, + topic: topic, + event: event, + payload: data, + }; + }, + + decodeReply( + buffer: ArrayBuffer, + view: DataView, + decoder: TextDecoder, + ): Message { + const joinRefSize = view.getUint8(1); + const refSize = view.getUint8(2); + const topicSize = view.getUint8(3); + const eventSize = view.getUint8(4); + let offset = this.HEADER_LENGTH + this.META_LENGTH; + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + offset = offset + joinRefSize; + const ref = decoder.decode(buffer.slice(offset, offset + refSize)); + offset = offset + refSize; + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + offset = offset + topicSize; + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); + offset = offset + eventSize; + const data = buffer.slice(offset, buffer.byteLength); + const payload: ReplyPayload = { status: event, response: data }; + return { + join_ref: joinRef, + ref: ref, + topic: topic, + event: CHANNEL_EVENTS.reply, + payload: payload, + }; + }, + + decodeBroadcast( + buffer: ArrayBuffer, + view: DataView, + decoder: TextDecoder, + ): Message { + const topicSize = view.getUint8(1); + const eventSize = view.getUint8(2); + let offset = this.HEADER_LENGTH + 2; + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + offset = offset + topicSize; + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); + offset = offset + eventSize; + const data = buffer.slice(offset, buffer.byteLength); + + return { + join_ref: null, + ref: null, + topic: topic, + event: event, + payload: data, + }; + }, +}; + +export default Serializer; diff --git a/assets/js/phoenix/socket.js b/assets/js/phoenix/socket.js deleted file mode 100644 index a92452196e..0000000000 --- a/assets/js/phoenix/socket.js +++ /dev/null @@ -1,667 +0,0 @@ -import { - global, - phxWindow, - CHANNEL_EVENTS, - DEFAULT_TIMEOUT, - DEFAULT_VSN, - SOCKET_STATES, - TRANSPORTS, - WS_CLOSE_NORMAL, - AUTH_TOKEN_PREFIX -} from "./constants" - -import { - closure -} from "./utils" - -import Ajax from "./ajax" -import Channel from "./channel" -import LongPoll from "./longpoll" -import Serializer from "./serializer" -import Timer from "./timer" - -/** Initializes the Socket * - * - * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim) - * - * @param {string} endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`, - * `"wss://example.com"` - * `"/socket"` (inherited host & protocol) - * @param {Object} [opts] - Optional configuration - * @param {Function} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll. - * - * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined. - * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`. - * - * @param {number} [opts.longPollFallbackMs] - The millisecond time to attempt the primary transport - * before falling back to the LongPoll transport. Disabled by default. - * - * @param {boolean} [opts.debug] - When true, enables debug logging. Default false. - * - * @param {Function} [opts.encode] - The function to encode outgoing messages. - * - * Defaults to JSON encoder. - * - * @param {Function} [opts.decode] - The function to decode incoming messages. - * - * Defaults to JSON: - * - * ```javascript - * (payload, callback) => callback(JSON.parse(payload)) - * ``` - * - * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts. - * - * Defaults `DEFAULT_TIMEOUT` - * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message - * @param {Function} [opts.reconnectAfterMs] - The optional function that returns the - * socket reconnect interval, in milliseconds. - * - * Defaults to stepped backoff of: - * - * ```javascript - * function(tries){ - * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000 - * } - * ```` - * - * @param {Function} [opts.rejoinAfterMs] - The optional function that returns the millisec - * rejoin interval for individual channels. - * - * ```javascript - * function(tries){ - * return [1000, 2000, 5000][tries - 1] || 10000 - * } - * ```` - * - * @param {Function} [opts.logger] - The optional function for specialized logging, ie: - * - * ```javascript - * function(kind, msg, data) { - * console.log(`${kind}: ${msg}`, data) - * } - * ``` - * - * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request. - * - * Defaults to 20s (double the server long poll timer). - * - * @param {(Object|function)} [opts.params] - The optional params to pass when connecting - * @param {string} [opts.authToken] - the optional authentication token to be exposed on the server - * under the `:auth_token` connect_info key. - * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames. - * - * Defaults to "arraybuffer" - * - * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect. - * - * Defaults to DEFAULT_VSN. - * - * @param {Object} [opts.sessionStorage] - An optional Storage compatible object - * Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is - * useful when Phoenix won't have access to `sessionStorage`. For example, This could - * happen if a site loads a cross-domain channel in an iframe. Example usage: - * - * class InMemoryStorage { - * constructor() { this.storage = {} } - * getItem(keyName) { return this.storage[keyName] || null } - * removeItem(keyName) { delete this.storage[keyName] } - * setItem(keyName, keyValue) { this.storage[keyName] = keyValue } - * } - * -*/ -export default class Socket { - constructor(endPoint, opts = {}){ - this.stateChangeCallbacks = {open: [], close: [], error: [], message: []} - this.channels = [] - this.sendBuffer = [] - this.ref = 0 - this.timeout = opts.timeout || DEFAULT_TIMEOUT - this.transport = opts.transport || global.WebSocket || LongPoll - this.primaryPassedHealthCheck = false - this.longPollFallbackMs = opts.longPollFallbackMs - this.fallbackTimer = null - this.sessionStore = opts.sessionStorage || (global && global.sessionStorage) - this.establishedConnections = 0 - this.defaultEncoder = Serializer.encode.bind(Serializer) - this.defaultDecoder = Serializer.decode.bind(Serializer) - this.closeWasClean = false - this.disconnecting = false - this.binaryType = opts.binaryType || "arraybuffer" - this.connectClock = 1 - if(this.transport !== LongPoll){ - this.encode = opts.encode || this.defaultEncoder - this.decode = opts.decode || this.defaultDecoder - } else { - this.encode = this.defaultEncoder - this.decode = this.defaultDecoder - } - let awaitingConnectionOnPageShow = null - if(phxWindow && phxWindow.addEventListener){ - phxWindow.addEventListener("pagehide", _e => { - if(this.conn){ - this.disconnect() - awaitingConnectionOnPageShow = this.connectClock - } - }) - phxWindow.addEventListener("pageshow", _e => { - if(awaitingConnectionOnPageShow === this.connectClock){ - awaitingConnectionOnPageShow = null - this.connect() - } - }) - } - this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000 - this.rejoinAfterMs = (tries) => { - if(opts.rejoinAfterMs){ - return opts.rejoinAfterMs(tries) - } else { - return [1000, 2000, 5000][tries - 1] || 10000 - } - } - this.reconnectAfterMs = (tries) => { - if(opts.reconnectAfterMs){ - return opts.reconnectAfterMs(tries) - } else { - return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000 - } - } - this.logger = opts.logger || null - if(!this.logger && opts.debug){ - this.logger = (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) } - } - this.longpollerTimeout = opts.longpollerTimeout || 20000 - this.params = closure(opts.params || {}) - this.endPoint = `${endPoint}/${TRANSPORTS.websocket}` - this.vsn = opts.vsn || DEFAULT_VSN - this.heartbeatTimeoutTimer = null - this.heartbeatTimer = null - this.pendingHeartbeatRef = null - this.reconnectTimer = new Timer(() => { - this.teardown(() => this.connect()) - }, this.reconnectAfterMs) - this.authToken = opts.authToken - } - - /** - * Returns the LongPoll transport reference - */ - getLongPollTransport(){ return LongPoll } - - /** - * Disconnects and replaces the active transport - * - * @param {Function} newTransport - The new transport class to instantiate - * - */ - replaceTransport(newTransport){ - this.connectClock++ - this.closeWasClean = true - clearTimeout(this.fallbackTimer) - this.reconnectTimer.reset() - if(this.conn){ - this.conn.close() - this.conn = null - } - this.transport = newTransport - } - - /** - * Returns the socket protocol - * - * @returns {string} - */ - protocol(){ return location.protocol.match(/^https/) ? "wss" : "ws" } - - /** - * The fully qualified socket url - * - * @returns {string} - */ - endPointURL(){ - let uri = Ajax.appendParams( - Ajax.appendParams(this.endPoint, this.params()), {vsn: this.vsn}) - if(uri.charAt(0) !== "/"){ return uri } - if(uri.charAt(1) === "/"){ return `${this.protocol()}:${uri}` } - - return `${this.protocol()}://${location.host}${uri}` - } - - /** - * Disconnects the socket - * - * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes. - * - * @param {Function} callback - Optional callback which is called after socket is disconnected. - * @param {integer} code - A status code for disconnection (Optional). - * @param {string} reason - A textual description of the reason to disconnect. (Optional) - */ - disconnect(callback, code, reason){ - this.connectClock++ - this.disconnecting = true - this.closeWasClean = true - clearTimeout(this.fallbackTimer) - this.reconnectTimer.reset() - this.teardown(() => { - this.disconnecting = false - callback && callback() - }, code, reason) - } - - /** - * - * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}` - * - * Passing params to connect is deprecated; pass them in the Socket constructor instead: - * `new Socket("/socket", {params: {user_id: userToken}})`. - */ - connect(params){ - if(params){ - console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor") - this.params = closure(params) - } - if(this.conn && !this.disconnecting){ return } - if(this.longPollFallbackMs && this.transport !== LongPoll){ - this.connectWithFallback(LongPoll, this.longPollFallbackMs) - } else { - this.transportConnect() - } - } - - /** - * Logs the message. Override `this.logger` for specialized logging. noops by default - * @param {string} kind - * @param {string} msg - * @param {Object} data - */ - log(kind, msg, data){ this.logger && this.logger(kind, msg, data) } - - /** - * Returns true if a logger has been set on this socket. - */ - hasLogger(){ return this.logger !== null } - - /** - * Registers callbacks for connection open events - * - * @example socket.onOpen(function(){ console.info("the socket was opened") }) - * - * @param {Function} callback - */ - onOpen(callback){ - let ref = this.makeRef() - this.stateChangeCallbacks.open.push([ref, callback]) - return ref - } - - /** - * Registers callbacks for connection close events - * @param {Function} callback - */ - onClose(callback){ - let ref = this.makeRef() - this.stateChangeCallbacks.close.push([ref, callback]) - return ref - } - - /** - * Registers callbacks for connection error events - * - * @example socket.onError(function(error){ alert("An error occurred") }) - * - * @param {Function} callback - */ - onError(callback){ - let ref = this.makeRef() - this.stateChangeCallbacks.error.push([ref, callback]) - return ref - } - - /** - * Registers callbacks for connection message events - * @param {Function} callback - */ - onMessage(callback){ - let ref = this.makeRef() - this.stateChangeCallbacks.message.push([ref, callback]) - return ref - } - - /** - * Pings the server and invokes the callback with the RTT in milliseconds - * @param {Function} callback - * - * Returns true if the ping was pushed or false if unable to be pushed. - */ - ping(callback){ - if(!this.isConnected()){ return false } - let ref = this.makeRef() - let startTime = Date.now() - this.push({topic: "phoenix", event: "heartbeat", payload: {}, ref: ref}) - let onMsgRef = this.onMessage(msg => { - if(msg.ref === ref){ - this.off([onMsgRef]) - callback(Date.now() - startTime) - } - }) - return true - } - - /** - * @private - */ - - transportConnect(){ - this.connectClock++ - this.closeWasClean = false - let protocols = undefined - // Sec-WebSocket-Protocol based token - // (longpoll uses Authorization header instead) - if(this.authToken){ - protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`] - } - this.conn = new this.transport(this.endPointURL(), protocols) - this.conn.binaryType = this.binaryType - this.conn.timeout = this.longpollerTimeout - this.conn.onopen = () => this.onConnOpen() - this.conn.onerror = error => this.onConnError(error) - this.conn.onmessage = event => this.onConnMessage(event) - this.conn.onclose = event => this.onConnClose(event) - } - - getSession(key){ return this.sessionStore && this.sessionStore.getItem(key) } - - storeSession(key, val){ this.sessionStore && this.sessionStore.setItem(key, val) } - - connectWithFallback(fallbackTransport, fallbackThreshold = 2500){ - clearTimeout(this.fallbackTimer) - let established = false - let primaryTransport = true - let openRef, errorRef - let fallback = (reason) => { - this.log("transport", `falling back to ${fallbackTransport.name}...`, reason) - this.off([openRef, errorRef]) - primaryTransport = false - this.replaceTransport(fallbackTransport) - this.transportConnect() - } - if(this.getSession(`phx:fallback:${fallbackTransport.name}`)){ return fallback("memorized") } - - this.fallbackTimer = setTimeout(fallback, fallbackThreshold) - - errorRef = this.onError(reason => { - this.log("transport", "error", reason) - if(primaryTransport && !established){ - clearTimeout(this.fallbackTimer) - fallback(reason) - } - }) - this.onOpen(() => { - established = true - if(!primaryTransport){ - // only memorize LP if we never connected to primary - if(!this.primaryPassedHealthCheck){ this.storeSession(`phx:fallback:${fallbackTransport.name}`, "true") } - return this.log("transport", `established ${fallbackTransport.name} fallback`) - } - // if we've established primary, give the fallback a new period to attempt ping - clearTimeout(this.fallbackTimer) - this.fallbackTimer = setTimeout(fallback, fallbackThreshold) - this.ping(rtt => { - this.log("transport", "connected to primary after", rtt) - this.primaryPassedHealthCheck = true - clearTimeout(this.fallbackTimer) - }) - }) - this.transportConnect() - } - - clearHeartbeats(){ - clearTimeout(this.heartbeatTimer) - clearTimeout(this.heartbeatTimeoutTimer) - } - - onConnOpen(){ - if(this.hasLogger()) this.log("transport", `${this.transport.name} connected to ${this.endPointURL()}`) - this.closeWasClean = false - this.disconnecting = false - this.establishedConnections++ - this.flushSendBuffer() - this.reconnectTimer.reset() - this.resetHeartbeat() - this.stateChangeCallbacks.open.forEach(([, callback]) => callback()) - } - - /** - * @private - */ - - heartbeatTimeout(){ - if(this.pendingHeartbeatRef){ - this.pendingHeartbeatRef = null - if(this.hasLogger()){ this.log("transport", "heartbeat timeout. Attempting to re-establish connection") } - this.triggerChanError() - this.closeWasClean = false - this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, "heartbeat timeout") - } - } - - resetHeartbeat(){ - if(this.conn && this.conn.skipHeartbeat){ return } - this.pendingHeartbeatRef = null - this.clearHeartbeats() - this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs) - } - - teardown(callback, code, reason){ - if(!this.conn){ - return callback && callback() - } - let connectClock = this.connectClock - - this.waitForBufferDone(() => { - if(connectClock !== this.connectClock){ return } - if(this.conn){ - if(code){ this.conn.close(code, reason || "") } else { this.conn.close() } - } - - this.waitForSocketClosed(() => { - if(connectClock !== this.connectClock){ return } - if(this.conn){ - this.conn.onopen = function (){ } // noop - this.conn.onerror = function (){ } // noop - this.conn.onmessage = function (){ } // noop - this.conn.onclose = function (){ } // noop - this.conn = null - } - - callback && callback() - }) - }) - } - - waitForBufferDone(callback, tries = 1){ - if(tries === 5 || !this.conn || !this.conn.bufferedAmount){ - callback() - return - } - - setTimeout(() => { - this.waitForBufferDone(callback, tries + 1) - }, 150 * tries) - } - - waitForSocketClosed(callback, tries = 1){ - if(tries === 5 || !this.conn || this.conn.readyState === SOCKET_STATES.closed){ - callback() - return - } - - setTimeout(() => { - this.waitForSocketClosed(callback, tries + 1) - }, 150 * tries) - } - - onConnClose(event){ - let closeCode = event && event.code - if(this.hasLogger()) this.log("transport", "close", event) - this.triggerChanError() - this.clearHeartbeats() - if(!this.closeWasClean && closeCode !== 1000){ - this.reconnectTimer.scheduleTimeout() - } - this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event)) - } - - /** - * @private - */ - onConnError(error){ - if(this.hasLogger()) this.log("transport", error) - let transportBefore = this.transport - let establishedBefore = this.establishedConnections - this.stateChangeCallbacks.error.forEach(([, callback]) => { - callback(error, transportBefore, establishedBefore) - }) - if(transportBefore === this.transport || establishedBefore > 0){ - this.triggerChanError() - } - } - - /** - * @private - */ - triggerChanError(){ - this.channels.forEach(channel => { - if(!(channel.isErrored() || channel.isLeaving() || channel.isClosed())){ - channel.trigger(CHANNEL_EVENTS.error) - } - }) - } - - /** - * @returns {string} - */ - connectionState(){ - switch(this.conn && this.conn.readyState){ - case SOCKET_STATES.connecting: return "connecting" - case SOCKET_STATES.open: return "open" - case SOCKET_STATES.closing: return "closing" - default: return "closed" - } - } - - /** - * @returns {boolean} - */ - isConnected(){ return this.connectionState() === "open" } - - /** - * @private - * - * @param {Channel} - */ - remove(channel){ - this.off(channel.stateChangeRefs) - this.channels = this.channels.filter(c => c !== channel) - } - - /** - * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations. - * - * @param {refs} - list of refs returned by calls to - * `onOpen`, `onClose`, `onError,` and `onMessage` - */ - off(refs){ - for(let key in this.stateChangeCallbacks){ - this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => { - return refs.indexOf(ref) === -1 - }) - } - } - - /** - * Initiates a new channel for the given topic - * - * @param {string} topic - * @param {Object} chanParams - Parameters for the channel - * @returns {Channel} - */ - channel(topic, chanParams = {}){ - let chan = new Channel(topic, chanParams, this) - this.channels.push(chan) - return chan - } - - /** - * @param {Object} data - */ - push(data){ - if(this.hasLogger()){ - let {topic, event, payload, ref, join_ref} = data - this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload) - } - - if(this.isConnected()){ - this.encode(data, result => this.conn.send(result)) - } else { - this.sendBuffer.push(() => this.encode(data, result => this.conn.send(result))) - } - } - - /** - * Return the next message ref, accounting for overflows - * @returns {string} - */ - makeRef(){ - let newRef = this.ref + 1 - if(newRef === this.ref){ this.ref = 0 } else { this.ref = newRef } - - return this.ref.toString() - } - - sendHeartbeat(){ - if(this.pendingHeartbeatRef && !this.isConnected()){ return } - this.pendingHeartbeatRef = this.makeRef() - this.push({topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef}) - this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs) - } - - flushSendBuffer(){ - if(this.isConnected() && this.sendBuffer.length > 0){ - this.sendBuffer.forEach(callback => callback()) - this.sendBuffer = [] - } - } - - onConnMessage(rawMessage){ - this.decode(rawMessage.data, msg => { - let {topic, event, payload, ref, join_ref} = msg - if(ref && ref === this.pendingHeartbeatRef){ - this.clearHeartbeats() - this.pendingHeartbeatRef = null - this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs) - } - - if(this.hasLogger()) this.log("receive", `${payload.status || ""} ${topic} ${event} ${ref && "(" + ref + ")" || ""}`, payload) - - for(let i = 0; i < this.channels.length; i++){ - const channel = this.channels[i] - if(!channel.isMember(topic, event, payload, join_ref)){ continue } - channel.trigger(event, payload, ref, join_ref) - } - - for(let i = 0; i < this.stateChangeCallbacks.message.length; i++){ - let [, callback] = this.stateChangeCallbacks.message[i] - callback(msg) - } - }) - } - - leaveOpenTopic(topic){ - let dupChannel = this.channels.find(c => c.topic === topic && (c.isJoined() || c.isJoining())) - if(dupChannel){ - if(this.hasLogger()) this.log("transport", `leaving duplicate topic "${topic}"`) - dupChannel.leave() - } - } -} diff --git a/assets/js/phoenix/socket.ts b/assets/js/phoenix/socket.ts new file mode 100644 index 0000000000..83ec3628f9 --- /dev/null +++ b/assets/js/phoenix/socket.ts @@ -0,0 +1,1003 @@ +import { + global, + phxWindow, + CHANNEL_EVENTS, + DEFAULT_TIMEOUT, + DEFAULT_VSN, + SOCKET_STATES, + TRANSPORTS, + WS_CLOSE_NORMAL, + AUTH_TOKEN_PREFIX, +} from "./constants"; + +import { closure } from "./utils"; + +import Ajax from "./ajax"; +import Channel from "./channel"; +import LongPoll from "./longpoll"; +import Serializer from "./serializer"; +import Timer from "./timer"; + +// Type definitions for WebSocket-like transport +interface Transport { + new (url: string, protocols?: string | string[]): TransportInstance; + name?: string; +} + +type TransportClass = Transport | typeof LongPoll; + +// Generic timer type that works in both Node.js and browser environments +type TimerHandle = ReturnType; + +interface TransportInstance { + binaryType?: string; + timeout?: number; + readyState: number; + bufferedAmount?: number; + skipHeartbeat?: boolean; + onopen: ((event: Event) => void) | null; + onerror: ((event: Event) => void) | null; + onmessage: ((event: MessageEvent) => void) | null; + onclose: ((event: CloseEvent) => void) | null; + send(data: string | ArrayBuffer): void; + close(code?: number, reason?: string): void; +} + +// Message structure interfaces +export interface Message { + topic: string; + event: string; + payload: any; + ref: string | null; + join_ref?: string | null; +} + +// Callback types +/** + * @inline + */ +type StateChangeCallback = () => void; +/** + * @inline + */ +type ErrorCallback = ( + error: any, + transport?: Transport, + establishedConnections?: number, +) => void; +/** + * @inline + */ +type MessageCallback = (message: Message) => void; +/** + * @inline + */ +type CloseCallback = (event: CloseEvent) => void; +/** + * @inline + */ +type LoggerFunction = (kind: string, msg: string, data?: any) => void; +/** + * @inline + */ +type ReconnectFunction = (tries: number) => number; +/** + * @inline + */ +type EncodeFunction = ( + payload: any, + callback: (encoded: string | ArrayBuffer) => void, +) => void; +/** + * @inline + */ +type DecodeFunction = ( + payload: string | ArrayBuffer, + callback: (decoded: Message) => void, +) => void; +/** + * @inline + */ +type ParamsFunction = () => Record; +/** + * @inline + */ +type PingCallback = (rtt: number) => void; + +/** + * Storage interface + * @inline + */ +interface Storage { + getItem(key: string): string | null; + setItem(key: string, value: string): void; + removeItem(key: string): void; +} + +// Socket options interface +export interface SocketOptions { + /** + * The Websocket Transport, for example WebSocket or Phoenix.LongPoll. + * + * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined. + * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`. + */ + transport?: Transport; + /** + * The millisecond time to attempt the primary transport + * before falling back to the LongPoll transport. Disabled by default. + */ + longPollFallbackMs?: number; + /** + * When true, enables debug logging. Default false. + */ + debug?: boolean; + /** + * The function to encode outgoing messages. + * + * Defaults to JSON encoder. + */ + encode?: EncodeFunction; + /** + * The function to decode incoming messages. + * + * Defaults to JSON: + * + * ```javascript + * (payload, callback) => callback(JSON.parse(payload)) + * ``` + */ + decode?: DecodeFunction; + /** + * The default timeout in milliseconds to trigger push timeouts. + * + * Defaults to `10000` + */ + timeout?: number; + /** + * The millisec interval to send a heartbeat message. + * + * Defaults to `30000`. + */ + heartbeatIntervalMs?: number; + /** + * The optional function that returns the + * socket reconnect interval, in milliseconds. + * + * Defaults to stepped backoff of: + * + * ```javascript + * function(tries){ + * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000 + * } + * ``` + */ + reconnectAfterMs?: ReconnectFunction; + /** + * The optional function that returns the millisec + * rejoin interval for individual channels. + * + * ```javascript + * function(tries){ + * return [1000, 2000, 5000][tries - 1] || 10000 + * } + * ``` + */ + rejoinAfterMs?: ReconnectFunction; + /** + * The optional function for specialized logging, ie: + * + * ```javascript + * function(kind, msg, data) { + * console.log(`${kind}: ${msg}`, data) + * } + * ``` + */ + logger?: LoggerFunction; + /** + * The maximum timeout of a long poll AJAX request. + * + * Defaults to 20s (double the server long poll timer). + */ + longpollerTimeout?: number; + /** + * The optional params to pass when connecting. + */ + params?: Record | ParamsFunction; + /** + * The optional authentication token to be exposed on the server + * under the `:auth_token` connect_info key. + */ + authToken?: string; + /** + * The binary type to use for binary WebSocket frames. + * + * Defaults to "arraybuffer" + */ + binaryType?: string; + /** + * The serializer's protocol version to send on connect. + * + * Defaults to "2.0.0". + */ + vsn?: string; + /** + * An optional Storage compatible object + * Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is + * useful when Phoenix won't have access to `sessionStorage`. For example, This could + * happen if a site loads a cross-domain channel in an iframe. Example usage: + * + * class InMemoryStorage { + * constructor() { this.storage = {} } + * getItem(keyName) { return this.storage[keyName] || null } + * removeItem(keyName) { delete this.storage[keyName] } + * setItem(keyName, keyValue) { this.storage[keyName] = keyValue } + * } + */ + sessionStorage?: Storage; +} + +// State change callbacks structure +interface StateChangeCallbacks { + open: Array<[string, StateChangeCallback]>; + close: Array<[string, CloseCallback]>; + error: Array<[string, ErrorCallback]>; + message: Array<[string, MessageCallback]>; +} + +/** The Socket class. + * + * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim) + */ +export default class Socket { + private stateChangeCallbacks: StateChangeCallbacks; + private channels: Channel[]; + private sendBuffer: Array<() => void>; + private ref: number; + private transport: TransportClass; + private primaryPassedHealthCheck: boolean; + private longPollFallbackMs: number | null; + private fallbackTimer: TimerHandle | null; + private sessionStore: Storage | null; + private establishedConnections: number; + private defaultEncoder: EncodeFunction; + private defaultDecoder: DecodeFunction; + private closeWasClean: boolean; + private disconnecting: boolean; + private binaryType: string; + private connectClock: number; + private encode: EncodeFunction; + private decode: DecodeFunction; + private heartbeatIntervalMs: number; + private reconnectAfterMs: ReconnectFunction; + private logger: LoggerFunction | null; + private longpollerTimeout: number; + private params: ParamsFunction; + private endPoint: string; + private vsn: string; + private heartbeatTimeoutTimer: TimerHandle | null; + private heartbeatTimer: TimerHandle | null; + private pendingHeartbeatRef: string | null; + private reconnectTimer: Timer; + private authToken?: string; + private conn: TransportInstance | null; + private fallbackRef: string; + + /** + * @internal + * @private + */ + timeout: number; + + /** + * @internal + * @private + */ + rejoinAfterMs: ReconnectFunction; + + /** + * Initializes the socket. + * @param endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`, + * `"wss://example.com"` + * `"/socket"` (inherited host & protocol) + * @param opts - Optional configuration + */ + constructor(endPoint: string, opts: SocketOptions = {}) { + this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] }; + this.channels = []; + this.sendBuffer = []; + this.ref = 0; + this.timeout = opts.timeout || DEFAULT_TIMEOUT; + this.transport = opts.transport || global.WebSocket || LongPoll; + this.primaryPassedHealthCheck = false; + this.longPollFallbackMs = opts.longPollFallbackMs ?? null; + this.fallbackTimer = null; + this.sessionStore = + opts.sessionStorage || (global && global.sessionStorage); + this.establishedConnections = 0; + this.defaultEncoder = Serializer.encode.bind(Serializer); + this.defaultDecoder = Serializer.decode.bind(Serializer); + this.closeWasClean = false; + this.disconnecting = false; + this.binaryType = opts.binaryType || "arraybuffer"; + this.connectClock = 1; + this.conn = null; + + if (this.transport !== LongPoll) { + this.encode = opts.encode || this.defaultEncoder; + this.decode = opts.decode || this.defaultDecoder; + } else { + this.encode = this.defaultEncoder; + this.decode = this.defaultDecoder; + } + + let awaitingConnectionOnPageShow: number | null = null; + if (phxWindow && phxWindow.addEventListener) { + phxWindow.addEventListener("pagehide", (_e: Event) => { + if (this.conn) { + this.disconnect(); + awaitingConnectionOnPageShow = this.connectClock; + } + }); + phxWindow.addEventListener("pageshow", (_e: Event) => { + if (awaitingConnectionOnPageShow === this.connectClock) { + awaitingConnectionOnPageShow = null; + this.connect(); + } + }); + } + + this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000; + this.rejoinAfterMs = (tries: number) => { + if (opts.rejoinAfterMs) { + return opts.rejoinAfterMs(tries); + } else { + return [1000, 2000, 5000][tries - 1] || 10000; + } + }; + this.reconnectAfterMs = (tries: number) => { + if (opts.reconnectAfterMs) { + return opts.reconnectAfterMs(tries); + } else { + return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000; + } + }; + this.logger = opts.logger || null; + if (!this.logger && opts.debug) { + this.logger = (kind: string, msg: string, data?: any) => { + console.log(`${kind}: ${msg}`, data); + }; + } + this.longpollerTimeout = opts.longpollerTimeout || 20000; + this.params = closure(opts.params || {}) as ParamsFunction; + this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`; + this.vsn = opts.vsn || DEFAULT_VSN; + this.heartbeatTimeoutTimer = null; + this.heartbeatTimer = null; + this.pendingHeartbeatRef = null; + this.reconnectTimer = new Timer(() => { + this.teardown(() => this.connect()); + }, this.reconnectAfterMs); + this.authToken = opts.authToken; + } + + /** + * Returns the LongPoll transport reference + */ + getLongPollTransport(): TransportClass { + return LongPoll; + } + + /** + * Disconnects and replaces the active transport + * + * @param newTransport - The new transport class to instantiate + * + */ + replaceTransport(newTransport: Transport): void { + this.connectClock++; + this.closeWasClean = true; + clearTimeout(this.fallbackTimer!); + this.reconnectTimer.reset(); + if (this.conn) { + this.conn.close(); + this.conn = null; + } + this.transport = newTransport; + } + + /** + * Returns the socket protocol + * + * @returns {string} + */ + protocol(): string { + return location.protocol.match(/^https/) ? "wss" : "ws"; + } + + /** + * The fully qualified socket url + * + * @returns {string} + */ + endPointURL(): string { + const uri = Ajax.appendParams( + Ajax.appendParams(this.endPoint, this.params()), + { vsn: this.vsn }, + ); + if (uri.charAt(0) !== "/") { + return uri; + } + if (uri.charAt(1) === "/") { + return `${this.protocol()}:${uri}`; + } + + return `${this.protocol()}://${location.host}${uri}`; + } + + /** + * Disconnects the socket + * + * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes. + * + * @param callback - Callback which is called after socket is disconnected. + * @param code - A status code for disconnection. + * @param reason - A textual description of the reason to disconnect. + */ + disconnect(callback?: () => void, code?: number, reason?: string): void { + this.connectClock++; + this.disconnecting = true; + this.closeWasClean = true; + clearTimeout(this.fallbackTimer!); + this.reconnectTimer.reset(); + this.teardown( + () => { + this.disconnecting = false; + callback && callback(); + }, + code, + reason, + ); + } + + /** + * + * @param params - The params to send when connecting, for example `{user_id: userToken}` + * + * Passing params to connect is deprecated; pass them in the Socket constructor instead: + * `new Socket("/socket", {params: {user_id: userToken}})`. + */ + connect(params?: Record): void { + if (params) { + console && + console.log( + "passing params to connect is deprecated. Instead pass :params to the Socket constructor", + ); + this.params = closure(params) as ParamsFunction; + } + if (this.conn && !this.disconnecting) { + return; + } + if (this.longPollFallbackMs && this.transport !== LongPoll) { + this.connectWithFallback(LongPoll, this.longPollFallbackMs); + } else { + this.transportConnect(); + } + } + + /** + * Logs the message. Override `this.logger` for specialized logging. noops by default + * @param kind + * @param msg + * @param data + */ + log(kind: string, msg: string, data?: any): void { + this.logger && this.logger(kind, msg, data); + } + + /** + * Returns true if a logger has been set on this socket. + */ + hasLogger(): boolean { + return this.logger !== null; + } + + /** + * Registers callbacks for connection open events + * + * @example socket.onOpen(function(){ console.info("the socket was opened") }) + * + * @param callback + */ + onOpen(callback: StateChangeCallback): string { + const ref = this.makeRef(); + this.stateChangeCallbacks.open.push([ref, callback]); + return ref; + } + + /** + * Registers callbacks for connection close events + * @param callback + */ + onClose(callback: CloseCallback): string { + const ref = this.makeRef(); + this.stateChangeCallbacks.close.push([ref, callback]); + return ref; + } + + /** + * Registers callbacks for connection error events + * + * @example socket.onError(function(error){ alert("An error occurred") }) + * + * @param callback + */ + onError(callback: ErrorCallback): string { + const ref = this.makeRef(); + this.stateChangeCallbacks.error.push([ref, callback]); + return ref; + } + + /** + * Registers callbacks for connection message events + * @param callback + */ + onMessage(callback: MessageCallback): string { + const ref = this.makeRef(); + this.stateChangeCallbacks.message.push([ref, callback]); + return ref; + } + + /** + * Pings the server and invokes the callback with the RTT in milliseconds + * @param callback + * + * Returns true if the ping was pushed or false if unable to be pushed. + */ + ping(callback: PingCallback): boolean { + if (!this.isConnected()) { + return false; + } + const ref = this.makeRef(); + const startTime = Date.now(); + this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: ref }); + const onMsgRef = this.onMessage((msg) => { + if (msg.ref === ref) { + this.off([onMsgRef]); + callback(Date.now() - startTime); + } + }); + return true; + } + + /** + * @internal + * @private + */ + transportConnect(): void { + this.connectClock++; + this.closeWasClean = false; + let protocols: string[] | undefined = undefined; + // Sec-WebSocket-Protocol based token + // (longpoll uses Authorization header instead) + if (this.authToken) { + protocols = [ + "phoenix", + `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`, + ]; + } + this.conn = new this.transport(this.endPointURL(), protocols); + this.conn.binaryType = this.binaryType; + this.conn.timeout = this.longpollerTimeout; + this.conn.onopen = () => this.onConnOpen(); + this.conn.onerror = (error) => this.onConnError(error); + this.conn.onmessage = (event) => this.onConnMessage(event); + this.conn.onclose = (event) => this.onConnClose(event); + } + + private getSession(key: string): string | null { + return this.sessionStore && this.sessionStore.getItem(key); + } + + private storeSession(key: string, val: string): void { + this.sessionStore && this.sessionStore.setItem(key, val); + } + + private connectWithFallback( + fallbackTransport: Transport, + fallbackThreshold: number = 2500, + ): void { + clearTimeout(this.fallbackTimer!); + let established = false; + let primaryTransport = true; + let openRef: string; + const fallback = (reason: any) => { + this.log( + "transport", + `falling back to ${fallbackTransport.name}...`, + reason, + ); + this.off([openRef, errorRef]); + primaryTransport = false; + this.replaceTransport(fallbackTransport); + this.transportConnect(); + }; + if (this.getSession(`phx:fallback:${fallbackTransport.name}`)) { + return fallback("memorized"); + } + + this.fallbackTimer = setTimeout(fallback, fallbackThreshold); + + const errorRef = this.onError((reason) => { + this.log("transport", "error", reason); + if (primaryTransport && !established) { + clearTimeout(this.fallbackTimer!); + fallback(reason); + } + }); + if (this.fallbackRef) { + this.off([this.fallbackRef]); + } + this.fallbackRef = this.onOpen(() => { + established = true; + if (!primaryTransport) { + // only memorize LP if we never connected to primary + if (!this.primaryPassedHealthCheck) { + this.storeSession(`phx:fallback:${fallbackTransport.name}`, "true"); + } + return this.log( + "transport", + `established ${fallbackTransport.name} fallback`, + ); + } + // if we've established primary, give the fallback a new period to attempt ping + clearTimeout(this.fallbackTimer!); + this.fallbackTimer = setTimeout(fallback, fallbackThreshold); + this.ping((rtt) => { + this.log("transport", "connected to primary after", rtt); + this.primaryPassedHealthCheck = true; + clearTimeout(this.fallbackTimer!); + }); + }); + this.transportConnect(); + } + + private clearHeartbeats(): void { + clearTimeout(this.heartbeatTimer!); + clearTimeout(this.heartbeatTimeoutTimer!); + } + + /** + * @internal + * @private + */ + onConnOpen(): void { + if (this.hasLogger()) + this.log( + "transport", + `${this.transport.name} connected to ${this.endPointURL()}`, + ); + this.closeWasClean = false; + this.disconnecting = false; + this.establishedConnections++; + this.flushSendBuffer(); + this.reconnectTimer.reset(); + this.resetHeartbeat(); + this.stateChangeCallbacks.open.forEach(([, callback]) => callback()); + } + + private heartbeatTimeout(): void { + if (this.pendingHeartbeatRef) { + this.pendingHeartbeatRef = null; + if (this.hasLogger()) { + this.log( + "transport", + "heartbeat timeout. Attempting to re-establish connection", + ); + } + this.triggerChanError(); + this.closeWasClean = false; + this.teardown( + () => this.reconnectTimer.scheduleTimeout(), + WS_CLOSE_NORMAL, + "heartbeat timeout", + ); + } + } + + private resetHeartbeat(): void { + if (this.conn && this.conn.skipHeartbeat) { + return; + } + this.pendingHeartbeatRef = null; + this.clearHeartbeats(); + this.heartbeatTimer = setTimeout( + () => this.sendHeartbeat(), + this.heartbeatIntervalMs, + ); + } + + private teardown( + callback?: () => void, + code?: number, + reason?: string, + ): void { + if (!this.conn) { + return callback && callback(); + } + const connectClock = this.connectClock; + + this.waitForBufferDone(() => { + if (connectClock !== this.connectClock) { + return; + } + if (this.conn) { + if (code) { + this.conn.close(code, reason || ""); + } else { + this.conn.close(); + } + } + + this.waitForSocketClosed(() => { + if (connectClock !== this.connectClock) { + return; + } + if (this.conn) { + this.conn.onopen = function () {}; // noop + this.conn.onerror = function () {}; // noop + this.conn.onmessage = function () {}; // noop + this.conn.onclose = function () {}; // noop + this.conn = null; + } + + callback && callback(); + }); + }); + } + + private waitForBufferDone(callback: () => void, tries: number = 1): void { + if (tries === 5 || !this.conn || !this.conn.bufferedAmount) { + callback(); + return; + } + + setTimeout(() => { + this.waitForBufferDone(callback, tries + 1); + }, 150 * tries); + } + + private waitForSocketClosed(callback: () => void, tries: number = 1): void { + if ( + tries === 5 || + !this.conn || + this.conn.readyState === SOCKET_STATES.closed + ) { + callback(); + return; + } + + setTimeout(() => { + this.waitForSocketClosed(callback, tries + 1); + }, 150 * tries); + } + + /** + * @internal + * @private + */ + onConnClose(event: CloseEvent): void { + const closeCode = event && event.code; + if (this.hasLogger()) this.log("transport", "close", event); + this.triggerChanError(); + this.clearHeartbeats(); + if (!this.closeWasClean && closeCode !== 1000) { + this.reconnectTimer.scheduleTimeout(); + } + this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event)); + } + + /** + * @internal + * @private + */ + onConnError(error: Event): void { + if (this.hasLogger()) this.log("transport", "error", error); + const transportBefore = this.transport; + const establishedBefore = this.establishedConnections; + this.stateChangeCallbacks.error.forEach(([, callback]) => { + callback(error, transportBefore, establishedBefore); + }); + if (transportBefore === this.transport || establishedBefore > 0) { + this.triggerChanError(); + } + } + + private triggerChanError(): void { + this.channels.forEach((channel) => { + if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) { + channel.trigger(CHANNEL_EVENTS.error, {}); + } + }); + } + + /** + * @returns {string} + */ + connectionState(): string { + switch (this.conn && this.conn.readyState) { + case SOCKET_STATES.connecting: + return "connecting"; + case SOCKET_STATES.open: + return "open"; + case SOCKET_STATES.closing: + return "closing"; + default: + return "closed"; + } + } + + /** + * @returns {boolean} + */ + isConnected(): boolean { + return this.connectionState() === "open"; + } + + /** + * @internal + * @private + */ + remove(channel: Channel): void { + this.off(channel.stateChangeRefs); + this.channels = this.channels.filter((c) => c !== channel); + } + + /** + * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations. + * + * @param refs - list of refs returned by calls to + * `onOpen`, `onClose`, `onError,` and `onMessage` + */ + off(refs: string[]): void { + const filter = (callbacks: Array<[string, any]>) => + callbacks.filter(([ref]) => refs.indexOf(ref) === -1); + + this.stateChangeCallbacks = { + open: filter(this.stateChangeCallbacks.open), + close: filter(this.stateChangeCallbacks.close), + error: filter(this.stateChangeCallbacks.error), + message: filter(this.stateChangeCallbacks.message), + }; + } + + /** + * Initiates a new channel for the given topic + * + * @param topic + * @param chanParams - Parameters for the channel + */ + channel(topic: string, chanParams: Record = {}): Channel { + const chan = new Channel(topic, chanParams, this); + this.channels.push(chan); + return chan; + } + + /** + * @param data + */ + push(data: Message): void { + if (this.hasLogger()) { + const { topic, event, payload, ref, join_ref } = data; + this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload); + } + + if (this.isConnected()) { + this.encode(data, (result) => this.conn!.send(result)); + } else { + this.sendBuffer.push(() => + this.encode(data, (result) => this.conn!.send(result)), + ); + } + } + + /** + * Return the next message ref, accounting for overflows + * @returns {string} + */ + makeRef(): string { + const newRef = this.ref + 1; + if (newRef === this.ref) { + this.ref = 0; + } else { + this.ref = newRef; + } + + return this.ref.toString(); + } + + /** + * @internal + * @private + */ + sendHeartbeat(): void { + if (this.pendingHeartbeatRef && !this.isConnected()) { + return; + } + this.pendingHeartbeatRef = this.makeRef(); + this.push({ + topic: "phoenix", + event: "heartbeat", + payload: {}, + ref: this.pendingHeartbeatRef, + }); + this.heartbeatTimeoutTimer = setTimeout( + () => this.heartbeatTimeout(), + this.heartbeatIntervalMs, + ); + } + + /** + * @internal + * @private + */ + flushSendBuffer(): void { + if (this.isConnected() && this.sendBuffer.length > 0) { + this.sendBuffer.forEach((callback) => callback()); + this.sendBuffer = []; + } + } + + /** + * @internal + * @private + */ + onConnMessage(rawMessage: MessageEvent): void { + this.decode(rawMessage.data, (msg) => { + const { topic, event, payload, ref, join_ref } = msg; + if (ref && ref === this.pendingHeartbeatRef) { + this.clearHeartbeats(); + this.pendingHeartbeatRef = null; + this.heartbeatTimer = setTimeout( + () => this.sendHeartbeat(), + this.heartbeatIntervalMs, + ); + } + + if (this.hasLogger()) + this.log( + "receive", + `${payload.status || ""} ${topic} ${event} ${(ref && "(" + ref + ")") || ""}`, + payload, + ); + + for (let i = 0; i < this.channels.length; i++) { + const channel = this.channels[i]; + if (!channel.isMember(topic, event, payload, join_ref)) { + continue; + } + channel.trigger(event, payload, ref, join_ref); + } + + for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) { + const [, callback] = this.stateChangeCallbacks.message[i]; + callback(msg); + } + }); + } + + /** + * @internal + * @private + */ + leaveOpenTopic(topic: string): void { + const dupChannel = this.channels.find( + (c) => c.topic === topic && (c.isJoined() || c.isJoining()), + ); + if (dupChannel) { + if (this.hasLogger()) + this.log("transport", `leaving duplicate topic "${topic}"`); + dupChannel.leave(); + } + } +} diff --git a/assets/js/phoenix/timer.js b/assets/js/phoenix/timer.js deleted file mode 100644 index 5784ce5042..0000000000 --- a/assets/js/phoenix/timer.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * - * Creates a timer that accepts a `timerCalc` function to perform - * calculated timeout retries, such as exponential backoff. - * - * @example - * let reconnectTimer = new Timer(() => this.connect(), function(tries){ - * return [1000, 5000, 10000][tries - 1] || 10000 - * }) - * reconnectTimer.scheduleTimeout() // fires after 1000 - * reconnectTimer.scheduleTimeout() // fires after 5000 - * reconnectTimer.reset() - * reconnectTimer.scheduleTimeout() // fires after 1000 - * - * @param {Function} callback - * @param {Function} timerCalc - */ -export default class Timer { - constructor(callback, timerCalc){ - this.callback = callback - this.timerCalc = timerCalc - this.timer = null - this.tries = 0 - } - - reset(){ - this.tries = 0 - clearTimeout(this.timer) - } - - /** - * Cancels any previous scheduleTimeout and schedules callback - */ - scheduleTimeout(){ - clearTimeout(this.timer) - - this.timer = setTimeout(() => { - this.tries = this.tries + 1 - this.callback() - }, this.timerCalc(this.tries + 1)) - } -} diff --git a/assets/js/phoenix/timer.ts b/assets/js/phoenix/timer.ts new file mode 100644 index 0000000000..b5187eeb56 --- /dev/null +++ b/assets/js/phoenix/timer.ts @@ -0,0 +1,51 @@ +/** + * + * Creates a timer that accepts a `timerCalc` function to perform + * calculated timeout retries, such as exponential backoff. + * + * @example + * let reconnectTimer = new Timer(() => this.connect(), function(tries){ + * return [1000, 5000, 10000][tries - 1] || 10000 + * }) + * reconnectTimer.scheduleTimeout() // fires after 1000 + * reconnectTimer.scheduleTimeout() // fires after 5000 + * reconnectTimer.reset() + * reconnectTimer.scheduleTimeout() // fires after 1000 + */ +export default class Timer { + private callback: () => void; + private timerCalc: (tries: number) => number; + private timer: number | null; + private tries: number; + + constructor(callback: () => void, timerCalc: (tries: number) => number) { + this.callback = callback; + this.timerCalc = timerCalc; + this.timer = null; + this.tries = 0; + } + + reset(): void { + this.tries = 0; + if (this.timer !== null) { + clearTimeout(this.timer); + } + } + + /** + * Cancels any previous scheduleTimeout and schedules callback + */ + scheduleTimeout(): void { + if (this.timer !== null) { + clearTimeout(this.timer); + } + + this.timer = setTimeout( + () => { + this.tries = this.tries + 1; + this.callback(); + }, + this.timerCalc(this.tries + 1), + ) as any; + } +} diff --git a/assets/js/phoenix/utils.js b/assets/js/phoenix/utils.js deleted file mode 100644 index b3a701a828..0000000000 --- a/assets/js/phoenix/utils.js +++ /dev/null @@ -1,9 +0,0 @@ -// wraps value in closure or returns closure -export let closure = (value) => { - if(typeof value === "function"){ - return value - } else { - let closure = function (){ return value } - return closure - } -} diff --git a/assets/js/phoenix/utils.ts b/assets/js/phoenix/utils.ts new file mode 100644 index 0000000000..0f05742922 --- /dev/null +++ b/assets/js/phoenix/utils.ts @@ -0,0 +1,8 @@ +// wraps value in closure or returns closure +export function closure(value: T | (() => T)): () => T { + if (typeof value === "function") { + return value as () => T; + } else { + return () => value; + } +} diff --git a/assets/test/channel_test.js b/assets/test/channel_test.js deleted file mode 100644 index 1ad88d291e..0000000000 --- a/assets/test/channel_test.js +++ /dev/null @@ -1,1051 +0,0 @@ -import {jest} from "@jest/globals" -import {Channel, Socket} from "../js/phoenix" - -let channel, socket - -const defaultRef = 1 -const defaultTimeout = 10000 - -class WSMock { - constructor(url, protocols){ - this.url = url - this.protocols = protocols - } - close(){} - send(){} -} - -describe("with transport", function (){ - beforeAll(function (){ - global.WebSocket = WSMock - }) - - afterAll(function (){ - global.WebSocket = null - }) - - describe("constructor", function (){ - beforeEach(function (){ - socket = new Socket("/", {timeout: 1234}) - }) - - it("sets defaults", function (){ - channel = new Channel("topic", {one: "two"}, socket) - - expect(channel.state).toBe("closed") - expect(channel.topic).toBe("topic") - expect(channel.params()).toEqual({one: "two"}) - expect(channel.socket).toBe(socket) - expect(channel.timeout).toBe(1234) - expect(channel.joinedOnce).toBe(false) - expect(channel.joinPush).toBeTruthy() - expect(channel.pushBuffer).toEqual([]) - }) - - it("sets up joinPush object with literal params", function (){ - channel = new Channel("topic", {one: "two"}, socket) - const joinPush = channel.joinPush - - expect(joinPush.channel).toBe(channel) - expect(joinPush.payload()).toEqual({one: "two"}) - expect(joinPush.event).toBe("phx_join") - expect(joinPush.timeout).toBe(1234) - }) - - it("sets up joinPush object with closure params", function (){ - channel = new Channel("topic", () => ({one: "two"}), socket) - const joinPush = channel.joinPush - - expect(joinPush.channel).toBe(channel) - expect(joinPush.payload()).toEqual({one: "two"}) - expect(joinPush.event).toBe("phx_join") - expect(joinPush.timeout).toBe(1234) - }) - - it("sets subprotocols when authToken is provided", function (){ - const authToken = "1234" - const socket = new Socket("/socket", {authToken}) - - socket.connect() - expect(socket.conn.protocols).toEqual(["phoenix", "base64url.bearer.phx.MTIzNA"]) - }) - }) - - describe("updating join params", function (){ - it("can update the join params", function (){ - let counter = 0 - let params = () => ({value: counter}) - socket = {timeout: 1234, onError: function (){}, onOpen: function (){}} - - channel = new Channel("topic", params, socket) - const joinPush = channel.joinPush - - expect(joinPush.channel).toBe(channel) - expect(joinPush.payload()).toEqual({value: 0}) - expect(joinPush.event).toBe("phx_join") - expect(joinPush.timeout).toBe(1234) - - counter++ - - expect(joinPush.channel).toBe(channel) - expect(joinPush.payload()).toEqual({value: 1}) - expect(channel.params()).toEqual({value: 1}) - expect(joinPush.event).toBe("phx_join") - expect(joinPush.timeout).toBe(1234) - }) - }) - - describe("join", function (){ - beforeEach(function (){ - socket = new Socket("/socket", {timeout: defaultTimeout}) - - channel = socket.channel("topic", {one: "two"}) - }) - - it("sets state to joining", function (){ - channel.join() - - expect(channel.state).toBe("joining") - }) - - it("sets joinedOnce to true", function (){ - expect(channel.joinedOnce).toBe(false) - - channel.join() - - expect(channel.joinedOnce).toBe(true) - }) - - it("throws if attempting to join multiple times", function (){ - channel.join() - - expect(() => channel.join()).toThrow(/^tried to join multiple times/) - }) - - it("triggers socket push with channel params", function (){ - jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef) - const spy = jest.spyOn(socket, "push") - - channel.join() - - expect(spy).toHaveBeenCalledTimes(1) - expect(spy).toHaveBeenCalledWith({ - topic: "topic", - event: "phx_join", - payload: {one: "two"}, - ref: defaultRef, - join_ref: channel.joinRef(), - }) - }) - - it("can set timeout on joinPush", function (){ - const newTimeout = 2000 - const joinPush = channel.joinPush - - expect(joinPush.timeout).toBe(defaultTimeout) - - channel.join(newTimeout) - - expect(joinPush.timeout).toBe(newTimeout) - }) - - it("leaves existing duplicate topic on new join", function (done){ - channel.join().receive("ok", () => { - let newChannel = socket.channel("topic") - expect(channel.isJoined()).toBe(true) - newChannel.join() - expect(channel.isJoined()).toBe(false) - done() - }) - - channel.joinPush.trigger("ok", {}) - }) - - describe("timeout behavior", function (){ - let joinPush - - const helpers = { - receiveSocketOpen(){ - jest.spyOn(socket, "isConnected").mockReturnValue(true) - socket.onConnOpen() - }, - } - - beforeEach(function (){ - jest.useFakeTimers() - joinPush = channel.joinPush - }) - - afterEach(function (){ - jest.useRealTimers() - }) - - it("succeeds before timeout", function (){ - const spy = jest.spyOn(socket, "push") - const timeout = joinPush.timeout - - socket.connect() - helpers.receiveSocketOpen() - - channel.join() - expect(spy).toHaveBeenCalledTimes(1) - - expect(channel.timeout).toBe(10000) - jest.advanceTimersByTime(100) - - joinPush.trigger("ok", {}) - - expect(channel.state).toBe("joined") - - jest.advanceTimersByTime(timeout) - expect(spy).toHaveBeenCalledTimes(1) - }) - - it("retries with backoff after timeout", function (){ - const spy = jest.spyOn(socket, "push") - const timeoutSpy = jest.fn() - const timeout = joinPush.timeout - - socket.connect() - helpers.receiveSocketOpen() - - channel.join().receive("timeout", timeoutSpy) - - expect(spy).toHaveBeenCalledTimes(1) - expect(timeoutSpy).toHaveBeenCalledTimes(0) - - jest.advanceTimersByTime(timeout) - expect(spy).toHaveBeenCalledTimes(2) // leave pushed to server - expect(timeoutSpy).toHaveBeenCalledTimes(1) - - jest.advanceTimersByTime(timeout + 1000) - expect(spy).toHaveBeenCalledTimes(4) // leave + rejoin - expect(timeoutSpy).toHaveBeenCalledTimes(2) - - jest.advanceTimersByTime(10000) - joinPush.trigger("ok", {}) - expect(spy).toHaveBeenCalledTimes(6) - expect(channel.state).toBe("joined") - }) - - it("with socket and join delay", function (){ - const spy = jest.spyOn(socket, "push") - jest.useFakeTimers() - const joinPush = channel.joinPush - - channel.join() - expect(spy).toHaveBeenCalledTimes(1) - - // open socket after delay - jest.advanceTimersByTime(9000) - - expect(spy).toHaveBeenCalledTimes(1) - - // join request returns between timeouts - jest.advanceTimersByTime(1000) - socket.connect() - - expect(channel.state).toBe("errored") - - helpers.receiveSocketOpen() - joinPush.trigger("ok", {}) - - // join request succeeds after delay - jest.advanceTimersByTime(1000) - - expect(channel.state).toBe("joined") - - expect(spy).toHaveBeenCalledTimes(3) // leave pushed to server - }) - - it("with socket delay only", function (){ - jest.useFakeTimers() - const joinPush = channel.joinPush - - channel.join() - - expect(channel.state).toBe("joining") - - // connect socket after delay - jest.advanceTimersByTime(6000) - socket.connect() - - // open socket after delay - jest.advanceTimersByTime(5000) - helpers.receiveSocketOpen() - joinPush.trigger("ok", {}) - - joinPush.trigger("ok", {}) - expect(channel.state).toBe("joined") - }) - }) - }) - - describe("joinPush", function (){ - let joinPush - let response - - const helpers = { - receiveOk(){ - jest.advanceTimersByTime(joinPush.timeout / 2) // before timeout - return joinPush.channel.trigger("phx_reply", {status: "ok", response: response}, joinPush.ref, joinPush.ref) - // return joinPush.trigger("ok", response) - }, - - receiveTimeout(){ - jest.advanceTimersByTime(joinPush.timeout * 2) // after timeout - }, - - receiveError(){ - jest.advanceTimersByTime(joinPush.timeout / 2) // before timeout - return joinPush.trigger("error", response) - }, - - getBindings(event){ - return channel.bindings.filter(bind => bind.event === event) - }, - } - - beforeEach(function (){ - jest.useFakeTimers() - - socket = new Socket("/socket", {timeout: defaultTimeout}) - jest.spyOn(socket, "isConnected").mockReturnValue(true) - jest.spyOn(socket, "push").mockReturnValue(true) - - channel = socket.channel("topic", {one: "two"}) - joinPush = channel.joinPush - - channel.join() - }) - - afterEach(function (){ - jest.useRealTimers() - }) - - describe("receives 'ok'", function (){ - beforeEach(function (){ - response = {chan: "reply"} - }) - - it("sets channel state to joined", function (){ - expect(channel.state).not.toBe("joined") - - helpers.receiveOk() - - expect(channel.state).toBe("joined") - }) - - it("triggers receive('ok') callback after ok response", function (){ - const spyOk = jest.fn() - - joinPush.receive("ok", spyOk) - - helpers.receiveOk() - - expect(spyOk).toHaveBeenCalledTimes(1) - }) - - it("triggers receive('ok') callback if ok response already received", function (){ - const spyOk = jest.fn() - - helpers.receiveOk() - - joinPush.receive("ok", spyOk) - - expect(spyOk).toHaveBeenCalledTimes(1) - }) - - it("does not trigger other receive callbacks after ok response", function (){ - const spyError = jest.fn() - const spyTimeout = jest.fn() - - joinPush.receive("error", spyError).receive("timeout", spyTimeout) - - helpers.receiveOk() - jest.advanceTimersByTime(channel.timeout * 2) // attempt timeout - - expect(spyError).not.toHaveBeenCalled() - expect(spyTimeout).not.toHaveBeenCalled() - }) - - it("clears timeoutTimer", function (){ - expect(joinPush.timeoutTimer).toBeTruthy() - - helpers.receiveOk() - - expect(joinPush.timeoutTimer).toBeNull() - }) - - it("sets receivedResp", function (){ - expect(joinPush.receivedResp).toBeNull() - - helpers.receiveOk() - - expect(joinPush.receivedResp).toEqual({status: "ok", response}) - }) - - it("removes channel bindings", function (){ - let bindings = helpers.getBindings("chan_reply_3") - expect(bindings.length).toBe(1) - - helpers.receiveOk() - - bindings = helpers.getBindings("chan_reply_3") - expect(bindings.length).toBe(0) - }) - - it("resets channel rejoinTimer", function (){ - expect(channel.rejoinTimer).toBeTruthy() - - const spy = jest.spyOn(channel.rejoinTimer, "reset") - - helpers.receiveOk() - - expect(spy).toHaveBeenCalledTimes(1) - }) - - it("sends and empties channel's buffered pushEvents", function (done){ - const pushEvent = {send(){}} - const spy = jest.spyOn(pushEvent, "send") - - channel.pushBuffer.push(pushEvent) - - expect(channel.state).toBe("joining") - joinPush.receive("ok", () => { - expect(spy).toHaveBeenCalledTimes(1) - expect(channel.pushBuffer.length).toBe(0) - done() - }) - helpers.receiveOk() - }) - }) - - describe("receives 'timeout'", function (){ - it("sets channel state to errored", function (done){ - joinPush.receive("timeout", () => { - expect(channel.state).toBe("errored") - done() - }) - - helpers.receiveTimeout() - }) - - it("triggers receive('timeout') callback after ok response", function (){ - const spyTimeout = jest.fn() - - joinPush.receive("timeout", spyTimeout) - - helpers.receiveTimeout() - - expect(spyTimeout).toHaveBeenCalledTimes(1) - }) - - it("does not trigger other receive callbacks after timeout response", function (done){ - const spyOk = jest.fn() - const spyError = jest.fn() - jest.spyOn(channel.rejoinTimer, "scheduleTimeout").mockReturnValue(true) - - channel.test = true - joinPush.receive("ok", spyOk).receive("error", spyError).receive("timeout", () => { - expect(spyOk).not.toHaveBeenCalled() - expect(spyError).not.toHaveBeenCalled() - done() - }) - - helpers.receiveTimeout() - helpers.receiveOk() - }) - - it("schedules rejoinTimer timeout", function (){ - expect(channel.rejoinTimer).toBeTruthy() - - const spy = jest.spyOn(channel.rejoinTimer, "scheduleTimeout") - - helpers.receiveTimeout() - - expect(spy).toHaveBeenCalled() // TODO why called multiple times? - }) - }) - - describe("receives 'error'", function (){ - beforeEach(function (){ - response = {chan: "fail"} - }) - - it("triggers receive('error') callback after error response", function (){ - const spyError = jest.fn() - - expect(channel.state).toBe("joining") - joinPush.receive("error", spyError) - - helpers.receiveError() - joinPush.trigger("error", {}) - - expect(spyError).toHaveBeenCalledTimes(1) - }) - - it("triggers receive('error') callback if error response already received", function (){ - const spyError = jest.fn() - - helpers.receiveError() - - joinPush.receive("error", spyError) - - expect(spyError).toHaveBeenCalledTimes(1) - }) - - it("does not trigger other receive callbacks after error response", function (){ - const spyOk = jest.fn() - const spyError = jest.fn() - const spyTimeout = jest.fn() - - joinPush.receive("ok", spyOk).receive("error", () => { - spyError() - channel.leave() - }).receive("timeout", spyTimeout) - - helpers.receiveError() - jest.advanceTimersByTime(channel.timeout * 2) // attempt timeout - - expect(spyError).toHaveBeenCalledTimes(1) - expect(spyOk).not.toHaveBeenCalled() - expect(spyTimeout).not.toHaveBeenCalled() - }) - - it("clears timeoutTimer", function (){ - expect(joinPush.timeoutTimer).toBeTruthy() - - helpers.receiveError() - - expect(joinPush.timeoutTimer).toBeNull() - }) - - it("sets receivedResp with error trigger after binding", function (done){ - expect(joinPush.receivedResp).toBeNull() - - joinPush.receive("error", resp => { - expect(resp).toEqual(response) - done() - }) - - helpers.receiveError() - }) - - it("sets receivedResp with error trigger before binding", function (done){ - expect(joinPush.receivedResp).toBeNull() - - helpers.receiveError() - joinPush.receive("error", resp => { - expect(resp).toEqual(response) - done() - }) - }) - - it("does not set channel state to joined", function (){ - helpers.receiveError() - - expect(channel.state).toBe("errored") - }) - - it("does not trigger channel's buffered pushEvents", function (){ - const pushEvent = {send: () => {}} - const spy = jest.spyOn(pushEvent, "send") - - channel.pushBuffer.push(pushEvent) - - helpers.receiveError() - - expect(spy).not.toHaveBeenCalled() - expect(channel.pushBuffer.length).toBe(1) - }) - }) - }) - - describe("onError", function (){ - let joinPush - - beforeEach(function (){ - jest.useFakeTimers() - - socket = new Socket("/socket", {timeout: defaultTimeout}) - jest.spyOn(socket, "isConnected").mockReturnValue(true) - jest.spyOn(socket, "push").mockReturnValue(true) - - channel = socket.channel("topic", {one: "two"}) - - joinPush = channel.joinPush - - channel.join() - joinPush.trigger("ok", {}) - }) - - afterEach(function (){ - jest.useRealTimers() - }) - - it("sets state to 'errored'", function (){ - expect(channel.state).not.toBe("errored") - - channel.trigger("phx_error") - - expect(channel.state).toBe("errored") - }) - - it("does not trigger redundant errors during backoff", function (){ - const spy = jest.spyOn(joinPush, "send").mockImplementation(() => {}) - - expect(spy).toHaveBeenCalledTimes(0) - - channel.trigger("phx_error") - - jest.advanceTimersByTime(1000) - expect(spy).toHaveBeenCalledTimes(1) - - joinPush.trigger("error", {}) - - jest.advanceTimersByTime(10000) - expect(spy).toHaveBeenCalledTimes(1) - }) - - it("does not rejoin if channel leaving", function (){ - channel.state = "leaving" - - const spy = jest.spyOn(joinPush, "send") - - socket.onConnError({}) - - jest.advanceTimersByTime(1000) - expect(spy).toHaveBeenCalledTimes(0) - - jest.advanceTimersByTime(2000) - expect(spy).toHaveBeenCalledTimes(0) - - expect(channel.state).toBe("leaving") - }) - - it("does not rejoin if channel closed", function (){ - channel.state = "closed" - - const spy = jest.spyOn(joinPush, "send") - - socket.onConnError({}) - - jest.advanceTimersByTime(1000) - expect(spy).toHaveBeenCalledTimes(0) - - jest.advanceTimersByTime(2000) - expect(spy).toHaveBeenCalledTimes(0) - - expect(channel.state).toBe("closed") - }) - - it("triggers additional callbacks after join", function (){ - const spy = jest.fn() - channel.onError(spy) - joinPush.trigger("ok", {}) - - expect(channel.state).toBe("joined") - expect(spy).toHaveBeenCalledTimes(0) - - channel.trigger("phx_error") - - expect(spy).toHaveBeenCalledTimes(1) - }) - }) - - describe("onClose", function (){ - let joinPush - - beforeEach(function (){ - jest.useFakeTimers() - - socket = new Socket("/socket", {timeout: defaultTimeout}) - jest.spyOn(socket, "isConnected").mockReturnValue(true) - jest.spyOn(socket, "push").mockReturnValue(true) - - channel = socket.channel("topic", {one: "two"}) - - joinPush = channel.joinPush - - channel.join() - }) - - afterEach(function (){ - jest.useRealTimers() - }) - - it("sets state to 'closed'", function (){ - expect(channel.state).not.toBe("closed") - - channel.trigger("phx_close") - - expect(channel.state).toBe("closed") - }) - - it("does not rejoin", function (){ - const spy = jest.spyOn(joinPush, "send") - - channel.trigger("phx_close") - - jest.advanceTimersByTime(1000) - expect(spy).toHaveBeenCalledTimes(0) - - jest.advanceTimersByTime(2000) - expect(spy).toHaveBeenCalledTimes(0) - }) - - it("triggers additional callbacks", function (){ - const spy = jest.fn() - channel.onClose(spy) - - expect(spy).toHaveBeenCalledTimes(0) - - channel.trigger("phx_close") - - expect(spy).toHaveBeenCalledTimes(1) - }) - - it("removes channel from socket", function (){ - expect(socket.channels.length).toBe(1) - expect(socket.channels[0]).toBe(channel) - - channel.trigger("phx_close") - - expect(socket.channels.length).toBe(0) - }) - }) - - describe("onMessage", function (){ - it("returns payload by default", function (){ - socket = new Socket("/socket") - channel = socket.channel("topic", {one: "two"}) - jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef) - const payload = channel.onMessage("event", {one: "two"}, defaultRef) - - expect(payload).toEqual({one: "two"}) - }) - }) - - describe("canPush", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - - channel = socket.channel("topic", {one: "two"}) - }) - - it("returns true when socket connected and channel joined", function (){ - jest.spyOn(socket, "isConnected").mockReturnValue(true) - channel.state = "joined" - - expect(channel.canPush()).toBe(true) - }) - - it("otherwise returns false", function (){ - const isConnectedStub = jest.spyOn(socket, "isConnected") - - isConnectedStub.mockReturnValue(false) - channel.state = "joined" - - expect(channel.canPush()).toBe(false) - - isConnectedStub.mockReturnValue(true) - channel.state = "joining" - - expect(channel.canPush()).toBe(false) - - isConnectedStub.mockReturnValue(false) - channel.state = "joining" - - expect(channel.canPush()).toBe(false) - }) - }) - - describe("on", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef) - - channel = socket.channel("topic", {one: "two"}) - }) - - it("sets up callback for event", function (){ - const spy = jest.fn() - - channel.trigger("event", {}, defaultRef) - expect(spy).not.toHaveBeenCalled() - - channel.on("event", spy) - - channel.trigger("event", {}, defaultRef) - - expect(spy).toHaveBeenCalled() - }) - - it("other event callbacks are ignored", function (){ - const spy = jest.fn() - const ignoredSpy = jest.fn() - - channel.trigger("event", {}, defaultRef) - - expect(ignoredSpy).not.toHaveBeenCalled() - - channel.on("event", spy) - - channel.trigger("event", {}, defaultRef) - - expect(ignoredSpy).not.toHaveBeenCalled() - }) - - it("generates unique refs for callbacks", function (){ - const ref1 = channel.on("event1", () => 0) - const ref2 = channel.on("event2", () => 0) - expect(ref1 + 1).toBe(ref2) - }) - - it("calls all callbacks for event if they modified during event processing", function (){ - const spy = jest.fn() - - const ref = channel.on("event", () => { - channel.off("event", ref) - }) - channel.on("event", spy) - - channel.trigger("event", {}, defaultRef) - - expect(spy).toHaveBeenCalled() - }) - }) - - describe("off", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef) - - channel = socket.channel("topic", {one: "two"}) - }) - - it("removes all callbacks for event", function (){ - const spy1 = jest.fn() - const spy2 = jest.fn() - const spy3 = jest.fn() - - channel.on("event", spy1) - channel.on("event", spy2) - channel.on("other", spy3) - - channel.off("event") - - channel.trigger("event", {}, defaultRef) - channel.trigger("other", {}, defaultRef) - - expect(spy1).not.toHaveBeenCalled() - expect(spy2).not.toHaveBeenCalled() - expect(spy3).toHaveBeenCalled() - }) - - it("removes callback by its ref", function (){ - const spy1 = jest.fn() - const spy2 = jest.fn() - - const ref1 = channel.on("event", spy1) - const _ref2 = channel.on("event", spy2) - - channel.off("event", ref1) - channel.trigger("event", {}, defaultRef) - - expect(spy1).not.toHaveBeenCalled() - expect(spy2).toHaveBeenCalled() - }) - }) - - describe("push", function (){ - let joinPush - let socketSpy - - const pushParams = (channel) => { - return { - topic: "topic", - event: "event", - payload: {foo: "bar"}, - join_ref: channel.joinRef(), - ref: defaultRef, - } - } - - beforeEach(function (){ - jest.useFakeTimers() - - socket = new Socket("/socket", {timeout: defaultTimeout}) - jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef) - jest.spyOn(socket, "isConnected").mockReturnValue(true) - socketSpy = jest.spyOn(socket, "push").mockReturnValue(undefined) - - channel = socket.channel("topic", {one: "two"}) - }) - - afterEach(function (){ - jest.useRealTimers() - }) - - it("sends push event when successfully joined", function (){ - channel.join().trigger("ok", {}) - channel.push("event", {foo: "bar"}) - - expect(socketSpy).toHaveBeenCalledWith(pushParams(channel)) - }) - - it("enqueues push event to be sent once join has succeeded", function (){ - joinPush = channel.join() - channel.push("event", {foo: "bar"}) - - expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel)) - - jest.advanceTimersByTime(channel.timeout / 2) - joinPush.trigger("ok", {}) - - expect(socketSpy).toHaveBeenCalledWith(pushParams(channel)) - }) - - it("does not push if channel join times out", function (){ - joinPush = channel.join() - channel.push("event", {foo: "bar"}) - - expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel)) - - jest.advanceTimersByTime(channel.timeout * 2) - joinPush.trigger("ok", {}) - - expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel)) - }) - - it("uses channel timeout by default", function (){ - const timeoutSpy = jest.fn() - channel.join().trigger("ok", {}) - - channel.push("event", {foo: "bar"}).receive("timeout", timeoutSpy) - - jest.advanceTimersByTime(channel.timeout / 2) - expect(timeoutSpy).not.toHaveBeenCalled() - - jest.advanceTimersByTime(channel.timeout) - expect(timeoutSpy).toHaveBeenCalled() - }) - - it("accepts timeout arg", function (){ - const timeoutSpy = jest.fn() - channel.join().trigger("ok", {}) - - channel.push("event", {foo: "bar"}, channel.timeout * 2).receive("timeout", timeoutSpy) - - jest.advanceTimersByTime(channel.timeout) - expect(timeoutSpy).not.toHaveBeenCalled() - - jest.advanceTimersByTime(channel.timeout * 2) - expect(timeoutSpy).toHaveBeenCalled() - }) - - it("does not time out after receiving 'ok'", function (){ - channel.join().trigger("ok", {}) - const timeoutSpy = jest.fn() - const push = channel.push("event", {foo: "bar"}) - push.receive("timeout", timeoutSpy) - - jest.advanceTimersByTime(push.timeout / 2) - expect(timeoutSpy).not.toHaveBeenCalled() - - push.trigger("ok", {}) - - jest.advanceTimersByTime(push.timeout) - expect(timeoutSpy).not.toHaveBeenCalled() - }) - - it("throws if channel has not been joined", function (){ - expect(() => channel.push("event", {})).toThrow(/^tried to push.*before joining/) - }) - }) - - describe("leave", function (){ - let socketSpy - - beforeEach(function (){ - jest.useFakeTimers() - - socket = new Socket("/socket", {timeout: defaultTimeout}) - jest.spyOn(socket, "isConnected").mockReturnValue(true) - socketSpy = jest.spyOn(socket, "push").mockReturnValue(undefined) - - channel = socket.channel("topic", {one: "two"}) - channel.join().trigger("ok", {}) - }) - - afterEach(function (){ - jest.useRealTimers() - }) - - it("unsubscribes from server events", function (){ - jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef) - const joinRef = channel.joinRef() - - channel.leave() - - expect(socketSpy).toHaveBeenCalledWith({ - topic: "topic", - event: "phx_leave", - payload: {}, - ref: defaultRef, - join_ref: joinRef, - }) - }) - - it("closes channel on 'ok' from server", function (){ - const anotherChannel = socket.channel("another", {three: "four"}) - expect(socket.channels.length).toBe(2) - - channel.leave().trigger("ok", {}) - - expect(socket.channels.length).toBe(1) - expect(socket.channels[0]).toBe(anotherChannel) - }) - - it("sets state to closed on 'ok' event", function (){ - expect(channel.state).not.toBe("closed") - - channel.leave().trigger("ok", {}) - - expect(channel.state).toBe("closed") - }) - - // TODO - the following tests are skipped until Channel.leave - // behavior can be fixed; currently, 'ok' is triggered immediately - // within Channel.leave so timeout callbacks are never reached - // - it.skip("sets state to leaving initially", function (){ - expect(channel.state).not.toBe("leaving") - - channel.leave() - - expect(channel.state).toBe("leaving") - }) - - it.skip("closes channel on 'timeout'", function (){ - channel.leave() - - jest.advanceTimersByTime(channel.timeout) - - expect(channel.state).toBe("closed") - }) - - it.skip("accepts timeout arg", function (){ - channel.leave(channel.timeout * 2) - - jest.advanceTimersByTime(channel.timeout) - - expect(channel.state).toBe("leaving") - - jest.advanceTimersByTime(channel.timeout * 2) - - expect(channel.state).toBe("closed") - }) - }) -}) diff --git a/assets/test/channel_test.ts b/assets/test/channel_test.ts new file mode 100644 index 0000000000..251cac4a9b --- /dev/null +++ b/assets/test/channel_test.ts @@ -0,0 +1,1087 @@ +import { jest } from "@jest/globals"; +import { Channel, Socket } from "../js/phoenix"; + +let channel: Channel, socket: Socket; + +const defaultRef = 1; +const defaultTimeout = 10000; + +interface WSMockInstance { + url: string; + protocols: string[] | undefined; + close(): void; + send(): void; +} + +class WSMock implements WSMockInstance { + public url: string; + public protocols: string[] | undefined; + + constructor(url: string, protocols?: string[]) { + this.url = url; + this.protocols = protocols; + } + + close(): void {} + send(): void {} +} + +describe("with transport", function () { + beforeAll(function () { + (global as any).WebSocket = WSMock; + }); + + afterAll(function () { + (global as any).WebSocket = null; + }); + + describe("constructor", function () { + beforeEach(function () { + socket = new Socket("/", { timeout: 1234 }); + }); + + it("sets defaults", function () { + channel = new Channel("topic", { one: "two" }, socket); + + expect(channel.state).toBe("closed"); + expect(channel.topic).toBe("topic"); + expect(channel.params()).toEqual({ one: "two" }); + expect(channel.socket).toBe(socket); + expect(channel.timeout).toBe(1234); + expect(channel["joinedOnce"]).toBe(false); + expect(channel["joinPush"]).toBeTruthy(); + expect(channel["pushBuffer"]).toEqual([]); + }); + + it("sets up joinPush object with literal params", function () { + channel = new Channel("topic", { one: "two" }, socket); + const joinPush = channel["joinPush"]; + + expect(joinPush["channel"]).toBe(channel); + expect(joinPush.payload()).toEqual({ one: "two" }); + expect(joinPush.event).toBe("phx_join"); + expect(joinPush.timeout).toBe(1234); + }); + + it("sets up joinPush object with closure params", function () { + channel = new Channel("topic", () => ({ one: "two" }), socket); + const joinPush = channel["joinPush"]; + + expect(joinPush.channel).toBe(channel); + expect(joinPush.payload()).toEqual({ one: "two" }); + expect(joinPush.event).toBe("phx_join"); + expect(joinPush.timeout).toBe(1234); + }); + + it("sets subprotocols when authToken is provided", function () { + const authToken = "1234"; + const socket = new Socket("/socket", { authToken }); + + socket.connect(); + expect((socket as any).conn.protocols).toEqual([ + "phoenix", + "base64url.bearer.phx.MTIzNA", + ]); + }); + }); + + describe("updating join params", function () { + it("can update the join params", function () { + let counter = 0; + const params = () => ({ value: counter }); + socket = { + timeout: 1234, + onError: function () {}, + onOpen: function () {}, + } as any; + + channel = new Channel("topic", params, socket); + const joinPush = channel["joinPush"]; + + expect(joinPush.channel).toBe(channel); + expect(joinPush.payload()).toEqual({ value: 0 }); + expect(joinPush.event).toBe("phx_join"); + expect(joinPush.timeout).toBe(1234); + + counter++; + + expect(joinPush.channel).toBe(channel); + expect(joinPush.payload()).toEqual({ value: 1 }); + expect(channel.params()).toEqual({ value: 1 }); + expect(joinPush.event).toBe("phx_join"); + expect(joinPush.timeout).toBe(1234); + }); + }); + + describe("join", function () { + beforeEach(function () { + socket = new Socket("/socket", { timeout: defaultTimeout }); + + channel = socket.channel("topic", { one: "two" }); + }); + + it("sets state to joining", function () { + channel.join(); + + expect(channel.state).toBe("joining"); + }); + + it("sets joinedOnce to true", function () { + expect(channel["joinedOnce"]).toBe(false); + + channel.join(); + + expect(channel["joinedOnce"]).toBe(true); + }); + + it("throws if attempting to join multiple times", function () { + channel.join(); + + expect(() => channel.join()).toThrow(/^tried to join multiple times/); + }); + + it("triggers socket push with channel params", function () { + jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef.toString()); + const spy = jest.spyOn(socket, "push"); + + channel.join(); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith({ + topic: "topic", + event: "phx_join", + payload: { one: "two" }, + ref: defaultRef.toString(), + join_ref: channel.joinRef(), + }); + }); + + it("can set timeout on joinPush", function () { + const newTimeout = 2000; + const joinPush = channel["joinPush"]; + + expect(joinPush.timeout).toBe(defaultTimeout); + + channel.join(newTimeout); + + expect(joinPush.timeout).toBe(newTimeout); + }); + + it("leaves existing duplicate topic on new join", function (done) { + channel.join().receive("ok", () => { + const newChannel = socket.channel("topic"); + expect(channel.isJoined()).toBe(true); + newChannel.join(); + expect(channel.isJoined()).toBe(false); + done(); + }); + + channel["joinPush"].trigger("ok", {}); + }); + + describe("timeout behavior", function () { + let joinPush: any; + + const helpers = { + receiveSocketOpen() { + jest.spyOn(socket, "isConnected").mockReturnValue(true); + (socket as any).onConnOpen(); + }, + }; + + beforeEach(function () { + jest.useFakeTimers(); + joinPush = channel["joinPush"]; + }); + + afterEach(function () { + jest.useRealTimers(); + }); + + it("succeeds before timeout", function () { + const spy = jest.spyOn(socket, "push"); + const timeout = joinPush.timeout; + + socket.connect(); + helpers.receiveSocketOpen(); + + channel.join(); + expect(spy).toHaveBeenCalledTimes(1); + + expect(channel.timeout).toBe(10000); + jest.advanceTimersByTime(100); + + joinPush.trigger("ok", {}); + + expect(channel.state).toBe("joined"); + + jest.advanceTimersByTime(timeout); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it("retries with backoff after timeout", function () { + const spy = jest.spyOn(socket, "push"); + const timeoutSpy = jest.fn(); + const timeout = joinPush.timeout; + + socket.connect(); + helpers.receiveSocketOpen(); + + channel.join().receive("timeout", timeoutSpy); + + expect(spy).toHaveBeenCalledTimes(1); + expect(timeoutSpy).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(timeout); + expect(spy).toHaveBeenCalledTimes(2); // leave pushed to server + expect(timeoutSpy).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(timeout + 1000); + expect(spy).toHaveBeenCalledTimes(4); // leave + rejoin + expect(timeoutSpy).toHaveBeenCalledTimes(2); + + jest.advanceTimersByTime(10000); + joinPush.trigger("ok", {}); + expect(spy).toHaveBeenCalledTimes(6); + expect(channel.state).toBe("joined"); + }); + + it("with socket and join delay", function () { + const spy = jest.spyOn(socket, "push"); + jest.useFakeTimers(); + const joinPush = channel["joinPush"]; + + channel.join(); + expect(spy).toHaveBeenCalledTimes(1); + + // open socket after delay + jest.advanceTimersByTime(9000); + + expect(spy).toHaveBeenCalledTimes(1); + + // join request returns between timeouts + jest.advanceTimersByTime(1000); + socket.connect(); + + expect(channel.state).toBe("errored"); + + helpers.receiveSocketOpen(); + joinPush.trigger("ok", {}); + + // join request succeeds after delay + jest.advanceTimersByTime(1000); + + expect(channel.state).toBe("joined"); + + expect(spy).toHaveBeenCalledTimes(3); // leave pushed to server + }); + + it("with socket delay only", function () { + jest.useFakeTimers(); + const joinPush = channel["joinPush"]; + + channel.join(); + + expect(channel.state).toBe("joining"); + + // connect socket after delay + jest.advanceTimersByTime(6000); + socket.connect(); + + // open socket after delay + jest.advanceTimersByTime(5000); + helpers.receiveSocketOpen(); + joinPush.trigger("ok", {}); + + joinPush.trigger("ok", {}); + expect(channel.state).toBe("joined"); + }); + }); + }); + + describe("joinPush", function () { + let joinPush: any; + let response: any; + + const helpers = { + receiveOk() { + jest.advanceTimersByTime(joinPush.timeout / 2); // before timeout + return joinPush.channel.trigger( + "phx_reply", + { status: "ok", response: response }, + joinPush.ref, + joinPush.ref, + ); + }, + + receiveTimeout() { + jest.advanceTimersByTime(joinPush.timeout * 2); // after timeout + }, + + receiveError() { + jest.advanceTimersByTime(joinPush.timeout / 2); // before timeout + return joinPush.trigger("error", response); + }, + + getBindings(event: string) { + return channel["bindings"].filter((bind: any) => bind.event === event); + }, + }; + + beforeEach(function () { + jest.useFakeTimers(); + + socket = new Socket("/socket", { timeout: defaultTimeout }); + jest.spyOn(socket, "isConnected").mockReturnValue(true); + jest.spyOn(socket, "push").mockReturnValue(undefined); + + channel = socket.channel("topic", { one: "two" }); + joinPush = channel["joinPush"]; + + channel.join(); + }); + + afterEach(function () { + jest.useRealTimers(); + }); + + describe("receives 'ok'", function () { + beforeEach(function () { + response = { chan: "reply" }; + }); + + it("sets channel state to joined", function () { + expect(channel.state).not.toBe("joined"); + + helpers.receiveOk(); + + expect(channel.state).toBe("joined"); + }); + + it("triggers receive('ok') callback after ok response", function () { + const spyOk = jest.fn(); + + joinPush.receive("ok", spyOk); + + helpers.receiveOk(); + + expect(spyOk).toHaveBeenCalledTimes(1); + }); + + it("triggers receive('ok') callback if ok response already received", function () { + const spyOk = jest.fn(); + + helpers.receiveOk(); + + joinPush.receive("ok", spyOk); + + expect(spyOk).toHaveBeenCalledTimes(1); + }); + + it("does not trigger other receive callbacks after ok response", function () { + const spyError = jest.fn(); + const spyTimeout = jest.fn(); + + joinPush.receive("error", spyError).receive("timeout", spyTimeout); + + helpers.receiveOk(); + jest.advanceTimersByTime(channel.timeout * 2); // attempt timeout + + expect(spyError).not.toHaveBeenCalled(); + expect(spyTimeout).not.toHaveBeenCalled(); + }); + + it("clears timeoutTimer", function () { + expect(joinPush.timeoutTimer).toBeTruthy(); + + helpers.receiveOk(); + + expect(joinPush.timeoutTimer).toBeNull(); + }); + + it("sets receivedResp", function () { + expect(joinPush.receivedResp).toBeNull(); + + helpers.receiveOk(); + + expect(joinPush.receivedResp).toEqual({ status: "ok", response }); + }); + + it("removes channel bindings", function () { + let bindings = helpers.getBindings("chan_reply_3"); + expect(bindings.length).toBe(1); + + helpers.receiveOk(); + + bindings = helpers.getBindings("chan_reply_3"); + expect(bindings.length).toBe(0); + }); + + it("resets channel rejoinTimer", function () { + expect(channel["rejoinTimer"]).toBeTruthy(); + + const spy = jest.spyOn(channel["rejoinTimer"], "reset"); + + helpers.receiveOk(); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it("sends and empties channel's buffered pushEvents", function (done) { + const pushEvent = { send() {} }; + const spy = jest.spyOn(pushEvent, "send"); + channel["pushBuffer"].push(pushEvent as any); + + expect(channel.state).toBe("joining"); + joinPush.receive("ok", () => { + expect(spy).toHaveBeenCalledTimes(1); + expect(channel["pushBuffer"].length).toBe(0); + done(); + }); + helpers.receiveOk(); + }); + }); + + describe("receives 'timeout'", function () { + it("sets channel state to errored", function (done) { + joinPush.receive("timeout", () => { + expect(channel.state).toBe("errored"); + done(); + }); + + helpers.receiveTimeout(); + }); + + it("triggers receive('timeout') callback after ok response", function () { + const spyTimeout = jest.fn(); + + joinPush.receive("timeout", spyTimeout); + + helpers.receiveTimeout(); + + expect(spyTimeout).toHaveBeenCalledTimes(1); + }); + + it("does not trigger other receive callbacks after timeout response", function (done) { + const spyOk = jest.fn(); + const spyError = jest.fn(); + jest + .spyOn(channel["rejoinTimer"], "scheduleTimeout") + .mockImplementation(() => {}); + + joinPush + .receive("ok", spyOk) + .receive("error", spyError) + .receive("timeout", () => { + expect(spyOk).not.toHaveBeenCalled(); + expect(spyError).not.toHaveBeenCalled(); + done(); + }); + + helpers.receiveTimeout(); + helpers.receiveOk(); + }); + + it("schedules rejoinTimer timeout", function () { + expect(channel["rejoinTimer"]).toBeTruthy(); + + const spy = jest.spyOn(channel["rejoinTimer"], "scheduleTimeout"); + + helpers.receiveTimeout(); + + expect(spy).toHaveBeenCalled(); // TODO why called multiple times? + }); + }); + + describe("receives 'error'", function () { + beforeEach(function () { + response = { chan: "fail" }; + }); + + it("triggers receive('error') callback after error response", function () { + const spyError = jest.fn(); + + expect(channel.state).toBe("joining"); + joinPush.receive("error", spyError); + + helpers.receiveError(); + joinPush.trigger("error", {}); + + expect(spyError).toHaveBeenCalledTimes(1); + }); + + it("triggers receive('error') callback if error response already received", function () { + const spyError = jest.fn(); + + helpers.receiveError(); + + joinPush.receive("error", spyError); + + expect(spyError).toHaveBeenCalledTimes(1); + }); + + it("does not trigger other receive callbacks after error response", function () { + const spyOk = jest.fn(); + const spyError = jest.fn(); + const spyTimeout = jest.fn(); + + joinPush + .receive("ok", spyOk) + .receive("error", () => { + spyError(); + channel.leave(); + }) + .receive("timeout", spyTimeout); + + helpers.receiveError(); + jest.advanceTimersByTime(channel.timeout * 2); // attempt timeout + + expect(spyError).toHaveBeenCalledTimes(1); + expect(spyOk).not.toHaveBeenCalled(); + expect(spyTimeout).not.toHaveBeenCalled(); + }); + + it("clears timeoutTimer", function () { + expect(joinPush.timeoutTimer).toBeTruthy(); + + helpers.receiveError(); + + expect(joinPush.timeoutTimer).toBeNull(); + }); + + it("sets receivedResp with error trigger after binding", function (done) { + expect(joinPush.receivedResp).toBeNull(); + + joinPush.receive("error", (resp: any) => { + expect(resp).toEqual(response); + done(); + }); + + helpers.receiveError(); + }); + + it("sets receivedResp with error trigger before binding", function (done) { + expect(joinPush.receivedResp).toBeNull(); + + helpers.receiveError(); + joinPush.receive("error", (resp: any) => { + expect(resp).toEqual(response); + done(); + }); + }); + + it("does not set channel state to joined", function () { + helpers.receiveError(); + + expect(channel.state).toBe("errored"); + }); + + it("does not trigger channel's buffered pushEvents", function () { + const pushEvent = { send: () => {} }; + const spy = jest.spyOn(pushEvent, "send"); + + channel["pushBuffer"].push(pushEvent as any); + + helpers.receiveError(); + + expect(spy).not.toHaveBeenCalled(); + expect(channel["pushBuffer"].length).toBe(1); + }); + }); + }); + + describe("onError", function () { + let joinPush: any; + + beforeEach(function () { + jest.useFakeTimers(); + + socket = new Socket("/socket", { timeout: defaultTimeout }); + jest.spyOn(socket, "isConnected").mockReturnValue(true); + jest.spyOn(socket, "push").mockReturnValue(undefined); + + channel = socket.channel("topic", { one: "two" }); + + joinPush = channel["joinPush"]; + + channel.join(); + joinPush.trigger("ok", {}); + }); + + afterEach(function () { + jest.useRealTimers(); + }); + + it("sets state to 'errored'", function () { + expect(channel.state).not.toBe("errored"); + + channel.trigger("phx_error", {}); + + expect(channel.state).toBe("errored"); + }); + + it("does not trigger redundant errors during backoff", function () { + const spy = jest.spyOn(joinPush, "send").mockImplementation(() => {}); + + expect(spy).toHaveBeenCalledTimes(0); + + channel.trigger("phx_error", {}); + + jest.advanceTimersByTime(1000); + expect(spy).toHaveBeenCalledTimes(1); + + joinPush.trigger("error", {}); + + jest.advanceTimersByTime(10000); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it("does not rejoin if channel leaving", function () { + (channel as any).state = "leaving"; + + const spy = jest.spyOn(joinPush, "send"); + + (socket as any).onConnError({}); + + jest.advanceTimersByTime(1000); + expect(spy).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(2000); + expect(spy).toHaveBeenCalledTimes(0); + + expect(channel.state).toBe("leaving"); + }); + + it("does not rejoin if channel closed", function () { + (channel as any).state = "closed"; + + const spy = jest.spyOn(joinPush, "send"); + + (socket as any).onConnError({}); + + jest.advanceTimersByTime(1000); + expect(spy).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(2000); + expect(spy).toHaveBeenCalledTimes(0); + + expect(channel.state).toBe("closed"); + }); + + it("triggers additional callbacks after join", function () { + const spy = jest.fn(); + channel.onError(spy); + joinPush.trigger("ok", {}); + + expect(channel.state).toBe("joined"); + expect(spy).toHaveBeenCalledTimes(0); + + channel.trigger("phx_error", {}); + + expect(spy).toHaveBeenCalledTimes(1); + }); + }); + + describe("onClose", function () { + let joinPush: any; + + beforeEach(function () { + jest.useFakeTimers(); + + socket = new Socket("/socket", { timeout: defaultTimeout }); + jest.spyOn(socket, "isConnected").mockReturnValue(true); + jest.spyOn(socket, "push").mockReturnValue(undefined); + + channel = socket.channel("topic", { one: "two" }); + + joinPush = channel["joinPush"]; + + channel.join(); + }); + + afterEach(function () { + jest.useRealTimers(); + }); + + it("sets state to 'closed'", function () { + expect(channel.state).not.toBe("closed"); + + channel.trigger("phx_close", {}); + + expect(channel.state).toBe("closed"); + }); + + it("does not rejoin", function () { + const spy = jest.spyOn(joinPush, "send"); + + channel.trigger("phx_close", {}); + + jest.advanceTimersByTime(1000); + expect(spy).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(2000); + expect(spy).toHaveBeenCalledTimes(0); + }); + + it("triggers additional callbacks", function () { + const spy = jest.fn(); + channel.onClose(spy); + + expect(spy).toHaveBeenCalledTimes(0); + + channel.trigger("phx_close", {}); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it("removes channel from socket", function () { + expect(socket["channels"].length).toBe(1); + expect(socket["channels"][0]).toBe(channel); + + channel.trigger("phx_close", {}); + + expect(socket["channels"].length).toBe(0); + }); + }); + + describe("onMessage", function () { + it("returns payload by default", function () { + socket = new Socket("/socket"); + channel = socket.channel("topic", { one: "two" }); + jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef.toString()); + const payload = channel.onMessage( + "event", + { one: "two" }, + defaultRef.toString(), + ); + + expect(payload).toEqual({ one: "two" }); + }); + }); + + describe("canPush", function () { + beforeEach(function () { + socket = new Socket("/socket"); + + channel = socket.channel("topic", { one: "two" }); + }); + + it("returns true when socket connected and channel joined", function () { + jest.spyOn(socket, "isConnected").mockReturnValue(true); + (channel as any).state = "joined"; + + expect(channel.canPush()).toBe(true); + }); + + it("otherwise returns false", function () { + const isConnectedStub = jest.spyOn(socket, "isConnected"); + + isConnectedStub.mockReturnValue(false); + (channel as any).state = "joined"; + + expect(channel.canPush()).toBe(false); + + isConnectedStub.mockReturnValue(true); + (channel as any).state = "joining"; + + expect(channel.canPush()).toBe(false); + + isConnectedStub.mockReturnValue(false); + (channel as any).state = "joining"; + + expect(channel.canPush()).toBe(false); + }); + }); + + describe("on", function () { + beforeEach(function () { + socket = new Socket("/socket"); + jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef.toString()); + + channel = socket.channel("topic", { one: "two" }); + }); + + it("sets up callback for event", function () { + const spy = jest.fn(); + + channel.trigger("event", {}, defaultRef.toString()); + expect(spy).not.toHaveBeenCalled(); + + channel.on("event", spy); + + channel.trigger("event", {}, defaultRef.toString()); + + expect(spy).toHaveBeenCalled(); + }); + + it("other event callbacks are ignored", function () { + const spy = jest.fn(); + const ignoredSpy = jest.fn(); + + channel.trigger("event", {}, defaultRef.toString()); + + expect(ignoredSpy).not.toHaveBeenCalled(); + + channel.on("event", spy); + + channel.trigger("event", {}, defaultRef.toString()); + + expect(ignoredSpy).not.toHaveBeenCalled(); + }); + + it("generates unique refs for callbacks", function () { + const ref1 = channel.on("event1", () => 0); + const ref2 = channel.on("event2", () => 0); + expect(ref1 + 1).toBe(ref2); + }); + + it("calls all callbacks for event if they modified during event processing", function () { + const spy = jest.fn(); + + const ref = channel.on("event", () => { + channel.off("event", ref); + }); + channel.on("event", spy); + + channel.trigger("event", {}, defaultRef.toString()); + + expect(spy).toHaveBeenCalled(); + }); + }); + + describe("off", function () { + beforeEach(function () { + socket = new Socket("/socket"); + jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef.toString()); + + channel = socket.channel("topic", { one: "two" }); + }); + + it("removes all callbacks for event", function () { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + const spy3 = jest.fn(); + + channel.on("event", spy1); + channel.on("event", spy2); + channel.on("other", spy3); + + channel.off("event"); + + channel.trigger("event", {}, defaultRef.toString()); + channel.trigger("other", {}, defaultRef.toString()); + + expect(spy1).not.toHaveBeenCalled(); + expect(spy2).not.toHaveBeenCalled(); + expect(spy3).toHaveBeenCalled(); + }); + + it("removes callback by its ref", function () { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + + const ref1 = channel.on("event", spy1); + channel.on("event", spy2); + + channel.off("event", ref1); + channel.trigger("event", {}, defaultRef.toString()); + + expect(spy1).not.toHaveBeenCalled(); + expect(spy2).toHaveBeenCalled(); + }); + }); + + describe("push", function () { + let joinPush: any; + let socketSpy: any; + + const pushParams = (channel: Channel) => { + return { + topic: "topic", + event: "event", + payload: { foo: "bar" }, + join_ref: channel.joinRef(), + ref: defaultRef.toString(), + }; + }; + + beforeEach(function () { + jest.useFakeTimers(); + + socket = new Socket("/socket", { timeout: defaultTimeout }); + jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef.toString()); + jest.spyOn(socket, "isConnected").mockReturnValue(true); + socketSpy = jest.spyOn(socket, "push").mockReturnValue(undefined); + + channel = socket.channel("topic", { one: "two" }); + }); + + afterEach(function () { + jest.useRealTimers(); + }); + + it("sends push event when successfully joined", function () { + channel.join().trigger("ok", {}); + channel.push("event", { foo: "bar" }); + + expect(socketSpy).toHaveBeenCalledWith(pushParams(channel)); + }); + + it("enqueues push event to be sent once join has succeeded", function () { + joinPush = channel.join(); + channel.push("event", { foo: "bar" }); + + expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel)); + + jest.advanceTimersByTime(channel.timeout / 2); + joinPush.trigger("ok", {}); + + expect(socketSpy).toHaveBeenCalledWith(pushParams(channel)); + }); + + it("does not push if channel join times out", function () { + joinPush = channel.join(); + channel.push("event", { foo: "bar" }); + + expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel)); + + jest.advanceTimersByTime(channel.timeout * 2); + joinPush.trigger("ok", {}); + + expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel)); + }); + + it("uses channel timeout by default", function () { + const timeoutSpy = jest.fn(); + channel.join().trigger("ok", {}); + + channel.push("event", { foo: "bar" }).receive("timeout", timeoutSpy); + + jest.advanceTimersByTime(channel.timeout / 2); + expect(timeoutSpy).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(channel.timeout); + expect(timeoutSpy).toHaveBeenCalled(); + }); + + it("accepts timeout arg", function () { + const timeoutSpy = jest.fn(); + channel.join().trigger("ok", {}); + + channel + .push("event", { foo: "bar" }, channel.timeout * 2) + .receive("timeout", timeoutSpy); + + jest.advanceTimersByTime(channel.timeout); + expect(timeoutSpy).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(channel.timeout * 2); + expect(timeoutSpy).toHaveBeenCalled(); + }); + + it("does not time out after receiving 'ok'", function () { + channel.join().trigger("ok", {}); + const timeoutSpy = jest.fn(); + const push = channel.push("event", { foo: "bar" }); + push.receive("timeout", timeoutSpy); + + jest.advanceTimersByTime(push.timeout / 2); + expect(timeoutSpy).not.toHaveBeenCalled(); + + push.trigger("ok", {}); + + jest.advanceTimersByTime(push.timeout); + expect(timeoutSpy).not.toHaveBeenCalled(); + }); + + it("throws if channel has not been joined", function () { + expect(() => channel.push("event", {})).toThrow( + /^tried to push.*before joining/, + ); + }); + }); + + describe("leave", function () { + let socketSpy: any; + + beforeEach(function () { + jest.useFakeTimers(); + + socket = new Socket("/socket", { timeout: defaultTimeout }); + jest.spyOn(socket, "isConnected").mockReturnValue(true); + socketSpy = jest.spyOn(socket, "push").mockReturnValue(undefined); + + channel = socket.channel("topic", { one: "two" }); + channel.join().trigger("ok", {}); + }); + + afterEach(function () { + jest.useRealTimers(); + }); + + it("unsubscribes from server events", function () { + jest.spyOn(socket, "makeRef").mockReturnValue(defaultRef.toString()); + const joinRef = channel.joinRef(); + + channel.leave(); + + expect(socketSpy).toHaveBeenCalledWith({ + topic: "topic", + event: "phx_leave", + payload: {}, + ref: defaultRef.toString(), + join_ref: joinRef, + }); + }); + + it("closes channel on 'ok' from server", function () { + const anotherChannel = socket.channel("another", { three: "four" }); + expect(socket["channels"].length).toBe(2); + + channel.leave().trigger("ok", {}); + + expect(socket["channels"].length).toBe(1); + expect(socket["channels"][0]).toBe(anotherChannel); + }); + + it("sets state to closed on 'ok' event", function () { + expect(channel.state).not.toBe("closed"); + + channel.leave().trigger("ok", {}); + + expect(channel.state).toBe("closed"); + }); + + // TODO - the following tests are skipped until Channel.leave + // behavior can be fixed; currently, 'ok' is triggered immediately + // within Channel.leave so timeout callbacks are never reached + // + it.skip("sets state to leaving initially", function () { + expect(channel.state).not.toBe("leaving"); + + channel.leave(); + + expect(channel.state).toBe("leaving"); + }); + + it.skip("closes channel on 'timeout'", function () { + channel.leave(); + + jest.advanceTimersByTime(channel.timeout); + + expect(channel.state).toBe("closed"); + }); + + it.skip("accepts timeout arg", function () { + channel.leave(channel.timeout * 2); + + jest.advanceTimersByTime(channel.timeout); + + expect(channel.state).toBe("leaving"); + + jest.advanceTimersByTime(channel.timeout * 2); + + expect(channel.state).toBe("closed"); + }); + }); +}); diff --git a/assets/test/longpoll_test.js b/assets/test/longpoll_test.js index f02871a39f..8a63c1cd13 100644 --- a/assets/test/longpoll_test.js +++ b/assets/test/longpoll_test.js @@ -1,21 +1,21 @@ -import {jest} from "@jest/globals" -import {LongPoll} from "../js/phoenix" -import {Socket} from "../js/phoenix" -import {AUTH_TOKEN_PREFIX} from "../js/phoenix/constants" -import Ajax from "../js/phoenix/ajax" +import { jest } from "@jest/globals"; +import { LongPoll } from "../js/phoenix"; +import { Socket } from "../js/phoenix"; +import { AUTH_TOKEN_PREFIX } from "../js/phoenix/constants"; +import Ajax from "../js/phoenix/ajax"; describe("LongPoll", () => { - let originalXHR + let originalXHR; beforeEach(() => { - originalXHR = global.XMLHttpRequest - + originalXHR = global.XMLHttpRequest; + // Mock XMLHttpRequest - const mockOpen = jest.fn() - const mockSend = jest.fn() - const mockAbort = jest.fn() - const mockSetRequestHeader = jest.fn() - + const mockOpen = jest.fn(); + const mockSend = jest.fn(); + const mockAbort = jest.fn(); + const mockSetRequestHeader = jest.fn(); + global.XMLHttpRequest = jest.fn(() => ({ open: mockOpen, send: mockSend, @@ -23,187 +23,206 @@ describe("LongPoll", () => { setRequestHeader: mockSetRequestHeader, readyState: 4, status: 200, - responseText: JSON.stringify({status: 200, token: "token123", messages: []}), + responseText: JSON.stringify({ + status: 200, + token: "token123", + messages: [], + }), onreadystatechange: null, - })) + })); // Spy on Ajax.request jest.spyOn(Ajax, "request").mockImplementation(() => { - return {abort: jest.fn()} - }) - }) + return { abort: jest.fn() }; + }); + }); afterEach(() => { - global.XMLHttpRequest = originalXHR - jest.restoreAllMocks() - }) + global.XMLHttpRequest = originalXHR; + jest.restoreAllMocks(); + }); describe("constructor", () => { it("should handle undefined protocols", () => { - const longpoll = new LongPoll("http://localhost/socket/longpoll", undefined) - + const longpoll = new LongPoll( + "http://localhost/socket/longpoll", + undefined, + ); + // Verify longpoll was initialized correctly without error - expect(longpoll.pollEndpoint).toBe("http://localhost/socket/longpoll") - expect(longpoll.authToken).toBeUndefined() - expect(longpoll.readyState).toBe(0) // connecting - }) + expect(longpoll.pollEndpoint).toBe("http://localhost/socket/longpoll"); + expect(longpoll.authToken).toBeUndefined(); + expect(longpoll.readyState).toBe(0); // connecting + }); it("should handle null protocols", () => { - const longpoll = new LongPoll("http://localhost/socket/longpoll", null) - + const longpoll = new LongPoll("http://localhost/socket/longpoll", null); + // Verify longpoll was initialized correctly without error - expect(longpoll.pollEndpoint).toBe("http://localhost/socket/longpoll") - expect(longpoll.authToken).toBeUndefined() - expect(longpoll.readyState).toBe(0) // connecting - }) + expect(longpoll.pollEndpoint).toBe("http://localhost/socket/longpoll"); + expect(longpoll.authToken).toBeUndefined(); + expect(longpoll.readyState).toBe(0); // connecting + }); it("should handle empty array protocols", () => { - const longpoll = new LongPoll("http://localhost/socket/longpoll", []) - + const longpoll = new LongPoll("http://localhost/socket/longpoll", []); + // Verify longpoll was initialized correctly without error - expect(longpoll.pollEndpoint).toBe("http://localhost/socket/longpoll") - expect(longpoll.authToken).toBeUndefined() - expect(longpoll.readyState).toBe(0) // connecting - }) + expect(longpoll.pollEndpoint).toBe("http://localhost/socket/longpoll"); + expect(longpoll.authToken).toBeUndefined(); + expect(longpoll.readyState).toBe(0); // connecting + }); it("should extract authToken when valid protocols are provided", () => { - const authToken = "my-auth-token" - const encodedToken = btoa(authToken) - const protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${encodedToken}`] - - const longpoll = new LongPoll("http://localhost/socket/longpoll", protocols) - + const authToken = "my-auth-token"; + const encodedToken = btoa(authToken); + const protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${encodedToken}`]; + + const longpoll = new LongPoll( + "http://localhost/socket/longpoll", + protocols, + ); + // Verify auth token was extracted correctly - expect(longpoll.authToken).toBe(authToken) - }) - }) + expect(longpoll.authToken).toBe(authToken); + }); + }); describe("poll", () => { it("should include auth token in headers when present", () => { - const authToken = "my-auth-token" - const encodedToken = btoa(authToken) - const protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${encodedToken}`] - - const longpoll = new LongPoll("http://localhost/socket/longpoll", protocols) - longpoll.timeout = 1000 - longpoll.poll() - + const authToken = "my-auth-token"; + const encodedToken = btoa(authToken); + const protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${encodedToken}`]; + + const longpoll = new LongPoll( + "http://localhost/socket/longpoll", + protocols, + ); + longpoll.timeout = 1000; + longpoll.poll(); + // Verify Ajax.request was called with the correct headers expect(Ajax.request).toHaveBeenCalledWith( "GET", expect.any(String), - {"Accept": "application/json", "X-Phoenix-AuthToken": authToken}, + { Accept: "application/json", "X-Phoenix-AuthToken": authToken }, null, expect.any(Number), expect.any(Function), - expect.any(Function) - ) - }) + expect.any(Function), + ); + }); it("should not include auth token in headers when not present", () => { - const longpoll = new LongPoll("http://localhost/socket/longpoll", undefined) - longpoll.timeout = 1000 - longpoll.poll() - + const longpoll = new LongPoll( + "http://localhost/socket/longpoll", + undefined, + ); + longpoll.timeout = 1000; + longpoll.poll(); + // Verify Ajax.request was called without auth token header expect(Ajax.request).toHaveBeenCalledWith( "GET", expect.any(String), - {"Accept": "application/json"}, + { Accept: "application/json" }, null, expect.any(Number), expect.any(Function), - expect.any(Function) - ) - }) - }) + expect.any(Function), + ); + }); + }); describe("batchSend", () => { it("should send with correct content-type header format", () => { - const longpoll = new LongPoll("http://localhost/socket/longpoll", undefined) - longpoll.timeout = 1000 - const messages = ["message1", "message2"] - - longpoll.batchSend(messages) - + const longpoll = new LongPoll( + "http://localhost/socket/longpoll", + undefined, + ); + longpoll.timeout = 1000; + const messages = ["message1", "message2"]; + + longpoll.batchSend(messages); + // Verify Ajax.request was called with correct headers format expect(Ajax.request).toHaveBeenCalledWith( "POST", expect.any(String), - {"Content-Type": "application/x-ndjson"}, + { "Content-Type": "application/x-ndjson" }, "message1\nmessage2", expect.any(Number), expect.any(Function), - expect.any(Function) - ) - }) - }) -}) + expect.any(Function), + ); + }); + }); +}); describe("Socket with LongPoll", () => { describe("transportConnect", () => { it("should initialize with undefined protocols when no auth token", () => { - const socket = new Socket("/socket", {transport: LongPoll}) - + const socket = new Socket("/socket", { transport: LongPoll }); + // Mock the transport to capture the protocols argument socket.transport = jest.fn(() => ({ onopen: jest.fn(), onerror: jest.fn(), onmessage: jest.fn(), - onclose: jest.fn() - })) - - socket.transportConnect() - + onclose: jest.fn(), + })); + + socket.transportConnect(); + // Verify that the transport was called with undefined protocols expect(socket.transport).toHaveBeenCalledWith( expect.any(String), - undefined - ) - }) - + undefined, + ); + }); + it("should only set protocols array when auth token is present", () => { - const authToken = "my-auth-token" + const authToken = "my-auth-token"; const socket = new Socket("/socket", { transport: LongPoll, - params: {token: authToken} - }) - + params: { token: authToken }, + }); + // Set auth token - socket.authToken = authToken - + socket.authToken = authToken; + // Mock the transport to capture the protocols argument socket.transport = jest.fn(() => ({ onopen: jest.fn(), onerror: jest.fn(), onmessage: jest.fn(), - onclose: jest.fn() - })) - - socket.transportConnect() - + onclose: jest.fn(), + })); + + socket.transportConnect(); + // Verify that the transport was called with correct protocols array - expect(socket.transport).toHaveBeenCalledWith( - expect.any(String), - ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(authToken).replace(/=/g, "")}`] - ) - }) - }) -}) + expect(socket.transport).toHaveBeenCalledWith(expect.any(String), [ + "phoenix", + `${AUTH_TOKEN_PREFIX}${btoa(authToken).replace(/=/g, "")}`, + ]); + }); + }); +}); describe("Ajax.request", () => { - let originalXMLHttpRequest, originalFetch, originalAbortController + let originalXMLHttpRequest, originalFetch, originalAbortController; beforeEach(() => { - originalXMLHttpRequest = global.XMLHttpRequest - originalFetch = global.fetch - originalAbortController = global.AbortController + originalXMLHttpRequest = global.XMLHttpRequest; + originalFetch = global.fetch; + originalAbortController = global.AbortController; // Mock AbortController global.AbortController = jest.fn(() => ({ abort: jest.fn(), - signal: {} - })) + signal: {}, + })); // Mock XMLHttpRequest global.XMLHttpRequest = jest.fn(() => ({ @@ -213,44 +232,43 @@ describe("Ajax.request", () => { onreadystatechange: null, readyState: 4, status: 200, - responseText: JSON.stringify({success: true}) - })) + responseText: JSON.stringify({ success: true }), + })); // Mock fetch global.fetch = jest.fn(() => Promise.resolve({ - text: () => Promise.resolve(JSON.stringify({success: true})) - }) - ) - }) + text: () => Promise.resolve(JSON.stringify({ success: true })), + }), + ); + }); afterEach(() => { - global.XMLHttpRequest = originalXMLHttpRequest - global.fetch = originalFetch - global.AbortController = originalAbortController - jest.restoreAllMocks() - }) + global.XMLHttpRequest = originalXMLHttpRequest; + global.fetch = originalFetch; + global.AbortController = originalAbortController; + jest.restoreAllMocks(); + }); it("should use XMLHttpRequest by default", () => { Ajax.request("GET", "/test-endpoint", {}, null, 0, null, (response) => { - expect(response).toEqual({success: true}) - }) + expect(response).toEqual({ success: true }); + }); - expect(global.XMLHttpRequest).toHaveBeenCalled() - }) + expect(global.XMLHttpRequest).toHaveBeenCalled(); + }); it("should use fetch when XMLHttpRequest is not availble", () => { - global.XMLHttpRequest = undefined // Simulate it being unavailable + global.XMLHttpRequest = undefined; // Simulate it being unavailable Ajax.request("GET", "/test-endpoint", {}, null, 0, null, (response) => { - expect(response).toEqual({success: true}) - }) + expect(response).toEqual({ success: true }); + }); expect(global.fetch).toHaveBeenCalledWith( "/test-endpoint", expect.objectContaining({ method: "GET", - }) - ) - }) -}) - + }), + ); + }); +}); diff --git a/assets/test/presence_test.js b/assets/test/presence_test.js deleted file mode 100644 index 09c22ae8a0..0000000000 --- a/assets/test/presence_test.js +++ /dev/null @@ -1,266 +0,0 @@ -import {Presence} from "../js/phoenix" - -const clone = (obj) => { - let cloned = JSON.parse(JSON.stringify(obj)) - Object.entries(obj).forEach(([key, val]) => { - if(val === undefined){ - cloned[key] = undefined - } - }) - return cloned -} - -const fixtures = { - joins(){ - return {u1: {metas: [{id: 1, phx_ref: "1.2"}]}} - }, - leaves(){ - return {u2: {metas: [{id: 2, phx_ref: "2"}]}} - }, - state(){ - return { - u1: {metas: [{id: 1, phx_ref: "1"}]}, - u2: {metas: [{id: 2, phx_ref: "2"}]}, - u3: {metas: [{id: 3, phx_ref: "3"}]}, - } - }, -} - -const channelStub = { - ref: 1, - events: {}, - - on(event, callback){ - this.events[event] = callback - }, - - trigger(event, data){ - this.events[event](data) - }, - - joinRef(){ - return `${this.ref}` - }, - - simulateDisconnectAndReconnect(){ - this.ref++ - }, -} - -const listByFirst = (id, {metas: [first, ..._rest]}) => first - -describe("syncState", () => { - it("syncs empty state", () => { - let newState = {u1: {metas: [{id: 1, phx_ref: "1"}]}} - let state = {} - let stateBefore = clone(state) - Presence.syncState(state, newState) - expect(state).toEqual(stateBefore) - - state = Presence.syncState(state, newState) - expect(state).toEqual(newState) - }) - - it("onJoins new presences and onLeave's left presences", () => { - let newState = fixtures.state() - let state = {u4: {metas: [{id: 4, phx_ref: "4"}]}} - let joined = {} - let left = {} - const onJoin = (key, current, newPres) => { - joined[key] = {current, newPres} - } - const onLeave = (key, current, leftPres) => { - left[key] = {current, leftPres} - } - - state = Presence.syncState(state, newState, onJoin, onLeave) - expect(state).toEqual(newState) - expect(joined).toEqual({ - u1: {current: undefined, newPres: {metas: [{id: 1, phx_ref: "1"}]}}, - u2: {current: undefined, newPres: {metas: [{id: 2, phx_ref: "2"}]}}, - u3: {current: undefined, newPres: {metas: [{id: 3, phx_ref: "3"}]}}, - }) - expect(left).toEqual({ - u4: {current: {metas: []}, leftPres: {metas: [{id: 4, phx_ref: "4"}]}}, - }) - }) - - it("onJoins only newly added metas", () => { - let newState = {u3: {metas: [{id: 3, phx_ref: "3"}, {id: 3, phx_ref: "3.new"}]}} - let state = {u3: {metas: [{id: 3, phx_ref: "3"}]}} - let joined = [] - let left = [] - const onJoin = (key, current, newPres) => { - joined.push([key, clone({current, newPres})]) - } - const onLeave = (key, current, leftPres) => { - left.push([key, clone({current, leftPres})]) - } - state = Presence.syncState(state, clone(newState), onJoin, onLeave) - expect(state).toEqual(newState) - expect(joined).toEqual([ - ["u3", {current: {metas: [{id: 3, phx_ref: "3"}]}, newPres: {metas: [{id: 3, phx_ref: "3.new"}]}}], - ]) - expect(left).toEqual([]) - }) -}) - -describe("syncDiff", () => { - it("syncs empty state", () => { - let joins = {u1: {metas: [{id: 1, phx_ref: "1"}]}} - let state = Presence.syncDiff({}, {joins, leaves: {}}) - expect(state).toEqual(joins) - }) - - it("removes presence when meta is empty and adds additional meta", () => { - let state = fixtures.state() - state = Presence.syncDiff(state, {joins: fixtures.joins(), leaves: fixtures.leaves()}) - - expect(state).toEqual({ - u1: {metas: [{id: 1, phx_ref: "1"}, {id: 1, phx_ref: "1.2"}]}, - u3: {metas: [{id: 3, phx_ref: "3"}]}, - }) - }) - - it("removes meta while leaving key if other metas exist", () => { - let state = {u1: {metas: [{id: 1, phx_ref: "1"}, {id: 1, phx_ref: "1.2"}]}} - state = Presence.syncDiff(state, {joins: {}, leaves: {u1: {metas: [{id: 1, phx_ref: "1"}]}}}) - - expect(state).toEqual({ - u1: {metas: [{id: 1, phx_ref: "1.2"}]}, - }) - }) -}) - -describe("list", () => { - it("lists full presence by default", () => { - let state = fixtures.state() - expect(Presence.list(state)).toEqual([ - {metas: [{id: 1, phx_ref: "1"}]}, - {metas: [{id: 2, phx_ref: "2"}]}, - {metas: [{id: 3, phx_ref: "3"}]}, - ]) - }) - - it("lists with custom function", () => { - let state = {u1: {metas: [{id: 1, phx_ref: "1.first"}, {id: 1, phx_ref: "1.second"}]}} - - const listBy = (key, {metas: [first, ..._rest]}) => first - - expect(Presence.list(state, listBy)).toEqual([{id: 1, phx_ref: "1.first"}]) - }) -}) - -describe("instance", () => { - it("syncs state and diffs", () => { - let presence = new Presence(channelStub) - let user1 = {metas: [{id: 1, phx_ref: "1"}]} - let user2 = {metas: [{id: 2, phx_ref: "2"}]} - let newState = {u1: user1, u2: user2} - - channelStub.trigger("presence_state", newState) - expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: "1"}, {id: 2, phx_ref: "2"}]) - - channelStub.trigger("presence_diff", {joins: {}, leaves: {u1: user1}}) - expect(presence.list(listByFirst)).toEqual([{id: 2, phx_ref: "2"}]) - }) - - it("applies pending diff if state is not yet synced", () => { - let presence = new Presence(channelStub) - let onJoins = [] - let onLeaves = [] - - presence.onJoin((id, current, newPres) => { - onJoins.push(clone({id, current, newPres})) - }) - presence.onLeave((id, current, leftPres) => { - onLeaves.push(clone({id, current, leftPres})) - }) - - let user1 = {metas: [{id: 1, phx_ref: "1"}]} - let user2 = {metas: [{id: 2, phx_ref: "2"}]} - let user3 = {metas: [{id: 3, phx_ref: "3"}]} - let newState = {u1: user1, u2: user2} - let leaves = {u2: user2} - - channelStub.trigger("presence_diff", {joins: {}, leaves: leaves}) - - expect(presence.list(listByFirst)).toEqual([]) - expect(presence.pendingDiffs).toEqual([{joins: {}, leaves: leaves}]) - - channelStub.trigger("presence_state", newState) - expect(onLeaves).toEqual([{id: "u2", current: {metas: []}, leftPres: {metas: [{id: 2, phx_ref: "2"}]}}]) - - expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: "1"}]) - expect(presence.pendingDiffs).toEqual([]) - expect(onJoins).toEqual([ - {id: "u1", current: undefined, newPres: {metas: [{id: 1, phx_ref: "1"}]}}, - {id: "u2", current: undefined, newPres: {metas: [{id: 2, phx_ref: "2"}]}}, - ]) - - channelStub.simulateDisconnectAndReconnect() - expect(presence.inPendingSyncState()).toBe(true) - - channelStub.trigger("presence_diff", {joins: {}, leaves: {u1: user1}}) - expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: "1"}]) - - channelStub.trigger("presence_state", {u1: user1, u3: user3}) - expect(presence.list(listByFirst)).toEqual([{id: 3, phx_ref: "3"}]) - }) - - it("allows custom channel events", () => { - let presence = new Presence(channelStub, { - events: { - state: "the_state", - diff: "the_diff", - }, - }) - - let user1 = {metas: [{id: 1, phx_ref: "1"}]} - channelStub.trigger("the_state", {user1}) - expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: "1"}]) - channelStub.trigger("the_diff", {joins: {}, leaves: {user1}}) - expect(presence.list(listByFirst)).toEqual([]) - }) - - it("updates existing meta for a presence update (leave + join)", () => { - let presence = new Presence(channelStub) - let onJoins = [] - let onLeaves = [] - - let user1 = {metas: [{id: 1, phx_ref: "1"}]} - let user2 = {metas: [{id: 2, name: "chris", phx_ref: "2"}]} - let newState = {u1: user1, u2: user2} - - channelStub.trigger("presence_state", clone(newState)) - - presence.onJoin((id, current, newPres) => { - onJoins.push(clone({id, current, newPres})) - }) - presence.onLeave((id, current, leftPres) => { - onLeaves.push(clone({id, current, leftPres})) - }) - - expect(presence.list((id, {metas: metas}) => metas)).toEqual([ - [{id: 1, phx_ref: "1"}], - [{id: 2, name: "chris", phx_ref: "2"}], - ]) - - let leaves = {u2: user2} - let joins = {u2: {metas: [{id: 2, name: "chris.2", phx_ref: "2.2", phx_ref_prev: "2"}]}} - channelStub.trigger("presence_diff", {joins, leaves}) - - expect(presence.list((id, {metas: metas}) => metas)).toEqual([ - [{id: 1, phx_ref: "1"}], - [{id: 2, name: "chris.2", phx_ref: "2.2", phx_ref_prev: "2"}], - ]) - - expect(onJoins).toEqual([ - { - id: "u2", - current: {metas: [{id: 2, name: "chris", phx_ref: "2"}]}, - newPres: {metas: [{id: 2, name: "chris.2", phx_ref: "2.2", phx_ref_prev: "2"}]}, - }, - ]) - }) -}) diff --git a/assets/test/presence_test.ts b/assets/test/presence_test.ts new file mode 100644 index 0000000000..c151248752 --- /dev/null +++ b/assets/test/presence_test.ts @@ -0,0 +1,363 @@ +import { + default as Presence, + type PresenceMap, + type PresenceMeta, +} from "../js/phoenix/presence"; + +interface Meta extends PresenceMeta { + id: number; + name?: string; + phx_ref_prev?: string; +} + +const clone = (obj: any): any => { + const cloned = JSON.parse(JSON.stringify(obj)); + Object.entries(obj).forEach(([key, val]) => { + if (val === undefined) { + cloned[key] = undefined; + } + }); + return cloned; +}; + +const fixtures = { + joins(): PresenceMap { + return { u1: { metas: [{ id: 1, phx_ref: "1.2" }] } }; + }, + leaves(): PresenceMap { + return { u2: { metas: [{ id: 2, phx_ref: "2" }] } }; + }, + state(): PresenceMap { + return { + u1: { metas: [{ id: 1, phx_ref: "1" }] }, + u2: { metas: [{ id: 2, phx_ref: "2" }] }, + u3: { metas: [{ id: 3, phx_ref: "3" }] }, + }; + }, +}; + +interface ChannelStub { + ref: number; + events: { [key: string]: (data: any) => void }; + on(event: string, callback: (data: any) => void): void; + trigger(event: string, data: any): void; + joinRef(): string; + simulateDisconnectAndReconnect(): void; +} + +const channelStub: ChannelStub = { + ref: 1, + events: {}, + + on(event: string, callback: (data: any) => void) { + this.events[event] = callback; + }, + + trigger(event: string, data: any) { + this.events[event](data); + }, + + joinRef(): string { + return `${this.ref}`; + }, + + simulateDisconnectAndReconnect() { + this.ref++; + }, +}; + +const listByFirst = ( + id: string, + { metas: [first, ..._rest] }: { metas: Meta[] }, +): Meta => first; + +describe("syncState", () => { + it("syncs empty state", () => { + const newState = { u1: { metas: [{ id: 1, phx_ref: "1" }] } }; + let state = {}; + const stateBefore = clone(state); + Presence.syncState(state, newState); + expect(state).toEqual(stateBefore); + + state = Presence.syncState(state, newState); + expect(state).toEqual(newState); + }); + + it("onJoins new presences and onLeave's left presences", () => { + const newState = fixtures.state(); + let state: PresenceMap = { u4: { metas: [{ id: 4, phx_ref: "4" }] } }; + const joined: any = {}; + const left: any = {}; + const onJoin = (key: string, current: any, newPres: any) => { + joined[key] = { current, newPres }; + }; + const onLeave = (key: string, current: any, leftPres: any) => { + left[key] = { current, leftPres }; + }; + + state = Presence.syncState(state, newState, onJoin, onLeave); + expect(state).toEqual(newState); + expect(joined).toEqual({ + u1: { current: undefined, newPres: { metas: [{ id: 1, phx_ref: "1" }] } }, + u2: { current: undefined, newPres: { metas: [{ id: 2, phx_ref: "2" }] } }, + u3: { current: undefined, newPres: { metas: [{ id: 3, phx_ref: "3" }] } }, + }); + expect(left).toEqual({ + u4: { + current: { metas: [] }, + leftPres: { metas: [{ id: 4, phx_ref: "4" }] }, + }, + }); + }); + + it("onJoins only newly added metas", () => { + const newState = { + u3: { + metas: [ + { id: 3, phx_ref: "3" }, + { id: 3, phx_ref: "3.new" }, + ], + }, + }; + let state: PresenceMap = { u3: { metas: [{ id: 3, phx_ref: "3" }] } }; + const joined: any[] = []; + const left: any[] = []; + const onJoin = (key: string, current: any, newPres: any) => { + joined.push([key, clone({ current, newPres })]); + }; + const onLeave = (key: string, current: any, leftPres: any) => { + left.push([key, clone({ current, leftPres })]); + }; + state = Presence.syncState(state, clone(newState), onJoin, onLeave); + expect(state).toEqual(newState); + expect(joined).toEqual([ + [ + "u3", + { + current: { metas: [{ id: 3, phx_ref: "3" }] }, + newPres: { metas: [{ id: 3, phx_ref: "3.new" }] }, + }, + ], + ]); + expect(left).toEqual([]); + }); +}); + +describe("syncDiff", () => { + it("syncs empty state", () => { + const joins = { u1: { metas: [{ id: 1, phx_ref: "1" }] } }; + const state = Presence.syncDiff({}, { joins, leaves: {} }); + expect(state).toEqual(joins); + }); + + it("removes presence when meta is empty and adds additional meta", () => { + let state = fixtures.state(); + state = Presence.syncDiff(state, { + joins: fixtures.joins(), + leaves: fixtures.leaves(), + }); + + expect(state).toEqual({ + u1: { + metas: [ + { id: 1, phx_ref: "1" }, + { id: 1, phx_ref: "1.2" }, + ], + }, + u3: { metas: [{ id: 3, phx_ref: "3" }] }, + }); + }); + + it("removes meta while leaving key if other metas exist", () => { + let state: PresenceMap = { + u1: { + metas: [ + { id: 1, phx_ref: "1" }, + { id: 1, phx_ref: "1.2" }, + ], + }, + }; + state = Presence.syncDiff(state, { + joins: {}, + leaves: { u1: { metas: [{ id: 1, phx_ref: "1" }] } }, + }); + + expect(state).toEqual({ + u1: { metas: [{ id: 1, phx_ref: "1.2" }] }, + }); + }); +}); + +describe("list", () => { + it("lists full presence by default", () => { + const state = fixtures.state(); + expect(Presence.list(state)).toEqual([ + { metas: [{ id: 1, phx_ref: "1" }] }, + { metas: [{ id: 2, phx_ref: "2" }] }, + { metas: [{ id: 3, phx_ref: "3" }] }, + ]); + }); + + it("lists with custom function", () => { + const state = { + u1: { + metas: [ + { id: 1, phx_ref: "1.first" }, + { id: 1, phx_ref: "1.second" }, + ], + }, + }; + + const listBy = ( + key: string, + { metas: [first, ..._rest] }: { metas: Meta[] }, + ): Meta => first; + + expect(Presence.list(state, listBy)).toEqual([ + { id: 1, phx_ref: "1.first" }, + ]); + }); +}); + +describe("instance", () => { + it("syncs state and diffs", () => { + const presence = new Presence(channelStub as any); + const user1 = { metas: [{ id: 1, phx_ref: "1" }] }; + const user2 = { metas: [{ id: 2, phx_ref: "2" }] }; + const newState = { u1: user1, u2: user2 }; + + channelStub.trigger("presence_state", newState); + expect(presence.list(listByFirst)).toEqual([ + { id: 1, phx_ref: "1" }, + { id: 2, phx_ref: "2" }, + ]); + + channelStub.trigger("presence_diff", { joins: {}, leaves: { u1: user1 } }); + expect(presence.list(listByFirst)).toEqual([{ id: 2, phx_ref: "2" }]); + }); + + it("applies pending diff if state is not yet synced", () => { + const presence = new Presence(channelStub as any); + const onJoins: any[] = []; + const onLeaves: any[] = []; + + presence.onJoin((id: string, current: any, newPres: any) => { + onJoins.push(clone({ id, current, newPres })); + }); + presence.onLeave((id: string, current: any, leftPres: any) => { + onLeaves.push(clone({ id, current, leftPres })); + }); + + const user1 = { metas: [{ id: 1, phx_ref: "1" }] }; + const user2 = { metas: [{ id: 2, phx_ref: "2" }] }; + const user3 = { metas: [{ id: 3, phx_ref: "3" }] }; + const newState = { u1: user1, u2: user2 }; + const leaves = { u2: user2 }; + + channelStub.trigger("presence_diff", { joins: {}, leaves: leaves }); + + expect(presence.list(listByFirst)).toEqual([]); + expect(presence["pendingDiffs"]).toEqual([{ joins: {}, leaves: leaves }]); + + channelStub.trigger("presence_state", newState); + expect(onLeaves).toEqual([ + { + id: "u2", + current: { metas: [] }, + leftPres: { metas: [{ id: 2, phx_ref: "2" }] }, + }, + ]); + + expect(presence.list(listByFirst)).toEqual([{ id: 1, phx_ref: "1" }]); + expect(presence["pendingDiffs"]).toEqual([]); + expect(onJoins).toEqual([ + { + id: "u1", + current: undefined, + newPres: { metas: [{ id: 1, phx_ref: "1" }] }, + }, + { + id: "u2", + current: undefined, + newPres: { metas: [{ id: 2, phx_ref: "2" }] }, + }, + ]); + + channelStub.simulateDisconnectAndReconnect(); + expect(presence.inPendingSyncState()).toBe(true); + + channelStub.trigger("presence_diff", { joins: {}, leaves: { u1: user1 } }); + expect(presence.list(listByFirst)).toEqual([{ id: 1, phx_ref: "1" }]); + + channelStub.trigger("presence_state", { u1: user1, u3: user3 }); + expect(presence.list(listByFirst)).toEqual([{ id: 3, phx_ref: "3" }]); + }); + + it("allows custom channel events", () => { + const presence = new Presence(channelStub as any, { + events: { + state: "the_state", + diff: "the_diff", + }, + }); + + const user1 = { metas: [{ id: 1, phx_ref: "1" }] }; + channelStub.trigger("the_state", { user1 }); + expect(presence.list(listByFirst)).toEqual([{ id: 1, phx_ref: "1" }]); + channelStub.trigger("the_diff", { joins: {}, leaves: { user1 } }); + expect(presence.list(listByFirst)).toEqual([]); + }); + + it("updates existing meta for a presence update (leave + join)", () => { + const presence = new Presence(channelStub as any); + const onJoins: any[] = []; + const onLeaves: any[] = []; + + const user1 = { metas: [{ id: 1, phx_ref: "1" }] }; + const user2 = { metas: [{ id: 2, name: "chris", phx_ref: "2" }] }; + const newState = { u1: user1, u2: user2 }; + + channelStub.trigger("presence_state", clone(newState)); + + presence.onJoin((id: string, current: any, newPres: any) => { + onJoins.push(clone({ id, current, newPres })); + }); + presence.onLeave((id: string, current: any, leftPres: any) => { + onLeaves.push(clone({ id, current, leftPres })); + }); + + expect( + presence.list((id: string, { metas }: { metas: Meta[] }) => metas), + ).toEqual([ + [{ id: 1, phx_ref: "1" }], + [{ id: 2, name: "chris", phx_ref: "2" }], + ]); + + const leaves = { u2: user2 }; + const joins = { + u2: { + metas: [{ id: 2, name: "chris.2", phx_ref: "2.2", phx_ref_prev: "2" }], + }, + }; + channelStub.trigger("presence_diff", { joins, leaves }); + + expect( + presence.list((id: string, { metas }: { metas: Meta[] }) => metas), + ).toEqual([ + [{ id: 1, phx_ref: "1" }], + [{ id: 2, name: "chris.2", phx_ref: "2.2", phx_ref_prev: "2" }], + ]); + + expect(onJoins).toEqual([ + { + id: "u2", + current: { metas: [{ id: 2, name: "chris", phx_ref: "2" }] }, + newPres: { + metas: [ + { id: 2, name: "chris.2", phx_ref: "2.2", phx_ref_prev: "2" }, + ], + }, + }, + ]); + }); +}); diff --git a/assets/test/serializer.js b/assets/test/serializer.js deleted file mode 100644 index 6a34a0e63a..0000000000 --- a/assets/test/serializer.js +++ /dev/null @@ -1,13 +0,0 @@ - -export const encode = (msg) => { - let payload = [ - msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload - ] - return JSON.stringify(payload) -} - -export const decode = (rawPayload) => { - let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload) - - return {join_ref, ref, topic, event, payload} -} diff --git a/assets/test/serializer.ts b/assets/test/serializer.ts new file mode 100644 index 0000000000..344ebf6fc7 --- /dev/null +++ b/assets/test/serializer.ts @@ -0,0 +1,12 @@ +import { Message } from "../js/phoenix/serializer"; + +export const encode = (msg: Message): string => { + const payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; + return JSON.stringify(payload); +}; + +export const decode = (rawPayload: string): Message => { + const [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); + + return { join_ref, ref, topic, event, payload }; +}; diff --git a/assets/test/serializer_test.js b/assets/test/serializer_test.js deleted file mode 100644 index 78a56e8816..0000000000 --- a/assets/test/serializer_test.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @jest-environment node - */ - -import {TextEncoder, TextDecoder} from "util" -import {Serializer} from "../js/phoenix" - -let exampleMsg = {join_ref: "0", ref: "1", topic: "t", event: "e", payload: {foo: 1}} - -let binPayload = () => { - let buffer = new ArrayBuffer(1) - new DataView(buffer).setUint8(0, 1) - return buffer -} - -describe("JSON", () => { - it("encodes general pushes", (done) => { - Serializer.encode(exampleMsg, (result) => { - expect(result).toBe("[\"0\",\"1\",\"t\",\"e\",{\"foo\":1}]") - done() - }) - }) - - it("decodes", (done) => { - Serializer.decode("[\"0\",\"1\",\"t\",\"e\",{\"foo\":1}]", (result) => { - expect(result).toEqual(exampleMsg) - done() - }) - }) -}) - -describe("binary", () => { - it("encodes", (done) => { - let buffer = binPayload() - let bin = "\0\x01\x01\x01\x0101te\x01" - let decoder = new TextDecoder() - Serializer.encode({join_ref: "0", ref: "1", topic: "t", event: "e", payload: buffer}, (result) => { - expect(decoder.decode(result)).toBe(bin) - done() - }) - }) - - it("encodes variable length segments", (done) => { - let buffer = binPayload() - let bin = "\0\x02\x01\x03\x02101topev\x01" - let decoder = new TextDecoder() - Serializer.encode({join_ref: "10", ref: "1", topic: "top", event: "ev", payload: buffer}, (result) => { - expect(decoder.decode(result)).toBe(bin) - done() - }) - }) - - it("decodes push", (done) => { - let bin = "\0\x03\x03\n123topsome-event\x01\x01" - let buffer = new TextEncoder().encode(bin).buffer - let decoder = new TextDecoder() - Serializer.decode(buffer, (result) => { - expect(result.join_ref).toBe("123") - expect(result.ref).toBeNull() - expect(result.topic).toBe("top") - expect(result.event).toBe("some-event") - expect(result.payload.constructor).toBe(ArrayBuffer) - expect(decoder.decode(result.payload)).toBe("\x01\x01") - done() - }) - }) - - it("decodes reply", (done) => { - let bin = "\x01\x03\x02\x03\x0210012topok\x01\x01" - let buffer = new TextEncoder().encode(bin).buffer - let decoder = new TextDecoder() - Serializer.decode(buffer, (result) => { - expect(result.join_ref).toBe("100") - expect(result.ref).toBe("12") - expect(result.topic).toBe("top") - expect(result.event).toBe("phx_reply") - expect(result.payload.status).toBe("ok") - expect(result.payload.response.constructor).toBe(ArrayBuffer) - expect(decoder.decode(result.payload.response)).toBe("\x01\x01") - done() - }) - }) - - it("decodes broadcast", (done) => { - let bin = "\x02\x03\ntopsome-event\x01\x01" - let buffer = new TextEncoder().encode(bin).buffer - let decoder = new TextDecoder() - Serializer.decode(buffer, (result) => { - expect(result.join_ref).toBeNull() - expect(result.ref).toBeNull() - expect(result.topic).toBe("top") - expect(result.event).toBe("some-event") - expect(result.payload.constructor).toBe(ArrayBuffer) - expect(decoder.decode(result.payload)).toBe("\x01\x01") - done() - }) - }) -}) diff --git a/assets/test/serializer_test.ts b/assets/test/serializer_test.ts new file mode 100644 index 0000000000..6d2c8e5c48 --- /dev/null +++ b/assets/test/serializer_test.ts @@ -0,0 +1,111 @@ +/** + * @jest-environment node + */ + +import { TextEncoder, TextDecoder } from "util"; +import { Serializer } from "../js/phoenix"; +import { Message } from "../js/phoenix/serializer"; + +const exampleMsg: Message = { + join_ref: "0", + ref: "1", + topic: "t", + event: "e", + payload: { foo: 1 }, +}; + +const binPayload = (): ArrayBuffer => { + const buffer = new ArrayBuffer(1); + new DataView(buffer).setUint8(0, 1); + return buffer; +}; + +describe("JSON", () => { + it("encodes general pushes", (done) => { + Serializer.encode(exampleMsg, (result) => { + expect(result).toBe('["0","1","t","e",{"foo":1}]'); + done(); + }); + }); + + it("decodes", (done) => { + Serializer.decode('["0","1","t","e",{"foo":1}]', (result) => { + expect(result).toEqual(exampleMsg); + done(); + }); + }); +}); + +describe("binary", () => { + it("encodes", (done) => { + const buffer = binPayload(); + const bin = "\0\x01\x01\x01\x0101te\x01"; + const decoder = new TextDecoder(); + Serializer.encode( + { join_ref: "0", ref: "1", topic: "t", event: "e", payload: buffer }, + (result) => { + expect(decoder.decode(result as ArrayBuffer)).toBe(bin); + done(); + }, + ); + }); + + it("encodes variable length segments", (done) => { + const buffer = binPayload(); + const bin = "\0\x02\x01\x03\x02101topev\x01"; + const decoder = new TextDecoder(); + Serializer.encode( + { join_ref: "10", ref: "1", topic: "top", event: "ev", payload: buffer }, + (result) => { + expect(decoder.decode(result as ArrayBuffer)).toBe(bin); + done(); + }, + ); + }); + + it("decodes push", (done) => { + const bin = "\0\x03\x03\n123topsome-event\x01\x01"; + const buffer = new TextEncoder().encode(bin).buffer; + const decoder = new TextDecoder(); + Serializer.decode(buffer, (result) => { + expect(result.join_ref).toBe("123"); + expect(result.ref).toBeNull(); + expect(result.topic).toBe("top"); + expect(result.event).toBe("some-event"); + expect(result.payload.constructor).toBe(ArrayBuffer); + expect(decoder.decode(result.payload as ArrayBuffer)).toBe("\x01\x01"); + done(); + }); + }); + + it("decodes reply", (done) => { + const bin = "\x01\x03\x02\x03\x0210012topok\x01\x01"; + const buffer = new TextEncoder().encode(bin).buffer; + const decoder = new TextDecoder(); + Serializer.decode(buffer, (result) => { + expect(result.join_ref).toBe("100"); + expect(result.ref).toBe("12"); + expect(result.topic).toBe("top"); + expect(result.event).toBe("phx_reply"); + expect((result.payload as any).status).toBe("ok"); + expect((result.payload as any).response.constructor).toBe(ArrayBuffer); + expect(decoder.decode((result.payload as any).response)).toBe("\x01\x01"); + done(); + }); + }); + + it("decodes broadcast", (done) => { + const bin = "\x02\x03\ntopsome-event\x01\x01"; + const buffer = new TextEncoder().encode(bin).buffer; + const decoder = new TextDecoder(); + Serializer.decode(buffer, (result) => { + expect(result.join_ref).toBeNull(); + expect(result.ref).toBeNull(); + expect(result.topic).toBe("top"); + expect(result.event).toBe("some-event"); + expect(result.payload.constructor).toBe(ArrayBuffer); + expect(decoder.decode(result.payload as ArrayBuffer)).toBe("\x01\x01"); + done(); + }); + }); +}); diff --git a/assets/test/socket_http_test.js b/assets/test/socket_http_test.ts similarity index 70% rename from assets/test/socket_http_test.js rename to assets/test/socket_http_test.ts index 361084f95d..e9b3773522 100644 --- a/assets/test/socket_http_test.js +++ b/assets/test/socket_http_test.ts @@ -2,22 +2,22 @@ * @jest-environment jsdom * @jest-environment-options {"url": "http://example.com/"} */ -import { Socket } from "../js/phoenix" +import { Socket } from "../js/phoenix"; // sadly, jsdom can only be configured globally for a file describe("protocol", function () { it("returns ws when location.protocol is http", function () { - const socket = new Socket("/socket") - expect(socket.protocol()).toBe("ws") - }) -}) + const socket = new Socket("/socket"); + expect(socket.protocol()).toBe("ws"); + }); +}); describe("endpointURL", function () { it("returns endpoint for given path on http host", function () { - const socket = new Socket("/socket") + const socket = new Socket("/socket"); expect(socket.endPointURL()).toBe( "ws://example.com/socket/websocket?vsn=2.0.0", - ) - }) -}) + ); + }); +}); diff --git a/assets/test/socket_test.js b/assets/test/socket_test.js index 5663e4ea1c..7abb1dbc86 100644 --- a/assets/test/socket_test.js +++ b/assets/test/socket_test.js @@ -1,18 +1,18 @@ -import {jest} from "@jest/globals" -import {WebSocket, Server as WebSocketServer} from "mock-socket" -import {encode} from "./serializer" -import {Socket, LongPoll} from "../js/phoenix" +import { jest } from "@jest/globals"; +import { WebSocket, Server as WebSocketServer } from "mock-socket"; +import { encode } from "./serializer"; +import { Socket, LongPoll } from "../js/phoenix"; -let socket +let socket; -describe("with transports", function (){ +describe("with transports", function () { beforeAll(() => { - window.WebSocket = WebSocket - const mockOpen = jest.fn() - const mockSend = jest.fn() - const mockAbort = jest.fn() - const mockSetRequestHeader = jest.fn() - + window.WebSocket = WebSocket; + const mockOpen = jest.fn(); + const mockSend = jest.fn(); + const mockAbort = jest.fn(); + const mockSetRequestHeader = jest.fn(); + global.XMLHttpRequest = jest.fn(() => ({ open: mockOpen, send: mockSend, @@ -22,39 +22,48 @@ describe("with transports", function (){ status: 200, responseText: JSON.stringify({}), onreadystatechange: null, - })) - }) - - describe("constructor", function (){ - it("sets defaults", function (){ - socket = new Socket("/socket") - - expect(socket.channels.length).toBe(0) - expect(socket.sendBuffer.length).toBe(0) - expect(socket.ref).toBe(0) - expect(socket.endPoint).toBe("/socket/websocket") - expect(socket.stateChangeCallbacks).toEqual({open: [], close: [], error: [], message: []}) - expect(socket.transport).toBe(WebSocket) - expect(socket.timeout).toBe(10000) - expect(socket.longpollerTimeout).toBe(20000) - expect(socket.heartbeatIntervalMs).toBe(30000) - expect(socket.logger).toBeNull() - expect(socket.binaryType).toBe("arraybuffer") - expect(typeof socket.reconnectAfterMs).toBe("function") - }) - - it("supports closure or literal params", function (){ - socket = new Socket("/socket", {params: {one: "two"}}) - expect(socket.params()).toEqual({one: "two"}) - - socket = new Socket("/socket", {params: function (){ return ({three: "four"}) }}) - expect(socket.params()).toEqual({three: "four"}) - }) - - it("overrides some defaults with options", function (){ - const customTransport = function transport(){ } - const customLogger = function logger(){ } - const customReconnect = function reconnect(){ } + })); + }); + + describe("constructor", function () { + it("sets defaults", function () { + socket = new Socket("/socket"); + + expect(socket.channels.length).toBe(0); + expect(socket.sendBuffer.length).toBe(0); + expect(socket.ref).toBe(0); + expect(socket.endPoint).toBe("/socket/websocket"); + expect(socket.stateChangeCallbacks).toEqual({ + open: [], + close: [], + error: [], + message: [], + }); + expect(socket.transport).toBe(WebSocket); + expect(socket.timeout).toBe(10000); + expect(socket.longpollerTimeout).toBe(20000); + expect(socket.heartbeatIntervalMs).toBe(30000); + expect(socket.logger).toBeNull(); + expect(socket.binaryType).toBe("arraybuffer"); + expect(typeof socket.reconnectAfterMs).toBe("function"); + }); + + it("supports closure or literal params", function () { + socket = new Socket("/socket", { params: { one: "two" } }); + expect(socket.params()).toEqual({ one: "two" }); + + socket = new Socket("/socket", { + params: function () { + return { three: "four" }; + }, + }); + expect(socket.params()).toEqual({ three: "four" }); + }); + + it("overrides some defaults with options", function () { + const customTransport = function transport() {}; + const customLogger = function logger() {}; + const customReconnect = function reconnect() {}; socket = new Socket("/socket", { timeout: 40000, @@ -63,845 +72,951 @@ describe("with transports", function (){ transport: customTransport, logger: customLogger, reconnectAfterMs: customReconnect, - params: {one: "two"}, - }) - - expect(socket.timeout).toBe(40000) - expect(socket.longpollerTimeout).toBe(50000) - expect(socket.heartbeatIntervalMs).toBe(60000) - expect(socket.transport).toBe(customTransport) - expect(socket.logger).toBe(customLogger) - expect(socket.params()).toEqual({one: "two"}) - }) - - describe("with Websocket", function (){ - it("defaults to Websocket transport if available", function (done){ - let mockServer = new WebSocketServer("wss://example.com/") - socket = new Socket("/socket") - expect(socket.transport).toBe(WebSocket) - mockServer.stop(() => done()) - }) - }) - - describe("longPollFallbackMs", function (){ - it("falls back to longpoll when set after primary transport failure", function (done){ - let mockServer - socket = new Socket("/socket", {longPollFallbackMs: 20}) - const replaceSpy = jest.spyOn(socket, "replaceTransport") - mockServer = new WebSocketServer("wss://example.test/") + params: { one: "two" }, + }); + + expect(socket.timeout).toBe(40000); + expect(socket.longpollerTimeout).toBe(50000); + expect(socket.heartbeatIntervalMs).toBe(60000); + expect(socket.transport).toBe(customTransport); + expect(socket.logger).toBe(customLogger); + expect(socket.params()).toEqual({ one: "two" }); + }); + + describe("with Websocket", function () { + it("defaults to Websocket transport if available", function (done) { + let mockServer = new WebSocketServer("wss://example.com/"); + socket = new Socket("/socket"); + expect(socket.transport).toBe(WebSocket); + mockServer.stop(() => done()); + }); + }); + + describe("longPollFallbackMs", function () { + it("falls back to longpoll when set after primary transport failure", function (done) { + let mockServer; + socket = new Socket("/socket", { longPollFallbackMs: 20 }); + const replaceSpy = jest.spyOn(socket, "replaceTransport"); + mockServer = new WebSocketServer("wss://example.test/"); mockServer.stop(() => { - expect(socket.transport).toBe(WebSocket) + expect(socket.transport).toBe(WebSocket); socket.onError((_reason) => { setTimeout(() => { - expect(replaceSpy).toHaveBeenCalledWith(LongPoll) - done() - }, 100) - }) - socket.connect() - }) - }) - }) - }) - - describe("protocol", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - }) - - it("returns wss when location.protocol is https", function (){ - expect(socket.protocol()).toBe("wss") - }) - }) - - describe("endpointURL", function (){ - it("returns endpoint for given full url", function (){ - socket = new Socket("wss://example.org/chat") - expect(socket.endPointURL()).toBe("wss://example.org/chat/websocket?vsn=2.0.0") - }) - - it("returns endpoint for given protocol-relative url", function (){ - socket = new Socket("//example.org/chat") - expect(socket.endPointURL()).toBe("wss://example.org/chat/websocket?vsn=2.0.0") - }) - - it("returns endpoint for given path on https host", function (){ - socket = new Socket("/socket") - expect(socket.endPointURL()).toBe("wss://example.com/socket/websocket?vsn=2.0.0") - }) - }) - - describe("connect with WebSocket", function (){ - let mockServer - - beforeAll(function (){ - mockServer = new WebSocketServer("wss://example.com/") - }) - - afterAll(function (done){ - mockServer.stop(() => done()) - }) - - beforeEach(function (){ - socket = new Socket("/socket") - }) - - it("establishes websocket connection with endpoint", function (){ - socket.connect() - const conn = socket.conn - expect(conn instanceof WebSocket).toBeTruthy() - expect(conn.url).toBe(socket.endPointURL()) - }) - - it("sets callbacks for connection", function (){ - let opens = 0 - socket.onOpen(() => ++opens) - let closes = 0 - socket.onClose(() => ++closes) - let lastError - socket.onError((error) => lastError = error) - let lastMessage - socket.onMessage((message) => lastMessage = message.payload) - - socket.connect() - - socket.conn.onopen() - expect(opens).toBe(1) - - socket.conn.onclose() - expect(closes).toBe(1) - - socket.conn.onerror("error") - expect(lastError).toBe("error") - - const data = {"topic": "topic", "event": "event", "payload": "payload", "status": "ok"} - socket.conn.onmessage({data: encode(data)}) - expect(lastMessage).toBe("payload") - }) - - it("is idempotent", function (){ - socket.connect() - const conn = socket.conn - socket.connect() - expect(conn).toBe(socket.conn) - }) - }) - - describe("connect with long poll", function (){ - beforeEach(function (){ - socket = new Socket("/socket", {transport: LongPoll}) - }) - - it("establishes long poll connection with endpoint", function (){ - socket.connect() - const conn = socket.conn - expect(conn instanceof LongPoll).toBeTruthy() - expect(conn.pollEndpoint).toBe("https://example.com/socket/longpoll?vsn=2.0.0") - expect(conn.timeout).toBe(20000) - }) - - it("sets callbacks for connection", function (){ - let opens = 0 - socket.onOpen(() => ++opens) - let closes = 0 - socket.onClose(() => ++closes) - let lastError - socket.onError((error) => lastError = error) - let lastMessage - socket.onMessage((message) => lastMessage = message.payload) - - socket.connect() - - socket.conn.onopen() - expect(opens).toBe(1) - - socket.conn.onclose() - expect(closes).toBe(1) - - socket.conn.onerror("error") - expect(lastError).toBe("error") - - socket.connect() - - const data = {"topic": "topic", "event": "event", "payload": "payload", "status": "ok"} - - socket.conn.onmessage({data: encode(data)}) - expect(lastMessage).toBe("payload") - }) - - it("is idempotent", function (){ - socket.connect() - const conn = socket.conn - socket.connect() - expect(conn).toBe(socket.conn) - }) - }) - - describe("disconnect", function (){ - let mockServer - - beforeAll(function (){ - mockServer = new WebSocketServer("wss://example.com/") - }) - - afterAll(function (done){ - mockServer.stop(() => done()) - }) - - beforeEach(function (){ - socket = new Socket("/socket") - }) - - it("removes existing connection", function (done){ - socket.connect() - socket.disconnect() + expect(replaceSpy).toHaveBeenCalledWith(LongPoll); + done(); + }, 100); + }); + socket.connect(); + }); + }); + + it("cleans up previous fallback onOpen handler on reconnect", function (done) { + jest.useFakeTimers(); + const mockLogger = jest.fn(); + socket = new Socket("/socket", { + longPollFallbackMs: 20, + logger: mockLogger, + debug: true + }); + + jest.spyOn(socket, "ping").mockImplementation((callback) => { + callback(100); + }); + + // First connection with fallback + socket.connect(); + socket.onConnOpen(); + jest.advanceTimersByTime(50); + + // Verify first "connected to primary after" log + expect(mockLogger).toHaveBeenCalledWith("transport", "connected to primary after", 100); + mockLogger.mockClear(); + + // Disconnect and reconnect to trigger another connectWithFallback + socket.disconnect(); + socket.connect(); + socket.onConnOpen(); + jest.advanceTimersByTime(50); + + // Verify that "connected to primary after" is logged only once for the second connection + const primaryConnectLogs = mockLogger.mock.calls.filter( + call => call[0] === "transport" && call[1] === "connected to primary after" + ); + expect(primaryConnectLogs.length).toBe(1); + + jest.useRealTimers(); + done(); + }); + }); + }); + + describe("protocol", function () { + beforeEach(function () { + socket = new Socket("/socket"); + }); + + it("returns wss when location.protocol is https", function () { + expect(socket.protocol()).toBe("wss"); + }); + }); + + describe("endpointURL", function () { + it("returns endpoint for given full url", function () { + socket = new Socket("wss://example.org/chat"); + expect(socket.endPointURL()).toBe( + "wss://example.org/chat/websocket?vsn=2.0.0", + ); + }); + + it("returns endpoint for given protocol-relative url", function () { + socket = new Socket("//example.org/chat"); + expect(socket.endPointURL()).toBe( + "wss://example.org/chat/websocket?vsn=2.0.0", + ); + }); + + it("returns endpoint for given path on https host", function () { + socket = new Socket("/socket"); + expect(socket.endPointURL()).toBe( + "wss://example.com/socket/websocket?vsn=2.0.0", + ); + }); + }); + + describe("connect with WebSocket", function () { + let mockServer; + + beforeAll(function () { + mockServer = new WebSocketServer("wss://example.com/"); + }); + + afterAll(function (done) { + mockServer.stop(() => done()); + }); + + beforeEach(function () { + socket = new Socket("/socket"); + }); + + it("establishes websocket connection with endpoint", function () { + socket.connect(); + const conn = socket.conn; + expect(conn instanceof WebSocket).toBeTruthy(); + expect(conn.url).toBe(socket.endPointURL()); + }); + + it("sets callbacks for connection", function () { + let opens = 0; + socket.onOpen(() => ++opens); + let closes = 0; + socket.onClose(() => ++closes); + let lastError; + socket.onError((error) => (lastError = error)); + let lastMessage; + socket.onMessage((message) => (lastMessage = message.payload)); + + socket.connect(); + + socket.conn.onopen(); + expect(opens).toBe(1); + + socket.conn.onclose(); + expect(closes).toBe(1); + + socket.conn.onerror("error"); + expect(lastError).toBe("error"); + + const data = { + topic: "topic", + event: "event", + payload: "payload", + status: "ok", + }; + socket.conn.onmessage({ data: encode(data) }); + expect(lastMessage).toBe("payload"); + }); + + it("is idempotent", function () { + socket.connect(); + const conn = socket.conn; + socket.connect(); + expect(conn).toBe(socket.conn); + }); + }); + + describe("connect with long poll", function () { + beforeEach(function () { + socket = new Socket("/socket", { transport: LongPoll }); + }); + + it("establishes long poll connection with endpoint", function () { + socket.connect(); + const conn = socket.conn; + expect(conn instanceof LongPoll).toBeTruthy(); + expect(conn.pollEndpoint).toBe( + "https://example.com/socket/longpoll?vsn=2.0.0", + ); + expect(conn.timeout).toBe(20000); + }); + + it("sets callbacks for connection", function () { + let opens = 0; + socket.onOpen(() => ++opens); + let closes = 0; + socket.onClose(() => ++closes); + let lastError; + socket.onError((error) => (lastError = error)); + let lastMessage; + socket.onMessage((message) => (lastMessage = message.payload)); + + socket.connect(); + + socket.conn.onopen(); + expect(opens).toBe(1); + + socket.conn.onclose(); + expect(closes).toBe(1); + + socket.conn.onerror("error"); + expect(lastError).toBe("error"); + + socket.connect(); + + const data = { + topic: "topic", + event: "event", + payload: "payload", + status: "ok", + }; + + socket.conn.onmessage({ data: encode(data) }); + expect(lastMessage).toBe("payload"); + }); + + it("is idempotent", function () { + socket.connect(); + const conn = socket.conn; + socket.connect(); + expect(conn).toBe(socket.conn); + }); + }); + + describe("disconnect", function () { + let mockServer; + + beforeAll(function () { + mockServer = new WebSocketServer("wss://example.com/"); + }); + + afterAll(function (done) { + mockServer.stop(() => done()); + }); + + beforeEach(function () { + socket = new Socket("/socket"); + }); + + it("removes existing connection", function (done) { + socket.connect(); + socket.disconnect(); socket.disconnect(() => { - expect(socket.conn).toBeNull() - done() - }) - }) - - it("calls callback", function (done){ - let count = 0 - socket.connect() + expect(socket.conn).toBeNull(); + done(); + }); + }); + + it("calls callback", function (done) { + let count = 0; + socket.connect(); socket.disconnect(() => { - count++ - expect(count).toBe(1) - done() - }) - }) - - it("calls connection close callback", function (done){ - socket.connect() - const closeSpy = jest.spyOn(socket.conn, "close") - - socket.disconnect(() => { - expect(closeSpy).toHaveBeenCalledWith(1000, "reason") - done() - }, 1000, "reason") - }) - - it("does not throw when no connection", function (){ + count++; + expect(count).toBe(1); + done(); + }); + }); + + it("calls connection close callback", function (done) { + socket.connect(); + const closeSpy = jest.spyOn(socket.conn, "close"); + + socket.disconnect( + () => { + expect(closeSpy).toHaveBeenCalledWith(1000, "reason"); + done(); + }, + 1000, + "reason", + ); + }); + + it("does not throw when no connection", function () { expect(() => { - socket.disconnect() - }).not.toThrow() - }) - }) - - describe("connectionState", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - }) - - it("defaults to closed", function (){ - expect(socket.connectionState()).toBe("closed") - }) - - it("returns closed if readyState unrecognized", function (){ - socket.connect() - socket.conn.readyState = 5678 - expect(socket.connectionState()).toBe("closed") - }) - - it("returns connecting", function (){ - socket.connect() - socket.conn.readyState = 0 - expect(socket.connectionState()).toBe("connecting") - expect(socket.isConnected()).toBe(false) - }) - - it("returns open", function (){ - socket.connect() - socket.conn.readyState = 1 - expect(socket.connectionState()).toBe("open") - expect(socket.isConnected()).toBe(true) - }) - - it("returns closing", function (){ - socket.connect() - socket.conn.readyState = 2 - expect(socket.connectionState()).toBe("closing") - expect(socket.isConnected()).toBe(false) - }) - - it("returns closed", function (){ - socket.connect() - socket.conn.readyState = 3 - expect(socket.connectionState()).toBe("closed") - expect(socket.isConnected()).toBe(false) - }) - }) - - describe("channel", function (){ - let channel - - beforeEach(function (){ - socket = new Socket("/socket") - }) - - it("returns channel with given topic and params", function (){ - channel = socket.channel("topic", {one: "two"}) - expect(channel.socket).toBe(socket) - expect(channel.topic).toBe("topic") - expect(channel.params()).toEqual({one: "two"}) - }) - - it("adds channel to sockets channels list", function (){ - expect(socket.channels.length).toBe(0) - channel = socket.channel("topic", {one: "two"}) - expect(socket.channels.length).toBe(1) - const [foundChannel] = socket.channels - expect(foundChannel).toBe(channel) - }) - }) - - describe("remove", function (){ - it("removes given channel from channels", function (){ - socket = new Socket("/socket") - const channel1 = socket.channel("topic-1") - const channel2 = socket.channel("topic-2") - - jest.spyOn(channel1, "joinRef").mockReturnValue(1) - jest.spyOn(channel2, "joinRef").mockReturnValue(2) - - expect(socket.stateChangeCallbacks.open.length).toBe(2) - - socket.remove(channel1) - - expect(socket.stateChangeCallbacks.open.length).toBe(1) - expect(socket.channels.length).toBe(1) - - const [foundChannel] = socket.channels - expect(foundChannel).toBe(channel2) - }) - }) - - describe("push", function (){ - let data, json - - beforeEach(function (){ - data = {topic: "topic", event: "event", payload: "payload", ref: "ref"} - json = encode(data) - socket = new Socket("/socket") - }) - - it("sends data to connection when connected", function (){ - socket.connect() - socket.conn.readyState = 1 // open - - const sendSpy = jest.spyOn(socket.conn, "send") - - socket.push(data) - - expect(sendSpy).toHaveBeenCalledWith(json) - }) - - it("buffers data when not connected", function (){ - socket.connect() - socket.conn.readyState = 0 // connecting - - const sendSpy = jest.spyOn(socket.conn, "send").mockImplementation(() => {}) - - expect(socket.sendBuffer.length).toBe(0) - - socket.push(data) - - expect(sendSpy).not.toHaveBeenCalledWith(json) - expect(socket.sendBuffer.length).toBe(1) - - const [callback] = socket.sendBuffer - callback() - expect(sendSpy).toHaveBeenCalledWith(json) - }) - }) - - describe("makeRef", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - }) - - it("returns next message ref", function (){ - expect(socket.ref).toBe(0) - expect(socket.makeRef()).toBe("1") - expect(socket.ref).toBe(1) - expect(socket.makeRef()).toBe("2") - expect(socket.ref).toBe(2) - }) - - it("restarts for overflow", function (){ - socket.ref = Number.MAX_SAFE_INTEGER + 1 - expect(socket.makeRef()).toBe("0") - expect(socket.ref).toBe(0) - }) - }) - - describe("sendHeartbeat", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - socket.connect() - }) - - it("closes socket when heartbeat is not ack'd within heartbeat window", function (done){ - jest.useFakeTimers() - let closed = false - socket.conn.readyState = 1 // open - socket.conn.close = () => closed = true - socket.sendHeartbeat() - expect(closed).toBe(false) - - jest.advanceTimersByTime(10000) - expect(closed).toBe(false) - - jest.advanceTimersByTime(20010) - expect(closed).toBe(true) - - jest.useRealTimers() - done() - }) - - it("pushes heartbeat data when connected", function (){ - socket.conn.readyState = 1 // open - - const sendSpy = jest.spyOn(socket.conn, "send") - const data = "[null,\"1\",\"phoenix\",\"heartbeat\",{}]" + socket.disconnect(); + }).not.toThrow(); + }); + }); + + describe("connectionState", function () { + beforeEach(function () { + socket = new Socket("/socket"); + }); + + it("defaults to closed", function () { + expect(socket.connectionState()).toBe("closed"); + }); + + it("returns closed if readyState unrecognized", function () { + socket.connect(); + socket.conn.readyState = 5678; + expect(socket.connectionState()).toBe("closed"); + }); + + it("returns connecting", function () { + socket.connect(); + socket.conn.readyState = 0; + expect(socket.connectionState()).toBe("connecting"); + expect(socket.isConnected()).toBe(false); + }); + + it("returns open", function () { + socket.connect(); + socket.conn.readyState = 1; + expect(socket.connectionState()).toBe("open"); + expect(socket.isConnected()).toBe(true); + }); + + it("returns closing", function () { + socket.connect(); + socket.conn.readyState = 2; + expect(socket.connectionState()).toBe("closing"); + expect(socket.isConnected()).toBe(false); + }); + + it("returns closed", function () { + socket.connect(); + socket.conn.readyState = 3; + expect(socket.connectionState()).toBe("closed"); + expect(socket.isConnected()).toBe(false); + }); + }); + + describe("channel", function () { + let channel; + + beforeEach(function () { + socket = new Socket("/socket"); + }); + + it("returns channel with given topic and params", function () { + channel = socket.channel("topic", { one: "two" }); + expect(channel.socket).toBe(socket); + expect(channel.topic).toBe("topic"); + expect(channel.params()).toEqual({ one: "two" }); + }); + + it("adds channel to sockets channels list", function () { + expect(socket.channels.length).toBe(0); + channel = socket.channel("topic", { one: "two" }); + expect(socket.channels.length).toBe(1); + const [foundChannel] = socket.channels; + expect(foundChannel).toBe(channel); + }); + }); + + describe("remove", function () { + it("removes given channel from channels", function () { + socket = new Socket("/socket"); + const channel1 = socket.channel("topic-1"); + const channel2 = socket.channel("topic-2"); + + jest.spyOn(channel1, "joinRef").mockReturnValue(1); + jest.spyOn(channel2, "joinRef").mockReturnValue(2); + + expect(socket.stateChangeCallbacks.open.length).toBe(2); + + socket.remove(channel1); + + expect(socket.stateChangeCallbacks.open.length).toBe(1); + expect(socket.channels.length).toBe(1); + + const [foundChannel] = socket.channels; + expect(foundChannel).toBe(channel2); + }); + }); + + describe("push", function () { + let data, json; + + beforeEach(function () { + data = { topic: "topic", event: "event", payload: "payload", ref: "ref" }; + json = encode(data); + socket = new Socket("/socket"); + }); + + it("sends data to connection when connected", function () { + socket.connect(); + socket.conn.readyState = 1; // open + + const sendSpy = jest.spyOn(socket.conn, "send"); + + socket.push(data); + + expect(sendSpy).toHaveBeenCalledWith(json); + }); + + it("buffers data when not connected", function () { + socket.connect(); + socket.conn.readyState = 0; // connecting + + const sendSpy = jest + .spyOn(socket.conn, "send") + .mockImplementation(() => {}); + + expect(socket.sendBuffer.length).toBe(0); + + socket.push(data); + + expect(sendSpy).not.toHaveBeenCalledWith(json); + expect(socket.sendBuffer.length).toBe(1); + + const [callback] = socket.sendBuffer; + callback(); + expect(sendSpy).toHaveBeenCalledWith(json); + }); + }); + + describe("makeRef", function () { + beforeEach(function () { + socket = new Socket("/socket"); + }); + + it("returns next message ref", function () { + expect(socket.ref).toBe(0); + expect(socket.makeRef()).toBe("1"); + expect(socket.ref).toBe(1); + expect(socket.makeRef()).toBe("2"); + expect(socket.ref).toBe(2); + }); + + it("restarts for overflow", function () { + socket.ref = Number.MAX_SAFE_INTEGER + 1; + expect(socket.makeRef()).toBe("0"); + expect(socket.ref).toBe(0); + }); + }); + + describe("sendHeartbeat", function () { + beforeEach(function () { + socket = new Socket("/socket"); + socket.connect(); + }); + + it("closes socket when heartbeat is not ack'd within heartbeat window", function (done) { + jest.useFakeTimers(); + let closed = false; + socket.conn.readyState = 1; // open + socket.conn.close = () => (closed = true); + socket.sendHeartbeat(); + expect(closed).toBe(false); + + jest.advanceTimersByTime(10000); + expect(closed).toBe(false); - socket.sendHeartbeat() - expect(sendSpy).toHaveBeenCalledWith(data) - }) + jest.advanceTimersByTime(20010); + expect(closed).toBe(true); + + jest.useRealTimers(); + done(); + }); - it("no ops when not connected", function (){ - socket.conn.readyState = 0 // connecting + it("pushes heartbeat data when connected", function () { + socket.conn.readyState = 1; // open - const sendSpy = jest.spyOn(socket.conn, "send") - const data = encode({topic: "phoenix", event: "heartbeat", payload: {}, ref: "1"}) - - socket.sendHeartbeat() - expect(sendSpy).not.toHaveBeenCalledWith(data) - }) - }) - - describe("flushSendBuffer", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - socket.connect() - }) - - it("calls callbacks in buffer when connected", function (){ - socket.conn.readyState = 1 // open - const spy1 = jest.fn() - const spy2 = jest.fn() - socket.sendBuffer.push(spy1) - socket.sendBuffer.push(spy2) - - socket.flushSendBuffer() - - expect(spy1).toHaveBeenCalledTimes(1) - expect(spy2).toHaveBeenCalledTimes(1) - }) - - it("empties sendBuffer", function (){ - socket.conn.readyState = 1 // open - socket.sendBuffer.push(() => { }) - - socket.flushSendBuffer() - - expect(socket.sendBuffer.length).toBe(0) - }) - }) - - describe("onConnOpen", function (){ - let mockServer - - beforeAll(function (){ - mockServer = new WebSocketServer("wss://example.com/") - }) - - afterAll(function (done){ - mockServer.stop(() => done()) - }) - - beforeEach(function (){ + const sendSpy = jest.spyOn(socket.conn, "send"); + const data = '[null,"1","phoenix","heartbeat",{}]'; + + socket.sendHeartbeat(); + expect(sendSpy).toHaveBeenCalledWith(data); + }); + + it("no ops when not connected", function () { + socket.conn.readyState = 0; // connecting + + const sendSpy = jest.spyOn(socket.conn, "send"); + const data = encode({ + topic: "phoenix", + event: "heartbeat", + payload: {}, + ref: "1", + }); + + socket.sendHeartbeat(); + expect(sendSpy).not.toHaveBeenCalledWith(data); + }); + }); + + describe("flushSendBuffer", function () { + beforeEach(function () { + socket = new Socket("/socket"); + socket.connect(); + }); + + it("calls callbacks in buffer when connected", function () { + socket.conn.readyState = 1; // open + const spy1 = jest.fn(); + const spy2 = jest.fn(); + socket.sendBuffer.push(spy1); + socket.sendBuffer.push(spy2); + + socket.flushSendBuffer(); + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(1); + }); + + it("empties sendBuffer", function () { + socket.conn.readyState = 1; // open + socket.sendBuffer.push(() => {}); + + socket.flushSendBuffer(); + + expect(socket.sendBuffer.length).toBe(0); + }); + }); + + describe("onConnOpen", function () { + let mockServer; + + beforeAll(function () { + mockServer = new WebSocketServer("wss://example.com/"); + }); + + afterAll(function (done) { + mockServer.stop(() => done()); + }); + + beforeEach(function () { socket = new Socket("/socket", { - reconnectAfterMs: () => 100000 - }) - socket.connect() - }) - - it("flushes the send buffer", function (){ - socket.conn.readyState = 1 // open - const spy = jest.fn() - socket.sendBuffer.push(spy) - - socket.onConnOpen() - - expect(spy).toHaveBeenCalledTimes(1) - }) - - it("resets reconnectTimer", function (){ - const resetSpy = jest.spyOn(socket.reconnectTimer, "reset") - socket.onConnOpen() - expect(resetSpy).toHaveBeenCalledTimes(1) - }) - - it("triggers onOpen callback", function (){ - const spy = jest.fn() - socket.onOpen(spy) - socket.onConnOpen() - expect(spy).toHaveBeenCalledTimes(1) - }) - }) - - describe("onConnClose", function (){ - let mockServer - - beforeAll(function (){ - mockServer = new WebSocketServer("wss://example.com/") - }) - - afterAll(function (done){ - mockServer.stop(() => done()) - }) - - beforeEach(function (){ + reconnectAfterMs: () => 100000, + }); + socket.connect(); + }); + + it("flushes the send buffer", function () { + socket.conn.readyState = 1; // open + const spy = jest.fn(); + socket.sendBuffer.push(spy); + + socket.onConnOpen(); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it("resets reconnectTimer", function () { + const resetSpy = jest.spyOn(socket.reconnectTimer, "reset"); + socket.onConnOpen(); + expect(resetSpy).toHaveBeenCalledTimes(1); + }); + + it("triggers onOpen callback", function () { + const spy = jest.fn(); + socket.onOpen(spy); + socket.onConnOpen(); + expect(spy).toHaveBeenCalledTimes(1); + }); + }); + + describe("onConnClose", function () { + let mockServer; + + beforeAll(function () { + mockServer = new WebSocketServer("wss://example.com/"); + }); + + afterAll(function (done) { + mockServer.stop(() => done()); + }); + + beforeEach(function () { socket = new Socket("/socket", { - reconnectAfterMs: () => 100000 - }) - socket.connect() - }) - - it("does not schedule reconnectTimer if normal close", function (){ - const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout") - const event = {code: 1000} - socket.onConnClose(event) - expect(scheduleSpy).not.toHaveBeenCalled() - }) - - it("schedules reconnectTimer timeout if abnormal close", function (){ - const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout") - const event = {code: 1006} - socket.onConnClose(event) - expect(scheduleSpy).toHaveBeenCalledTimes(1) - }) - - it("does not schedule reconnectTimer timeout if normal close after explicit disconnect", function (){ - const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout") - socket.disconnect() - expect(scheduleSpy).not.toHaveBeenCalled() - }) - - it("schedules reconnectTimer timeout if not normal close", function (){ - const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout") - const event = {code: 1001} - socket.onConnClose(event) - expect(scheduleSpy).toHaveBeenCalledTimes(1) - }) - - it("schedules reconnectTimer timeout if connection cannot be made after a previous clean disconnect", function (done){ - const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout") + reconnectAfterMs: () => 100000, + }); + socket.connect(); + }); + + it("does not schedule reconnectTimer if normal close", function () { + const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout"); + const event = { code: 1000 }; + socket.onConnClose(event); + expect(scheduleSpy).not.toHaveBeenCalled(); + }); + + it("schedules reconnectTimer timeout if abnormal close", function () { + const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout"); + const event = { code: 1006 }; + socket.onConnClose(event); + expect(scheduleSpy).toHaveBeenCalledTimes(1); + }); + + it("does not schedule reconnectTimer timeout if normal close after explicit disconnect", function () { + const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout"); + socket.disconnect(); + expect(scheduleSpy).not.toHaveBeenCalled(); + }); + + it("schedules reconnectTimer timeout if not normal close", function () { + const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout"); + const event = { code: 1001 }; + socket.onConnClose(event); + expect(scheduleSpy).toHaveBeenCalledTimes(1); + }); + + it("schedules reconnectTimer timeout if connection cannot be made after a previous clean disconnect", function (done) { + const scheduleSpy = jest.spyOn(socket.reconnectTimer, "scheduleTimeout"); socket.disconnect(() => { - socket.connect() - const event = {code: 1001} - socket.onConnClose(event) - expect(scheduleSpy).toHaveBeenCalledTimes(1) - done() - }) - }) - - it("triggers onClose callback", function (){ - const spy = jest.fn() - socket.onClose(spy) - socket.onConnClose("event") - expect(spy).toHaveBeenCalledWith("event") - }) - - it("triggers channel error if joining", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join() - expect(channel.state).toBe("joining") - socket.onConnClose() - expect(triggerSpy).toHaveBeenCalledWith("phx_error") - }) - - it("triggers channel error if joined", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join().trigger("ok", {}) - expect(channel.state).toBe("joined") - socket.onConnClose() - expect(triggerSpy).toHaveBeenCalledWith("phx_error") - }) - - it("does not trigger channel error after leave", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join().trigger("ok", {}) - channel.leave() - expect(channel.state).toBe("closed") - socket.onConnClose() - expect(triggerSpy).not.toHaveBeenCalledWith("phx_error") - }) - - it("does not send heartbeat after explicit disconnect", function (done){ - jest.useFakeTimers() - const sendHeartbeatSpy = jest.spyOn(socket, "sendHeartbeat") - socket.onConnOpen() - socket.disconnect() - jest.advanceTimersByTime(30000) - expect(sendHeartbeatSpy).not.toHaveBeenCalled() - jest.useRealTimers() - done() - }) - - it("does not timeout the heartbeat after explicit disconnect", function (done){ - jest.useFakeTimers() - const heartbeatTimeoutSpy = jest.spyOn(socket, "heartbeatTimeout") - socket.onConnOpen() - socket.disconnect() - jest.advanceTimersByTime(60000) - expect(heartbeatTimeoutSpy).not.toHaveBeenCalled() - jest.useRealTimers() - done() - }) - }) - - describe("onConnError", function (){ - let mockServer - - beforeAll(function (){ - mockServer = new WebSocketServer("wss://example.com/") - }) - - afterAll(function (done){ - mockServer.stop(() => done()) - }) - - beforeEach(function (){ + socket.connect(); + const event = { code: 1001 }; + socket.onConnClose(event); + expect(scheduleSpy).toHaveBeenCalledTimes(1); + done(); + }); + }); + + it("triggers onClose callback", function () { + const spy = jest.fn(); + socket.onClose(spy); + socket.onConnClose("event"); + expect(spy).toHaveBeenCalledWith("event"); + }); + + it("triggers channel error if joining", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join(); + expect(channel.state).toBe("joining"); + socket.onConnClose(); + expect(triggerSpy).toHaveBeenCalledWith("phx_error", {}); + }); + + it("triggers channel error if joined", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join().trigger("ok", {}); + expect(channel.state).toBe("joined"); + socket.onConnClose(); + expect(triggerSpy).toHaveBeenCalledWith("phx_error", {}); + }); + + it("does not trigger channel error after leave", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join().trigger("ok", {}); + channel.leave(); + expect(channel.state).toBe("closed"); + socket.onConnClose(); + expect(triggerSpy).not.toHaveBeenCalledWith("phx_error", {}); + }); + + it("does not send heartbeat after explicit disconnect", function (done) { + jest.useFakeTimers(); + const sendHeartbeatSpy = jest.spyOn(socket, "sendHeartbeat"); + socket.onConnOpen(); + socket.disconnect(); + jest.advanceTimersByTime(30000); + expect(sendHeartbeatSpy).not.toHaveBeenCalled(); + jest.useRealTimers(); + done(); + }); + + it("does not timeout the heartbeat after explicit disconnect", function (done) { + jest.useFakeTimers(); + const heartbeatTimeoutSpy = jest.spyOn(socket, "heartbeatTimeout"); + socket.onConnOpen(); + socket.disconnect(); + jest.advanceTimersByTime(60000); + expect(heartbeatTimeoutSpy).not.toHaveBeenCalled(); + jest.useRealTimers(); + done(); + }); + }); + + describe("onConnError", function () { + let mockServer; + + beforeAll(function () { + mockServer = new WebSocketServer("wss://example.com/"); + }); + + afterAll(function (done) { + mockServer.stop(() => done()); + }); + + beforeEach(function () { socket = new Socket("/socket", { - reconnectAfterMs: () => 100000 - }) - socket.connect() - }) - - it("triggers onClose callback", function (){ - const spy = jest.fn() - socket.onError(spy) - socket.onConnError("error") - expect(spy).toHaveBeenCalledWith("error", expect.any(Function), 0) - }) - - it("triggers channel error if joining with open connection", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join() - socket.onConnOpen() - expect(channel.state).toBe("joining") - socket.onConnError("error") - expect(triggerSpy).toHaveBeenCalledWith("phx_error") - }) - - it("triggers channel error if joining with no connection", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join() - expect(channel.state).toBe("joining") - socket.onConnError("error") - expect(triggerSpy).toHaveBeenCalledWith("phx_error") - }) - - it("triggers channel error if joined", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join().trigger("ok", {}) - socket.onConnOpen() - expect(channel.state).toBe("joined") - - let connectionsCount = null - let transport = null + reconnectAfterMs: () => 100000, + }); + socket.connect(); + }); + + it("triggers onClose callback", function () { + const spy = jest.fn(); + socket.onError(spy); + socket.onConnError("error"); + expect(spy).toHaveBeenCalledWith("error", expect.any(Function), 0); + }); + + it("triggers channel error if joining with open connection", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join(); + socket.onConnOpen(); + expect(channel.state).toBe("joining"); + socket.onConnError("error"); + expect(triggerSpy).toHaveBeenCalledWith("phx_error", {}); + }); + + it("triggers channel error if joining with no connection", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join(); + expect(channel.state).toBe("joining"); + socket.onConnError("error"); + expect(triggerSpy).toHaveBeenCalledWith("phx_error", {}); + }); + + it("triggers channel error if joined", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join().trigger("ok", {}); + socket.onConnOpen(); + expect(channel.state).toBe("joined"); + + let connectionsCount = null; + let transport = null; socket.onError((error, erroredTransport, conns) => { - transport = erroredTransport - connectionsCount = conns - }) - - socket.onConnError("error") - - expect(transport).toBe(WebSocket) - expect(connectionsCount).toBe(1) - expect(triggerSpy).toHaveBeenCalledWith("phx_error") - }) - - it("does not trigger channel error after leave", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join().trigger("ok", {}) - channel.leave() - expect(channel.state).toBe("closed") - socket.onConnError("error") - expect(triggerSpy).not.toHaveBeenCalledWith("phx_error") - }) - - it("does not trigger channel error if transport replaced with no previous connection", function (){ - const channel = socket.channel("topic") - const triggerSpy = jest.spyOn(channel, "trigger") - channel.join() - expect(channel.state).toBe("joining") - - let connectionsCount = null - class FakeTransport { } + transport = erroredTransport; + connectionsCount = conns; + }); + + socket.onConnError("error"); + + expect(transport).toBe(WebSocket); + expect(connectionsCount).toBe(1); + expect(triggerSpy).toHaveBeenCalledWith("phx_error", {}); + }); + + it("does not trigger channel error after leave", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join().trigger("ok", {}); + channel.leave(); + expect(channel.state).toBe("closed"); + socket.onConnError("error"); + expect(triggerSpy).not.toHaveBeenCalledWith("phx_error"); + }); + + it("does not trigger channel error if transport replaced with no previous connection", function () { + const channel = socket.channel("topic"); + const triggerSpy = jest.spyOn(channel, "trigger"); + channel.join(); + expect(channel.state).toBe("joining"); + + let connectionsCount = null; + class FakeTransport {} socket.onError((error, transport, conns) => { - socket.replaceTransport(FakeTransport) - connectionsCount = conns - }) - socket.onConnError("error") + socket.replaceTransport(FakeTransport); + connectionsCount = conns; + }); + socket.onConnError("error"); - expect(connectionsCount).toBe(0) - expect(socket.transport).toBe(FakeTransport) - expect(triggerSpy).not.toHaveBeenCalledWith("phx_error") - }) - }) + expect(connectionsCount).toBe(0); + expect(socket.transport).toBe(FakeTransport); + expect(triggerSpy).not.toHaveBeenCalledWith("phx_error"); + }); + }); - describe("onConnMessage", function (){ - let mockServer + describe("onConnMessage", function () { + let mockServer; - beforeAll(function (){ - mockServer = new WebSocketServer("wss://example.com/") - }) + beforeAll(function () { + mockServer = new WebSocketServer("wss://example.com/"); + }); - afterAll(function (done){ - mockServer.stop(() => done()) - }) + afterAll(function (done) { + mockServer.stop(() => done()); + }); - beforeEach(function (){ + beforeEach(function () { socket = new Socket("/socket", { - reconnectAfterMs: () => 100000 - }) - socket.connect() - }) - - it("parses raw message and triggers channel event", function (){ - const message = encode({topic: "topic", event: "event", payload: "payload", ref: "ref"}) - const data = {data: message} - - const targetChannel = socket.channel("topic") - const otherChannel = socket.channel("off-topic") - - const targetSpy = jest.spyOn(targetChannel, "trigger") - const otherSpy = jest.spyOn(otherChannel, "trigger") - - socket.onConnMessage(data) - - expect(targetSpy).toHaveBeenCalledWith("event", "payload", "ref", null) - expect(targetSpy).toHaveBeenCalledTimes(1) - expect(otherSpy).toHaveBeenCalledTimes(0) - }) - - it("triggers onMessage callback", function (){ - const message = {"topic": "topic", "event": "event", "payload": "payload", "ref": "ref"} - const spy = jest.fn() - socket.onMessage(spy) - socket.onConnMessage({data: encode(message)}) + reconnectAfterMs: () => 100000, + }); + socket.connect(); + }); + + it("parses raw message and triggers channel event", function () { + const message = encode({ + topic: "topic", + event: "event", + payload: "payload", + ref: "ref", + }); + const data = { data: message }; + + const targetChannel = socket.channel("topic"); + const otherChannel = socket.channel("off-topic"); + + const targetSpy = jest.spyOn(targetChannel, "trigger"); + const otherSpy = jest.spyOn(otherChannel, "trigger"); + + socket.onConnMessage(data); + + expect(targetSpy).toHaveBeenCalledWith("event", "payload", "ref", null); + expect(targetSpy).toHaveBeenCalledTimes(1); + expect(otherSpy).toHaveBeenCalledTimes(0); + }); + + it("triggers onMessage callback", function () { + const message = { + topic: "topic", + event: "event", + payload: "payload", + ref: "ref", + }; + const spy = jest.fn(); + socket.onMessage(spy); + socket.onConnMessage({ data: encode(message) }); expect(spy).toHaveBeenCalledWith({ - "topic": "topic", - "event": "event", - "payload": "payload", - "ref": "ref", - "join_ref": null - }) - }) - }) - - describe("ping", function (){ - beforeEach(function (){ - socket = new Socket("/socket") - socket.connect() - }) - - it("pushes when connected", function (done){ - let latency = 100 - socket.conn.readyState = 1 // open - expect(socket.isConnected()).toBe(true) + topic: "topic", + event: "event", + payload: "payload", + ref: "ref", + join_ref: null, + }); + }); + }); + + describe("ping", function () { + beforeEach(function () { + socket = new Socket("/socket"); + socket.connect(); + }); + + it("pushes when connected", function (done) { + let latency = 100; + socket.conn.readyState = 1; // open + expect(socket.isConnected()).toBe(true); socket.push = (msg) => { setTimeout(() => { - socket.onConnMessage({data: encode({topic: "phoenix", event: "phx_reply", ref: msg.ref})}) - }, latency) - } - - const result = socket.ping(rtt => { + socket.onConnMessage({ + data: encode({ + topic: "phoenix", + event: "phx_reply", + ref: msg.ref, + }), + }); + }, latency); + }; + + const result = socket.ping((rtt) => { // if we're unlucky we could also receive 99 as rtt, so let's be generous - expect(rtt >= (latency - 10)).toBe(true) - done() - }) - expect(result).toBe(true) - }) - - it("returns false when disconnected", function (){ - socket.conn.readyState = 0 - expect(socket.isConnected()).toBe(false) - const result = socket.ping(_rtt => true) - expect(result).toBe(false) - }) - }) - - describe("custom encoder and decoder", function (){ - - it("encodes to JSON array by default", function (){ - socket = new Socket("/socket") - const payload = {topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}} - - socket.encode(payload, encoded => { - expect(encoded).toBe("[\"1\",\"2\",\"topic\",\"join\",{\"foo\":\"bar\"}]") - }) - }) - - it("allows custom encoding when using WebSocket transport", function (){ - const encoder = (payload, callback) => callback("encode works") - socket = new Socket("/socket", {transport: WebSocket, encode: encoder}) - - socket.encode({foo: "bar"}, encoded => { - expect(encoded).toBe("encode works") - }) - }) - - it("forces JSON encoding when using LongPoll transport", function (){ - const encoder = (payload, callback) => callback("encode works") - socket = new Socket("/socket", {transport: LongPoll, encode: encoder}) - const payload = {topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}} - - socket.encode(payload, encoded => { - expect(encoded).toBe("[\"1\",\"2\",\"topic\",\"join\",{\"foo\":\"bar\"}]") - }) - }) - - it("decodes JSON by default", function (){ - socket = new Socket("/socket") - const encoded = "[\"1\",\"2\",\"topic\",\"join\",{\"foo\":\"bar\"}]" - - socket.decode(encoded, decoded => { - expect(decoded).toEqual({topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}}) - }) - }) - - it("allows custom decoding when using WebSocket transport", function (){ - const decoder = (payload, callback) => callback("decode works") - socket = new Socket("/socket", {transport: WebSocket, decode: decoder}) - - socket.decode("...esoteric format...", decoded => { - expect(decoded).toBe("decode works") - }) - }) - - it("forces JSON decoding when using LongPoll transport", function (){ - const decoder = (payload, callback) => callback("decode works") - socket = new Socket("/socket", {transport: LongPoll, decode: decoder}) - const payload = {topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}} - - socket.decode("[\"1\",\"2\",\"topic\",\"join\",{\"foo\":\"bar\"}]", decoded => { - expect(decoded).toEqual(payload) - }) - }) - }) -}) - -window.XMLHttpRequest = jest.fn() -window.WebSocket = WebSocket + expect(rtt >= latency - 10).toBe(true); + done(); + }); + expect(result).toBe(true); + }); + + it("returns false when disconnected", function () { + socket.conn.readyState = 0; + expect(socket.isConnected()).toBe(false); + const result = socket.ping((_rtt) => true); + expect(result).toBe(false); + }); + }); + + describe("custom encoder and decoder", function () { + it("encodes to JSON array by default", function () { + socket = new Socket("/socket"); + const payload = { + topic: "topic", + ref: "2", + join_ref: "1", + event: "join", + payload: { foo: "bar" }, + }; + + socket.encode(payload, (encoded) => { + expect(encoded).toBe('["1","2","topic","join",{"foo":"bar"}]'); + }); + }); + + it("allows custom encoding when using WebSocket transport", function () { + const encoder = (payload, callback) => callback("encode works"); + socket = new Socket("/socket", { transport: WebSocket, encode: encoder }); + + socket.encode({ foo: "bar" }, (encoded) => { + expect(encoded).toBe("encode works"); + }); + }); + + it("forces JSON encoding when using LongPoll transport", function () { + const encoder = (payload, callback) => callback("encode works"); + socket = new Socket("/socket", { transport: LongPoll, encode: encoder }); + const payload = { + topic: "topic", + ref: "2", + join_ref: "1", + event: "join", + payload: { foo: "bar" }, + }; + + socket.encode(payload, (encoded) => { + expect(encoded).toBe('["1","2","topic","join",{"foo":"bar"}]'); + }); + }); + + it("decodes JSON by default", function () { + socket = new Socket("/socket"); + const encoded = '["1","2","topic","join",{"foo":"bar"}]'; + + socket.decode(encoded, (decoded) => { + expect(decoded).toEqual({ + topic: "topic", + ref: "2", + join_ref: "1", + event: "join", + payload: { foo: "bar" }, + }); + }); + }); + + it("allows custom decoding when using WebSocket transport", function () { + const decoder = (payload, callback) => callback("decode works"); + socket = new Socket("/socket", { transport: WebSocket, decode: decoder }); + + socket.decode("...esoteric format...", (decoded) => { + expect(decoded).toBe("decode works"); + }); + }); + + it("forces JSON decoding when using LongPoll transport", function () { + const decoder = (payload, callback) => callback("decode works"); + socket = new Socket("/socket", { transport: LongPoll, decode: decoder }); + const payload = { + topic: "topic", + ref: "2", + join_ref: "1", + event: "join", + payload: { foo: "bar" }, + }; + + socket.decode('["1","2","topic","join",{"foo":"bar"}]', (decoded) => { + expect(decoded).toEqual(payload); + }); + }); + }); +}); + +window.XMLHttpRequest = jest.fn(); +window.WebSocket = WebSocket; diff --git a/config/config.exs b/config/config.exs index 9656d8e51a..f00e1f4b8f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,7 +12,7 @@ config :phoenix, if Mix.env() == :dev do esbuild = fn args -> [ - args: ~w(./js/phoenix --bundle) ++ args, + args: ~w(./dist/phoenix --bundle) ++ args, cd: Path.expand("../assets", __DIR__), env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} ] diff --git a/eslint.config.mjs b/eslint.config.mjs index cff4702fa5..4a3d6bce4c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,14 +1,16 @@ -import jest from "eslint-plugin-jest" -import js from "@eslint/js" -import stylistic from "@stylistic/eslint-plugin" +import jest from "eslint-plugin-jest"; +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; -export default [ +export default tseslint.config([ { // eslint config is very unintuitive; they will match an js file in any // directory by default and you can only expand this; // moreover, to have a global ignore, it must be specified without // any other key as a separate object... ignores: [ + "assets/dist/", "integration_test/", "installer/", "doc/", @@ -16,19 +18,19 @@ export default [ "coverage/", "priv/", "tmp/", - "test/" + "test/", ], }, { - ...js.configs.recommended, + extends: [js.configs.recommended, ...tseslint.configs.recommended], plugins: { jest, - "@stylistic": stylistic }, languageOptions: { globals: { + ...globals.browser, ...jest.environments.globals.globals, global: "writable", }, @@ -38,63 +40,16 @@ export default [ }, rules: { - "@stylistic/indent": ["error", 2, { - SwitchCase: 1, - }], - - "@stylistic/linebreak-style": ["error", "unix"], - "@stylistic/quotes": ["error", "double"], - "@stylistic/semi": ["error", "never"], - - "@stylistic/object-curly-spacing": ["error", "never", { - objectsInObjects: false, - arraysInObjects: false, - }], - - "@stylistic/array-bracket-spacing": ["error", "never"], - - "@stylistic/comma-spacing": ["error", { - before: false, - after: true, - }], - - "@stylistic/computed-property-spacing": ["error", "never"], - - "@stylistic/space-before-blocks": ["error", { - functions: "never", - keywords: "never", - classes: "always", - }], - - "@stylistic/keyword-spacing": ["error", { - overrides: { - if: { - after: false, - }, - - for: { - after: false, - }, - - while: { - after: false, - }, - - switch: { - after: false, - }, + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", }, - }], - - "@stylistic/eol-last": ["error", "always"], - - "no-unused-vars": ["error", { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - }], - - "no-useless-escape": "off", - "no-cond-assign": "off", - "no-case-declarations": "off", + ], + + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-explicit-any": "off", }, - }] + }, +]); diff --git a/jest.config.js b/jest.config.js index 2138609ae6..47aef6d4fb 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,9 @@ -/* - * For a detailed explanation regarding each configuration property, visit: - * https://jestjs.io/docs/configuration - */ +import { createDefaultPreset } from "ts-jest"; -module.exports = { +const tsJestTransformCfg = createDefaultPreset().transform; + +/** @type {import("jest").Config} **/ +export default { // Automatically clear mock calls and instances between every test clearMocks: true, @@ -17,11 +17,16 @@ module.exports = { // The test environment that will be used for testing testEnvironment: "jest-environment-jsdom", - + testEnvironmentOptions: { - url: "https://example.com" + url: "https://example.com", }, // The regexp pattern or array of patterns that Jest uses to detect test files - testRegex: "/assets/test/.*_test\\.js$", -} + testRegex: "/assets/test/.*_test\\.(js|ts)$", + + transform: { + ...tsJestTransformCfg, + "\\.jsx?$": "babel-jest", + }, +}; diff --git a/mix.exs b/mix.exs index 99639c905c..09960cb83a 100644 --- a/mix.exs +++ b/mix.exs @@ -256,7 +256,13 @@ defmodule Phoenix.MixProject do defp aliases do [ docs: ["docs", &generate_js_docs/1], - "assets.build": ["esbuild module", "esbuild cdn", "esbuild cdn_min", "esbuild main"], + "assets.build": [ + "cmd npm run build", + "esbuild module", + "esbuild cdn", + "esbuild cdn_min", + "esbuild main" + ], "assets.watch": "esbuild module --watch", "archive.build": &raise_on_archive_build/1, # copy core_components before compiling / publishing diff --git a/package-lock.json b/package-lock.json index 79929a4fbb..2f83a71462 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,32 @@ { "name": "phoenix", - "version": "1.8.0-rc.3", + "version": "1.8.0-rc.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "phoenix", - "version": "1.8.0-rc.3", + "version": "1.8.0-rc.4", "license": "MIT", "devDependencies": { "@babel/cli": "7.28.0", "@babel/core": "7.28.0", "@babel/preset-env": "7.28.0", "@eslint/js": "^9.28.0", - "@stylistic/eslint-plugin": "^5.0.0", - "documentation": "^14.0.3", + "@types/jest": "^30.0.0", + "@types/node": "^24.0.14", "eslint": "9.31.0", "eslint-plugin-jest": "29.0.1", + "globals": "^16.3.0", "jest": "^30.0.0", "jest-environment-jsdom": "^30.0.0", "jsdom": "^26.1.0", - "mock-socket": "^9.3.1" + "mock-socket": "^9.3.1", + "prettier": "^3.6.2", + "ts-jest": "^29.4.0", + "typedoc": "^0.28.7", + "typescript": "^5.8.3", + "typescript-eslint": "^8.37.0" } }, "node_modules/@ampproject/remapping": { @@ -2106,6 +2112,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.8.0.tgz", + "integrity": "sha512-tloLVqvvoyv636PilYZwNhCmZ+xxgRicysMvpKdZ4Y6+9IH6v4lp7GodbDDncApNQjflwTSnXuYQoe3el5C59w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.8.0", + "@shikijs/langs": "^3.8.0", + "@shikijs/themes": "^3.8.0", + "@shikijs/types": "^3.8.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -3133,6 +3153,65 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.8.0.tgz", + "integrity": "sha512-Tx7kR0oFzqa+rY7t80LjN8ZVtHO3a4+33EUnBVx2qYP3fGxoI9H0bvnln5ySelz9SIUTsS0/Qn+9dg5zcUMsUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.8.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.8.0.tgz", + "integrity": "sha512-mfGYuUgjQ5GgXinB5spjGlBVhG2crKRpKkfADlp8r9k/XvZhtNXxyOToSnCEnF0QNiZnJjlt5MmU9PmhRdwAbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.8.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.8.0.tgz", + "integrity": "sha512-yaZiLuyO23sXe16JFU76KyUMTZCJi4EMQKIrdQt7okoTzI4yAaJhVXT2Uy4k8yBIEFRiia5dtD7gC1t8m6y3oQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.8.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.8.0.tgz", + "integrity": "sha512-I/b/aNg0rP+kznVDo7s3UK8jMcqEGTtoPDdQ+JlQ2bcJIyu/e2iRvl42GLIDMK03/W1YOHOuhlhQ7aM+XbKUeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/types/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.34.36", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.36.tgz", @@ -3158,53 +3237,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@stylistic/eslint-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.1.0.tgz", - "integrity": "sha512-TJRJul4u/lmry5N/kyCU+7RWWOk0wyXN+BncRlDYBqpLFnzXkd7QGVfN7KewarFIXv0IX0jSF/Ksu7aHWEDeuw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/types": "^8.34.1", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=9.0.0" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", - "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -3256,16 +3288,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -3273,23 +3295,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/extend": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.4.tgz", - "integrity": "sha512-ArMouDUTJEz1SQRpFsT2rIw7DeqICFv5aaVzLSIYMYQSLcwcGOfT3VyglQs/p7K3F7fT4zxr0NWxYZIdifD6dA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", - "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3317,6 +3322,17 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, "node_modules/@types/jsdom": { "version": "21.1.7", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", @@ -3335,47 +3351,16 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mdast": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", - "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "24.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", + "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/parse5": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", - "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -3383,13 +3368,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/supports-color": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", - "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -3420,15 +3398,80 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", + "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.37.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", + "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", + "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "engines": { @@ -3443,14 +3486,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", + "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3461,11 +3504,35 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", + "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", + "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3474,13 +3541,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", + "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", "dev": true, "license": "MIT", "engines": { @@ -3492,16 +3560,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", + "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3521,9 +3589,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3560,16 +3628,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", + "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3584,14 +3652,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", + "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.37.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3857,72 +3925,6 @@ "win32" ] }, - "node_modules/@vue/compiler-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", - "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/shared": "3.5.16", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", - "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@vue/compiler-core": "3.5.16", - "@vue/shared": "3.5.16" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", - "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/compiler-core": "3.5.16", - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.17", - "postcss": "^8.5.3", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", - "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/shared": "3.5.16" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", - "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -4032,6 +4034,13 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/babel-jest": { "version": "30.0.4", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.4.tgz", @@ -4204,17 +4213,6 @@ "@babel/core": "^7.11.0" } }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4228,6 +4226,7 @@ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" }, @@ -4292,6 +4291,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -4347,30 +4359,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -4380,57 +4368,25 @@ "node": ">=10" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, "funding": { "url": "https://paulmillr.com/funding/" }, @@ -4532,17 +4488,6 @@ "dev": true, "license": "MIT" }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -4623,14 +4568,6 @@ "node": ">=18" } }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -4656,20 +4593,6 @@ "dev": true, "license": "MIT" }, - "node_modules/decode-named-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", - "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -4700,16 +4623,6 @@ "node": ">=0.10.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -4719,136 +4632,29 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } + "license": "MIT" }, - "node_modules/doctrine-temporary-fork": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", - "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/documentation": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.3.tgz", - "integrity": "sha512-B7cAviVKN9Rw7Ofd+9grhVuxiHwly6Ieh+d/ceMw8UdBOv/irkuwnDEJP8tq0wgdLJDUVuIkovV+AX9mTrZFxg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@babel/core": "^7.18.10", - "@babel/generator": "^7.18.10", - "@babel/parser": "^7.18.11", - "@babel/traverse": "^7.18.11", - "@babel/types": "^7.18.10", - "chalk": "^5.0.1", - "chokidar": "^3.5.3", - "diff": "^5.1.0", - "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^13.1.0", - "github-slugger": "1.4.0", - "glob": "^8.0.3", - "globals-docs": "^2.4.1", - "highlight.js": "^11.6.0", - "ini": "^3.0.0", - "js-yaml": "^4.1.0", - "konan": "^2.1.1", - "lodash": "^4.17.21", - "mdast-util-find-and-replace": "^2.2.1", - "mdast-util-inject": "^1.1.0", - "micromark-util-character": "^1.1.0", - "parse-filepath": "^1.0.2", - "pify": "^6.0.0", - "read-pkg-up": "^9.1.0", - "remark": "^14.0.2", - "remark-gfm": "^3.0.1", - "remark-html": "^15.0.1", - "remark-reference-links": "^6.0.1", - "remark-toc": "^8.0.1", - "resolve": "^1.22.1", - "strip-json-comments": "^5.0.0", - "unist-builder": "^3.0.0", - "unist-util-visit": "^4.1.0", - "vfile": "^5.3.4", - "vfile-reporter": "^7.0.4", - "vfile-sort": "^3.0.0", - "yargs": "^17.5.1" + "jake": "^10.8.5" }, "bin": { - "documentation": "bin/documentation.js" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@vue/compiler-sfc": "^3.2.37", - "vue-template-compiler": "^2.7.8" - } - }, - "node_modules/documentation/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/documentation/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/documentation/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" + "ejs": "bin/cli.js" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/electron-to-chromium": { "version": "1.5.162", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.162.tgz", @@ -4881,7 +4687,6 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", - "optional": true, "engines": { "node": ">=0.12" }, @@ -5165,14 +4970,6 @@ "node": ">=4.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5232,13 +5029,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5309,6 +5099,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5468,34 +5291,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", - "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-ssh": "^1.4.0", - "parse-url": "^8.1.0" - } - }, - "node_modules/git-url-parse": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.1.tgz", - "integrity": "sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "git-up": "^7.0.0" - } - }, - "node_modules/github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", - "dev": true, - "license": "ISC" - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5531,12 +5326,18 @@ "node": ">= 6" } }, - "node_modules/globals-docs": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz", - "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/graceful-fs": { "version": "4.2.11", @@ -5545,6 +5346,13 @@ "dev": true, "license": "ISC" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5568,295 +5376,79 @@ "node": ">= 0.4" } }, - "node_modules/hast-util-from-parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", - "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0", - "hastscript": "^7.0.0", - "property-information": "^6.0.0", - "vfile": "^5.0.0", - "vfile-location": "^4.0.0", - "web-namespaces": "^2.0.0" + "whatwg-encoding": "^3.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=18" } }, - "node_modules/hast-util-parse-selector": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", - "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^2.0.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 14" } }, - "node_modules/hast-util-raw": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", - "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "hast-util-from-parse5": "^7.0.0", - "hast-util-to-parse5": "^7.0.0", - "html-void-elements": "^2.0.0", - "parse5": "^6.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 14" } }, - "node_modules/hast-util-raw/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=10.17.0" + } }, - "node_modules/hast-util-sanitize": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz", - "integrity": "sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==", + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^2.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/hast-util-to-html": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", - "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-raw": "^7.0.0", - "hast-util-whitespace": "^2.0.0", - "html-void-elements": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", - "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", - "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", - "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^3.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/html-void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", - "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -5928,30 +5520,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ini": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", - "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5965,6 +5533,7 @@ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -5972,30 +5541,6 @@ "node": ">=8" } }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -6064,19 +5609,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -6084,29 +5616,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-ssh": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", - "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "protocols": "^2.0.1" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6119,29 +5628,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6269,44 +5755,96 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", - "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==", + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jest/core": "30.0.4", - "@jest/types": "30.0.1", - "import-local": "^3.2.0", - "jest-cli": "30.0.4" + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, "bin": { - "jest": "bin/jest.js" + "jake": "bin/cli.js" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=10" } }, - "node_modules/jest-changed-files": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.2.tgz", - "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==", + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.0.2", - "p-limit": "^3.1.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", + "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==", + "dev": true, + "dependencies": { + "@jest/core": "30.0.4", + "@jest/types": "30.0.1", + "import-local": "^3.2.0", + "jest-cli": "30.0.4" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.2.tgz", + "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==", + "dev": true, + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.0.2", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { @@ -7743,17 +7281,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/konan": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", - "integrity": "sha512-7ZhYV84UzJ0PR/RJnnsMZcAbn+kLasJhVNWsu8ZyVEJYRpGA5XESQ9d/7zOa08U0Ou4cmB++hMNY/3OSV9KIbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.10.5", - "@babel/traverse": "^7.10.5" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -7784,6 +7311,16 @@ "dev": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7800,1046 +7337,133 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdast-util-definitions": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", - "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", - "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", - "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-gfm-autolink-literal": "^1.0.0", - "mdast-util-gfm-footnote": "^1.0.0", - "mdast-util-gfm-strikethrough": "^1.0.0", - "mdast-util-gfm-table": "^1.0.0", - "mdast-util-gfm-task-list-item": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", - "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "ccount": "^2.0.0", - "mdast-util-find-and-replace": "^2.0.0", - "micromark-util-character": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", - "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.3.0", - "micromark-util-normalize-identifier": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", - "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", - "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-to-markdown": "^1.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", - "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-inject": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", - "integrity": "sha512-CcJ0mHa36QYumDKiZ2OIR+ClhfOM7zIzN+Wfy8tRZ1hpH9DKLCS+Mh4DyK5bCxzE9uxMWcbIpeNFWsg1zrj/2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdast-util-to-string": "^1.0.0" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", - "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", - "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-definitions": "^5.0.0", - "micromark-util-sanitize-uri": "^1.1.0", - "trim-lines": "^3.0.0", - "unist-util-generated": "^2.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", - "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", - "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-toc": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.1.tgz", - "integrity": "sha512-Er21728Kow8hehecK2GZtb7Ny3omcoPUVrmObiSUwmoRYVZaXLR751QROEFjR8W/vAQdHMLj49Lz20J55XaNpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/extend": "^3.0.0", - "@types/mdast": "^3.0.0", - "extend": "^3.0.0", - "github-slugger": "^2.0.0", - "mdast-util-to-string": "^3.1.0", - "unist-util-is": "^5.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-toc/node_modules/github-slugger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "dev": true, - "license": "ISC" - }, - "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", - "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^1.0.0", - "micromark-extension-gfm-footnote": "^1.0.0", - "micromark-extension-gfm-strikethrough": "^1.0.0", - "micromark-extension-gfm-table": "^1.0.0", - "micromark-extension-gfm-tagfilter": "^1.0.0", - "micromark-extension-gfm-task-list-item": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", - "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", - "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-core-commonmark": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", - "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", - "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", - "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", - "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", - "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", - "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true }, - "node_modules/micromark-util-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", - "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT" }, - "node_modules/micromark-util-html-tag-name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", - "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT" }, - "node_modules/micromark-util-normalize-identifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", - "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", + "license": "ISC", "dependencies": { - "micromark-util-symbol": "^1.0.0" + "yallist": "^3.0.2" } }, - "node_modules/micromark-util-resolve-all": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", - "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "micromark-util-types": "^1.0.0" + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" } }, - "node_modules/micromark-util-sanitize-uri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", - "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "node_modules/make-dir/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" + "tmpl": "1.0.5" } }, - "node_modules/micromark-util-subtokenize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", - "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT" }, - "node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 8" + } }, "node_modules/micromatch": { "version": "4.0.8", @@ -8896,16 +7520,6 @@ "node": ">= 8" } }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -8913,26 +7527,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "optional": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/napi-postinstall": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.0.tgz", @@ -8968,35 +7562,6 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9129,21 +7694,6 @@ "node": ">=6" } }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -9163,26 +7713,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-path": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", - "integrity": "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "protocols": "^2.0.0" - } - }, - "node_modules/parse-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", - "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-path": "^7.0.0" - } - }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -9246,29 +7776,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -9311,19 +7818,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", - "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -9397,36 +7891,6 @@ "node": ">=8" } }, - "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9437,6 +7901,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", @@ -9452,28 +7932,20 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/protocols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", - "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", - "dev": true, - "license": "MIT" + "engines": { + "node": ">=6" + } }, - "node_modules/punycode": { + "node_modules/punycode.js": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, "license": "MIT", "engines": { @@ -9524,163 +7996,13 @@ "dev": true, "license": "MIT" }, - "node_modules/read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -9759,122 +8081,6 @@ "node": ">=6" } }, - "node_modules/remark": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz", - "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "remark-parse": "^10.0.0", - "remark-stringify": "^10.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", - "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-gfm": "^2.0.0", - "micromark-extension-gfm": "^2.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-html": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.2.tgz", - "integrity": "sha512-/CIOI7wzHJzsh48AiuIyIe1clxVkUtreul73zcCXLub0FmnevQE0UMFDQm7NUx8/3rl/4zCshlMfqBdWScQthw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "hast-util-sanitize": "^4.0.0", - "hast-util-to-html": "^8.0.0", - "mdast-util-to-hast": "^12.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", - "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-reference-links": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-6.0.1.tgz", - "integrity": "sha512-34wY2C6HXSuKVTRtyJJwefkUD8zBOZOSHFZ4aSTnU2F656gr9WeuQ2dL6IJDK3NPd2F6xKF2t4XXcQY9MygAXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "unified": "^10.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz", - "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-toc": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-8.0.1.tgz", - "integrity": "sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-toc": "^6.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9979,19 +8185,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10070,17 +8263,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -10091,53 +8273,6 @@ "source-map": "^0.6.0" } }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -10248,21 +8383,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10307,19 +8427,6 @@ "node": ">=6" } }, - "node_modules/strip-json-comments": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.2.tgz", - "integrity": "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10447,39 +8554,96 @@ "node": ">=18" } }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", + "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } } }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=18.12" + "node": ">=16" }, - "peerDependencies": { - "typescript": ">=4.8.4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/tslib": { @@ -10523,13 +8687,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedoc": { + "version": "0.28.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.7.tgz", + "integrity": "sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.7.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.0" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10538,20 +8751,41 @@ "node": ">=14.17" } }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "node_modules/typescript-eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz", + "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.37.0", + "@typescript-eslint/parser": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true, "license": "MIT" }, @@ -10599,124 +8833,6 @@ "node": ">=4" } }, - "node_modules/unified": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", - "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "bail": "^2.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-builder": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz", - "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-generated": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", - "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", - "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", - "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", - "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unrs-resolver": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.0.tgz", @@ -10792,35 +8908,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uvu": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - }, - "bin": { - "uvu": "bin.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/uvu/node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -10835,140 +8922,6 @@ "node": ">=10.12.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vfile": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", - "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", - "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "vfile": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", - "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-reporter": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.5.tgz", - "integrity": "sha512-NdWWXkv6gcd7AZMvDomlQbK3MqFWL1RlGzMn++/O2TI+68+nqxCPTvLugdOtfSzXmjh+xUyhp07HhlrbJjT+mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/supports-color": "^8.0.0", - "string-width": "^5.0.0", - "supports-color": "^9.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile": "^5.0.0", - "vfile-message": "^3.0.0", - "vfile-sort": "^3.0.0", - "vfile-statistics": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-reporter/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/vfile-sort": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.1.tgz", - "integrity": "sha512-1os1733XY6y0D5x0ugqSeaVJm9lYgj0j5qdcZQFyxlZOSy1jYarL77lLyb5gK4Wqr1d5OxmuyflSO3zKyFnTFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "vfile": "^5.0.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-statistics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.1.tgz", - "integrity": "sha512-W6dkECZmP32EG/l+dp2jCLdYzmnDBIw6jwiLZSER81oR5AHRcVqL+k3Z+pfH1R73le6ayDkJRMk0sutj1bMVeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "vfile": "^5.0.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vue-template-compiler": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", - "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -10991,17 +8944,6 @@ "makeerror": "1.0.12" } }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -11272,6 +9214,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -11335,17 +9290,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } } } } diff --git a/package.json b/package.json index 5d56b333b5..90a48d2472 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.8.0-rc.4", "description": "The official JavaScript client for the Phoenix web framework.", "license": "MIT", + "types": "./assets/dist/phoenix/index.d.ts", "module": "./priv/static/phoenix.mjs", "main": "./priv/static/phoenix.cjs.js", "unpkg": "./priv/static/phoenix.min.js", @@ -21,6 +22,7 @@ "LICENSE.md", "package.json", "priv/static/*", + "assets/dist/phoenix/*", "assets/js/phoenix/*" ], "devDependencies": { @@ -28,19 +30,30 @@ "@babel/core": "7.28.0", "@babel/preset-env": "7.28.0", "@eslint/js": "^9.28.0", - "@stylistic/eslint-plugin": "^5.0.0", - "documentation": "^14.0.3", + "@types/jest": "^30.0.0", + "@types/node": "^24.0.14", "eslint": "9.31.0", "eslint-plugin-jest": "29.0.1", + "globals": "^16.3.0", "jest": "^30.0.0", "jest-environment-jsdom": "^30.0.0", "jsdom": "^26.1.0", - "mock-socket": "^9.3.1" + "mock-socket": "^9.3.1", + "prettier": "^3.6.2", + "ts-jest": "^29.4.0", + "typedoc": "^0.28.7", + "typescript": "^5.8.3", + "typescript-eslint": "^8.37.0" }, "scripts": { + "build": "tsc", + "build:watch": "tsc --watch", "test": "jest", "test.coverage": "jest --coverage", "test.watch": "jest --watch", - "docs": "documentation build assets/js/phoenix/index.js -f html -o doc/js" + "docs": "typedoc", + "lint": "eslint --fix", + "format": "prettier --write assets --log-level warn", + "format.check": "prettier --check assets --log-level warn" } } diff --git a/priv/static/phoenix.cjs.js b/priv/static/phoenix.cjs.js index a046843ae4..e1971c1193 100644 --- a/priv/static/phoenix.cjs.js +++ b/priv/static/phoenix.cjs.js @@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => { }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); -// js/phoenix/index.js +// dist/phoenix/index.js var phoenix_exports = {}; __export(phoenix_exports, { Channel: () => Channel, @@ -27,24 +27,26 @@ __export(phoenix_exports, { }); module.exports = __toCommonJS(phoenix_exports); -// js/phoenix/utils.js -var closure = (value) => { +// dist/phoenix/utils.js +function closure(value) { if (typeof value === "function") { return value; } else { - let closure2 = function() { - return value; - }; - return closure2; + return () => value; } -}; +} -// js/phoenix/constants.js +// dist/phoenix/constants.js var globalSelf = typeof self !== "undefined" ? self : null; var phxWindow = typeof window !== "undefined" ? window : null; var global = globalSelf || phxWindow || globalThis; var DEFAULT_VSN = "2.0.0"; -var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; +var SOCKET_STATES = { + connecting: 0, + open: 1, + closing: 2, + closed: 3 +}; var DEFAULT_TIMEOUT = 1e4; var WS_CLOSE_NORMAL = 1e3; var CHANNEL_STATES = { @@ -70,23 +72,22 @@ var XHR_STATES = { }; var AUTH_TOKEN_PREFIX = "base64url.bearer.phx."; -// js/phoenix/push.js +// dist/phoenix/push.js var Push = class { constructor(channel, event, payload, timeout) { this.channel = channel; this.event = event; - this.payload = payload || function() { - return {}; - }; + this.payload = typeof payload === "function" ? payload : () => payload || {}; this.receivedResp = null; this.timeout = timeout; this.timeoutTimer = null; this.recHooks = []; this.sent = false; + this.ref = null; + this.refEvent = null; } /** - * - * @param {number} timeout + * Resend the push with a new timeout */ resend(timeout) { this.timeout = timeout; @@ -94,7 +95,7 @@ var Push = class { this.send(); } /** - * + * Send the push */ send() { if (this.hasReceived("timeout")) { @@ -111,9 +112,7 @@ var Push = class { }); } /** - * - * @param {*} status - * @param {*} callback + * Register a callback for a specific response status */ receive(status, callback) { if (this.hasReceived(status)) { @@ -151,8 +150,10 @@ var Push = class { * @private */ cancelTimeout() { - clearTimeout(this.timeoutTimer); - this.timeoutTimer = null; + if (this.timeoutTimer !== null) { + clearTimeout(this.timeoutTimer); + this.timeoutTimer = null; + } } /** * @private @@ -187,7 +188,7 @@ var Push = class { } }; -// js/phoenix/timer.js +// dist/phoenix/timer.js var Timer = class { constructor(callback, timerCalc) { this.callback = callback; @@ -197,13 +198,17 @@ var Timer = class { } reset() { this.tries = 0; - clearTimeout(this.timer); + if (this.timer !== null) { + clearTimeout(this.timer); + } } /** * Cancels any previous scheduleTimeout and schedules callback */ scheduleTimeout() { - clearTimeout(this.timer); + if (this.timer !== null) { + clearTimeout(this.timer); + } this.timer = setTimeout(() => { this.tries = this.tries + 1; this.callback(); @@ -211,7 +216,7 @@ var Timer = class { } }; -// js/phoenix/channel.js +// dist/phoenix/channel.js var Channel = class { constructor(topic, params, socket) { this.state = CHANNEL_STATES.closed; @@ -231,14 +236,12 @@ var Channel = class { } }, this.socket.rejoinAfterMs); this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset())); - this.stateChangeRefs.push( - this.socket.onOpen(() => { - this.rejoinTimer.reset(); - if (this.isErrored()) { - this.rejoin(); - } - }) - ); + this.stateChangeRefs.push(this.socket.onOpen(() => { + this.rejoinTimer.reset(); + if (this.isErrored()) { + this.rejoin(); + } + })); this.joinPush.receive("ok", () => { this.state = CHANNEL_STATES.joined; this.rejoinTimer.reset(); @@ -272,7 +275,7 @@ var Channel = class { this.joinPush.receive("timeout", () => { if (this.socket.hasLogger()) this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout); - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout); + const leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout); leavePush.send(); this.state = CHANNEL_STATES.errored; this.joinPush.reset(); @@ -286,8 +289,6 @@ var Channel = class { } /** * Join the channel - * @param {integer} timeout - * @returns {Push} */ join(timeout = this.timeout) { if (this.joinedOnce) { @@ -301,14 +302,12 @@ var Channel = class { } /** * Hook into channel close - * @param {Function} callback */ onClose(callback) { - this.on(CHANNEL_EVENTS.close, callback); + return this.on(CHANNEL_EVENTS.close, callback); } /** * Hook into channel errors - * @param {Function} callback */ onError(callback) { return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason)); @@ -325,13 +324,9 @@ var Channel = class { * channel.off("event", ref1) * // Since unsubscription, do_stuff won't fire, * // while do_other_stuff will keep firing on the "event" - * - * @param {string} event - * @param {Function} callback - * @returns {integer} ref */ on(event, callback) { - let ref = this.bindingRef++; + const ref = this.bindingRef++; this.bindings.push({ event, ref, callback }); return ref; } @@ -349,9 +344,6 @@ var Channel = class { * * // Unsubscribe all handlers from event * channel.off("event") - * - * @param {string} event - * @param {integer} ref */ off(event, ref) { this.bindings = this.bindings.filter((bind) => { @@ -359,6 +351,7 @@ var Channel = class { }); } /** + * @internal * @private */ canPush() { @@ -375,19 +368,12 @@ var Channel = class { * .receive("ok", payload => console.log("phoenix replied:", payload)) * .receive("error", err => console.log("phoenix errored", err)) * .receive("timeout", () => console.log("timed out pushing")) - * @param {string} event - * @param {Object} payload - * @param {number} [timeout] - * @returns {Push} */ - push(event, payload, timeout = this.timeout) { - payload = payload || {}; + push(event, payload = {}, timeout = this.timeout) { if (!this.joinedOnce) { throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`); } - let pushEvent = new Push(this, event, function() { - return payload; - }, timeout); + const pushEvent = new Push(this, event, () => payload, timeout); if (this.canPush()) { pushEvent.send(); } else { @@ -396,7 +382,8 @@ var Channel = class { } return pushEvent; } - /** Leaves the channel + /** + * Leaves the channel * * Unsubscribes from server events, and * instructs channel to terminate on server @@ -408,20 +395,17 @@ var Channel = class { * * @example * channel.leave().receive("ok", () => alert("left!") ) - * - * @param {integer} timeout - * @returns {Push} */ leave(timeout = this.timeout) { this.rejoinTimer.reset(); this.joinPush.cancelTimeout(); this.state = CHANNEL_STATES.leaving; - let onClose = () => { + const onClose = () => { if (this.socket.hasLogger()) this.socket.log("channel", `leave ${this.topic}`); this.trigger(CHANNEL_EVENTS.close, "leave"); }; - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout); + const leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout); leavePush.receive("ok", () => onClose()).receive("timeout", () => onClose()); leavePush.send(); if (!this.canPush()) { @@ -436,15 +420,12 @@ var Channel = class { * before dispatching to the channel callbacks. * * Must return the payload, modified or unmodified - * @param {string} event - * @param {Object} payload - * @param {integer} ref - * @returns {Object} */ - onMessage(_event, payload, _ref) { + onMessage(_event, payload, _ref, _joinRef) { return payload; } /** + * @internal * @private */ isMember(topic, event, payload, joinRef) { @@ -453,19 +434,26 @@ var Channel = class { } if (joinRef && joinRef !== this.joinRef()) { if (this.socket.hasLogger()) - this.socket.log("channel", "dropping outdated message", { topic, event, payload, joinRef }); + this.socket.log("channel", "dropping outdated message", { + topic, + event, + payload, + joinRef + }); return false; } else { return true; } } /** + * @internal * @private */ joinRef() { return this.joinPush.ref; } /** + * @internal * @private */ rejoin(timeout = this.timeout) { @@ -477,50 +465,57 @@ var Channel = class { this.joinPush.resend(timeout); } /** + * @internal * @private */ trigger(event, payload, ref, joinRef) { - let handledPayload = this.onMessage(event, payload, ref, joinRef); + const handledPayload = this.onMessage(event, payload, ref, joinRef); if (payload && !handledPayload) { throw new Error("channel onMessage callbacks must return the payload, modified or unmodified"); } - let eventBindings = this.bindings.filter((bind) => bind.event === event); + const eventBindings = this.bindings.filter((bind) => bind.event === event); for (let i = 0; i < eventBindings.length; i++) { - let bind = eventBindings[i]; + const bind = eventBindings[i]; bind.callback(handledPayload, ref, joinRef || this.joinRef()); } } /** + * @internal * @private */ replyEventName(ref) { return `chan_reply_${ref}`; } /** + * @internal * @private */ isClosed() { return this.state === CHANNEL_STATES.closed; } /** + * @internal * @private */ isErrored() { return this.state === CHANNEL_STATES.errored; } /** + * @internal * @private */ isJoined() { return this.state === CHANNEL_STATES.joined; } /** + * @internal * @private */ isJoining() { return this.state === CHANNEL_STATES.joining; } /** + * @internal * @private */ isLeaving() { @@ -528,31 +523,30 @@ var Channel = class { } }; -// js/phoenix/ajax.js +// dist/phoenix/ajax.js var Ajax = class { static request(method, endPoint, headers, body, timeout, ontimeout, callback) { if (global.XDomainRequest) { - let req = new global.XDomainRequest(); + const req = new global.XDomainRequest(); return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback); } else if (global.XMLHttpRequest) { - let req = new global.XMLHttpRequest(); + const req = new global.XMLHttpRequest(); return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback); - } else if (global.fetch && global.AbortController) { + } else if (typeof global.fetch === "function" && typeof global.AbortController === "function") { return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback); } else { throw new Error("No suitable XMLHttpRequest implementation found"); } } static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) { - let options = { + const options = { method, headers, body }; - let controller = null; + const controller = new AbortController(); if (timeout) { - controller = new AbortController(); - const _timeoutId = setTimeout(() => controller.abort(), timeout); + setTimeout(() => controller.abort(), timeout); options.signal = controller.signal; } global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => { @@ -568,7 +562,7 @@ var Ajax = class { req.timeout = timeout; req.open(method, endPoint); req.onload = () => { - let response = this.parseJSON(req.responseText); + const response = this.parseJSON(req.responseText); callback && callback(response); }; if (ontimeout) { @@ -582,13 +576,13 @@ var Ajax = class { static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) { req.open(method, endPoint, true); req.timeout = timeout; - for (let [key, value] of Object.entries(headers)) { + for (const [key, value] of Object.entries(headers)) { req.setRequestHeader(key, value); } req.onerror = () => callback && callback(null); req.onreadystatechange = () => { if (req.readyState === XHR_STATES.complete && callback) { - let response = this.parseJSON(req.responseText); + const response = this.parseJSON(req.responseText); callback(response); } }; @@ -604,19 +598,19 @@ var Ajax = class { } try { return JSON.parse(resp); - } catch { + } catch (_a) { console && console.log("failed to parse JSON response", resp); return null; } } static serialize(obj, parentKey) { - let queryStr = []; - for (var key in obj) { + const queryStr = []; + for (const key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) { continue; } - let paramKey = parentKey ? `${parentKey}[${key}]` : key; - let paramVal = obj[key]; + const paramKey = parentKey ? `${parentKey}[${key}]` : key; + const paramVal = obj[key]; if (typeof paramVal === "object") { queryStr.push(this.serialize(paramVal, paramKey)); } else { @@ -629,21 +623,21 @@ var Ajax = class { if (Object.keys(params).length === 0) { return url; } - let prefix = url.match(/\?/) ? "&" : "?"; + const prefix = url.match(/\?/) ? "&" : "?"; return `${url}${prefix}${this.serialize(params)}`; } }; -// js/phoenix/longpoll.js -var arrayBufferToBase64 = (buffer) => { +// dist/phoenix/longpoll.js +function arrayBufferToBase64(buffer) { let binary = ""; - let bytes = new Uint8Array(buffer); - let len = bytes.byteLength; + const bytes = new Uint8Array(buffer); + const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); -}; +} var LongPoll = class { constructor(endPoint, protocols) { if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) { @@ -667,6 +661,7 @@ var LongPoll = class { }; this.pollEndpoint = this.normalizeEndpoint(endPoint); this.readyState = SOCKET_STATES.connecting; + this.timeout = 2e4; setTimeout(() => this.poll(), 0); } normalizeEndpoint(endPoint) { @@ -687,20 +682,22 @@ var LongPoll = class { return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting; } poll() { - const headers = { "Accept": "application/json" }; + const headers = { Accept: "application/json" }; if (this.authToken) { headers["X-Phoenix-AuthToken"] = this.authToken; } this.ajax("GET", headers, null, () => this.ontimeout(), (resp) => { + let status; if (resp) { - var { status, token, messages } = resp; - this.token = token; + const { status: respStatus, token } = resp; + status = respStatus; + this.token = token || null; } else { status = 0; } switch (status) { case 200: - messages.forEach((msg) => { + resp.messages.forEach((msg) => { setTimeout(() => this.onmessage({ data: msg }), 0); }); this.poll(); @@ -720,7 +717,7 @@ var LongPoll = class { case 0: case 500: this.onerror(500); - this.closeAndRetry(1011, "internal server error", 500); + this.closeAndRetry(1011, "internal server error", false); break; default: throw new Error(`unhandled poll status ${status}`); @@ -731,15 +728,18 @@ var LongPoll = class { // setTimeout 0, which optimizes back-to-back procedural // pushes against an empty buffer send(body) { + let bodyStr; if (typeof body !== "string") { - body = arrayBufferToBase64(body); + bodyStr = arrayBufferToBase64(body); + } else { + bodyStr = body; } if (this.currentBatch) { - this.currentBatch.push(body); + this.currentBatch.push(bodyStr); } else if (this.awaitingBatchAck) { - this.batchBuffer.push(body); + this.batchBuffer.push(bodyStr); } else { - this.currentBatch = [body]; + this.currentBatch = [bodyStr]; this.currentBatchTimer = setTimeout(() => { this.batchSend(this.currentBatch); this.currentBatch = null; @@ -760,14 +760,16 @@ var LongPoll = class { }); } close(code, reason, wasClean) { - for (let req of this.reqs) { + for (const req of this.reqs) { req.abort(); } this.readyState = SOCKET_STATES.closed; - let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean }); + const opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean }); this.batchBuffer = []; - clearTimeout(this.currentBatchTimer); - this.currentBatchTimer = null; + if (this.currentBatchTimer !== null) { + clearTimeout(this.currentBatchTimer); + this.currentBatchTimer = null; + } if (typeof CloseEvent !== "undefined") { this.onclose(new CloseEvent("close", opts)); } else { @@ -775,12 +777,11 @@ var LongPoll = class { } } ajax(method, headers, body, onCallerTimeout, callback) { - let req; - let ontimeout = () => { + const ontimeout = () => { this.reqs.delete(req); onCallerTimeout(); }; - req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => { + const req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => { this.reqs.delete(req); if (this.isActive()) { callback(resp); @@ -790,10 +791,13 @@ var LongPoll = class { } }; -// js/phoenix/presence.js -var Presence = class { +// dist/phoenix/presence.js +var Presence = class _Presence { constructor(channel, opts = {}) { - let events = opts.events || { state: "presence_state", diff: "presence_diff" }; + const events = opts.events || { + state: "presence_state", + diff: "presence_diff" + }; this.state = {}; this.pendingDiffs = []; this.channel = channel; @@ -807,37 +811,57 @@ var Presence = class { } }; this.channel.on(events.state, (newState) => { - let { onJoin, onLeave, onSync } = this.caller; + const { onJoin, onLeave, onSync } = this.caller; this.joinRef = this.channel.joinRef(); - this.state = Presence.syncState(this.state, newState, onJoin, onLeave); + this.state = _Presence.syncState(this.state, newState, onJoin, onLeave); this.pendingDiffs.forEach((diff) => { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave); }); this.pendingDiffs = []; onSync(); }); this.channel.on(events.diff, (diff) => { - let { onJoin, onLeave, onSync } = this.caller; + const { onJoin, onLeave, onSync } = this.caller; if (this.inPendingSyncState()) { this.pendingDiffs.push(diff); } else { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave); onSync(); } }); } + /** + * @internal + * @private + */ onJoin(callback) { this.caller.onJoin = callback; } + /** + * @internal + * @private + */ onLeave(callback) { this.caller.onLeave = callback; } + /** + * @internal + * @private + */ onSync(callback) { this.caller.onSync = callback; } + /** + * @internal + * @private + */ list(by) { - return Presence.list(this.state, by); + return _Presence.list(this.state, by); } + /** + * @internal + * @private + */ inPendingSyncState() { return !this.joinRef || this.joinRef !== this.channel.joinRef(); } @@ -847,25 +871,23 @@ var Presence = class { * with the client's state. An optional `onJoin` and `onLeave` callback can * be provided to react to changes in the client's local presences across * disconnects and reconnects with the server. - * - * @returns {Presence} */ static syncState(currentState, newState, onJoin, onLeave) { - let state = this.clone(currentState); - let joins = {}; - let leaves = {}; + const state = this.clone(currentState); + const joins = {}; + const leaves = {}; this.map(state, (key, presence) => { if (!newState[key]) { leaves[key] = presence; } }); this.map(newState, (key, newPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; if (currentPresence) { - let newRefs = newPresence.metas.map((m) => m.phx_ref); - let curRefs = currentPresence.metas.map((m) => m.phx_ref); - let joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0); - let leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0); + const newRefs = newPresence.metas.map((m) => m.phx_ref); + const curRefs = currentPresence.metas.map((m) => m.phx_ref); + const joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0); + const leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0); if (joinedMetas.length > 0) { joins[key] = newPresence; joins[key].metas = joinedMetas; @@ -881,16 +903,13 @@ var Presence = class { return this.syncDiff(state, { joins, leaves }, onJoin, onLeave); } /** - * * Used to sync a diff of presence join and leave * events from the server, as they happen. Like `syncState`, `syncDiff` * accepts optional `onJoin` and `onLeave` callbacks to react to a user * joining or leaving from a device. - * - * @returns {Presence} */ static syncDiff(state, diff, onJoin, onLeave) { - let { joins, leaves } = this.clone(diff); + const { joins, leaves } = this.clone(diff); if (!onJoin) { onJoin = function() { }; @@ -900,21 +919,21 @@ var Presence = class { }; } this.map(joins, (key, newPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; state[key] = this.clone(newPresence); if (currentPresence) { - let joinedRefs = state[key].metas.map((m) => m.phx_ref); - let curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0); + const joinedRefs = state[key].metas.map((m) => m.phx_ref); + const curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0); state[key].metas.unshift(...curMetas); } onJoin(key, currentPresence, newPresence); }); this.map(leaves, (key, leftPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; if (!currentPresence) { return; } - let refsToRemove = leftPresence.metas.map((m) => m.phx_ref); + const refsToRemove = leftPresence.metas.map((m) => m.phx_ref); currentPresence.metas = currentPresence.metas.filter((p) => { return refsToRemove.indexOf(p.phx_ref) < 0; }); @@ -927,11 +946,6 @@ var Presence = class { } /** * Returns the array of presences, with selected metadata. - * - * @param {Object} presences - * @param {Function} chooser - * - * @returns {Presence} */ static list(presences, chooser) { if (!chooser) { @@ -952,8 +966,8 @@ var Presence = class { } }; -// js/phoenix/serializer.js -var serializer_default = { +// dist/phoenix/serializer.js +var Serializer = { HEADER_LENGTH: 1, META_LENGTH: 4, KINDS: { push: 0, reply: 1, broadcast: 2 }, @@ -961,7 +975,7 @@ var serializer_default = { if (msg.payload.constructor === ArrayBuffer) { return callback(this.binaryEncode(msg)); } else { - let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; + const payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; return callback(JSON.stringify(payload)); } }, @@ -969,16 +983,16 @@ var serializer_default = { if (rawPayload.constructor === ArrayBuffer) { return callback(this.binaryDecode(rawPayload)); } else { - let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); + const [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); return callback({ join_ref, ref, topic, event, payload }); } }, // private binaryEncode(message) { - let { join_ref, ref, event, topic, payload } = message; - let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length; - let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); - let view = new DataView(header); + const { join_ref, ref, event, topic, payload } = message; + const metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length; + const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); + const view = new DataView(header); let offset = 0; view.setUint8(offset++, this.KINDS.push); view.setUint8(offset++, join_ref.length); @@ -989,15 +1003,15 @@ var serializer_default = { Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0))); - var combined = new Uint8Array(header.byteLength + payload.byteLength); + const combined = new Uint8Array(header.byteLength + payload.byteLength); combined.set(new Uint8Array(header), 0); combined.set(new Uint8Array(payload), header.byteLength); return combined.buffer; }, binaryDecode(buffer) { - let view = new DataView(buffer); - let kind = view.getUint8(0); - let decoder = new TextDecoder(); + const view = new DataView(buffer); + const kind = view.getUint8(0); + const decoder = new TextDecoder(); switch (kind) { case this.KINDS.push: return this.decodePush(buffer, view, decoder); @@ -1005,56 +1019,78 @@ var serializer_default = { return this.decodeReply(buffer, view, decoder); case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder); + default: + throw new Error(`Unknown message kind: ${kind}`); } }, decodePush(buffer, view, decoder) { - let joinRefSize = view.getUint8(1); - let topicSize = view.getUint8(2); - let eventSize = view.getUint8(3); + const joinRefSize = view.getUint8(1); + const topicSize = view.getUint8(2); + const eventSize = view.getUint8(3); let offset = this.HEADER_LENGTH + this.META_LENGTH - 1; - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - return { join_ref: joinRef, ref: null, topic, event, payload: data }; + const data = buffer.slice(offset, buffer.byteLength); + return { + join_ref: joinRef, + ref: null, + topic, + event, + payload: data + }; }, decodeReply(buffer, view, decoder) { - let joinRefSize = view.getUint8(1); - let refSize = view.getUint8(2); - let topicSize = view.getUint8(3); - let eventSize = view.getUint8(4); + const joinRefSize = view.getUint8(1); + const refSize = view.getUint8(2); + const topicSize = view.getUint8(3); + const eventSize = view.getUint8(4); let offset = this.HEADER_LENGTH + this.META_LENGTH; - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; - let ref = decoder.decode(buffer.slice(offset, offset + refSize)); + const ref = decoder.decode(buffer.slice(offset, offset + refSize)); offset = offset + refSize; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - let payload = { status: event, response: data }; - return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload }; + const data = buffer.slice(offset, buffer.byteLength); + const payload = { status: event, response: data }; + return { + join_ref: joinRef, + ref, + topic, + event: CHANNEL_EVENTS.reply, + payload + }; }, decodeBroadcast(buffer, view, decoder) { - let topicSize = view.getUint8(1); - let eventSize = view.getUint8(2); + const topicSize = view.getUint8(1); + const eventSize = view.getUint8(2); let offset = this.HEADER_LENGTH + 2; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - return { join_ref: null, ref: null, topic, event, payload: data }; + const data = buffer.slice(offset, buffer.byteLength); + return { + join_ref: null, + ref: null, + topic, + event, + payload: data + }; } }; +var serializer_default = Serializer; -// js/phoenix/socket.js +// dist/phoenix/socket.js var Socket = class { constructor(endPoint, opts = {}) { + var _a; this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] }; this.channels = []; this.sendBuffer = []; @@ -1062,7 +1098,7 @@ var Socket = class { this.timeout = opts.timeout || DEFAULT_TIMEOUT; this.transport = opts.transport || global.WebSocket || LongPoll; this.primaryPassedHealthCheck = false; - this.longPollFallbackMs = opts.longPollFallbackMs; + this.longPollFallbackMs = (_a = opts.longPollFallbackMs) !== null && _a !== void 0 ? _a : null; this.fallbackTimer = null; this.sessionStore = opts.sessionStorage || global && global.sessionStorage; this.establishedConnections = 0; @@ -1072,6 +1108,7 @@ var Socket = class { this.disconnecting = false; this.binaryType = opts.binaryType || "arraybuffer"; this.connectClock = 1; + this.conn = null; if (this.transport !== LongPoll) { this.encode = opts.encode || this.defaultEncoder; this.decode = opts.decode || this.defaultDecoder; @@ -1164,10 +1201,7 @@ var Socket = class { * @returns {string} */ endPointURL() { - let uri = Ajax.appendParams( - Ajax.appendParams(this.endPoint, this.params()), - { vsn: this.vsn } - ); + const uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params()), { vsn: this.vsn }); if (uri.charAt(0) !== "/") { return uri; } @@ -1240,7 +1274,7 @@ var Socket = class { * @param {Function} callback */ onOpen(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.open.push([ref, callback]); return ref; } @@ -1249,7 +1283,7 @@ var Socket = class { * @param {Function} callback */ onClose(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.close.push([ref, callback]); return ref; } @@ -1261,7 +1295,7 @@ var Socket = class { * @param {Function} callback */ onError(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.error.push([ref, callback]); return ref; } @@ -1270,7 +1304,7 @@ var Socket = class { * @param {Function} callback */ onMessage(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.message.push([ref, callback]); return ref; } @@ -1284,10 +1318,10 @@ var Socket = class { if (!this.isConnected()) { return false; } - let ref = this.makeRef(); - let startTime = Date.now(); + const ref = this.makeRef(); + const startTime = Date.now(); this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref }); - let onMsgRef = this.onMessage((msg) => { + const onMsgRef = this.onMessage((msg) => { if (msg.ref === ref) { this.off([onMsgRef]); callback(Date.now() - startTime); @@ -1296,6 +1330,7 @@ var Socket = class { return true; } /** + * @internal * @private */ transportConnect() { @@ -1303,7 +1338,10 @@ var Socket = class { this.closeWasClean = false; let protocols = void 0; if (this.authToken) { - protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`]; + protocols = [ + "phoenix", + `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}` + ]; } this.conn = new this.transport(this.endPointURL(), protocols); this.conn.binaryType = this.binaryType; @@ -1323,8 +1361,8 @@ var Socket = class { clearTimeout(this.fallbackTimer); let established = false; let primaryTransport = true; - let openRef, errorRef; - let fallback = (reason) => { + let openRef; + const fallback = (reason) => { this.log("transport", `falling back to ${fallbackTransport.name}...`, reason); this.off([openRef, errorRef]); primaryTransport = false; @@ -1335,7 +1373,7 @@ var Socket = class { return fallback("memorized"); } this.fallbackTimer = setTimeout(fallback, fallbackThreshold); - errorRef = this.onError((reason) => { + const errorRef = this.onError((reason) => { this.log("transport", "error", reason); if (primaryTransport && !established) { clearTimeout(this.fallbackTimer); @@ -1364,6 +1402,10 @@ var Socket = class { clearTimeout(this.heartbeatTimer); clearTimeout(this.heartbeatTimeoutTimer); } + /** + * @internal + * @private + */ onConnOpen() { if (this.hasLogger()) this.log("transport", `${this.transport.name} connected to ${this.endPointURL()}`); @@ -1375,9 +1417,6 @@ var Socket = class { this.resetHeartbeat(); this.stateChangeCallbacks.open.forEach(([, callback]) => callback()); } - /** - * @private - */ heartbeatTimeout() { if (this.pendingHeartbeatRef) { this.pendingHeartbeatRef = null; @@ -1401,7 +1440,7 @@ var Socket = class { if (!this.conn) { return callback && callback(); } - let connectClock = this.connectClock; + const connectClock = this.connectClock; this.waitForBufferDone(() => { if (connectClock !== this.connectClock) { return; @@ -1450,8 +1489,12 @@ var Socket = class { this.waitForSocketClosed(callback, tries + 1); }, 150 * tries); } + /** + * @internal + * @private + */ onConnClose(event) { - let closeCode = event && event.code; + const closeCode = event && event.code; if (this.hasLogger()) this.log("transport", "close", event); this.triggerChanError(); @@ -1462,13 +1505,14 @@ var Socket = class { this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event)); } /** + * @internal * @private */ onConnError(error) { if (this.hasLogger()) - this.log("transport", error); - let transportBefore = this.transport; - let establishedBefore = this.establishedConnections; + this.log("transport", "error", error); + const transportBefore = this.transport; + const establishedBefore = this.establishedConnections; this.stateChangeCallbacks.error.forEach(([, callback]) => { callback(error, transportBefore, establishedBefore); }); @@ -1476,13 +1520,10 @@ var Socket = class { this.triggerChanError(); } } - /** - * @private - */ triggerChanError() { this.channels.forEach((channel) => { if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) { - channel.trigger(CHANNEL_EVENTS.error); + channel.trigger(CHANNEL_EVENTS.error, {}); } }); } @@ -1508,6 +1549,7 @@ var Socket = class { return this.connectionState() === "open"; } /** + * @internal * @private * * @param {Channel} @@ -1523,11 +1565,13 @@ var Socket = class { * `onOpen`, `onClose`, `onError,` and `onMessage` */ off(refs) { - for (let key in this.stateChangeCallbacks) { - this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => { - return refs.indexOf(ref) === -1; - }); - } + const filter = (callbacks) => callbacks.filter(([ref]) => refs.indexOf(ref) === -1); + this.stateChangeCallbacks = { + open: filter(this.stateChangeCallbacks.open), + close: filter(this.stateChangeCallbacks.close), + error: filter(this.stateChangeCallbacks.error), + message: filter(this.stateChangeCallbacks.message) + }; } /** * Initiates a new channel for the given topic @@ -1537,7 +1581,7 @@ var Socket = class { * @returns {Channel} */ channel(topic, chanParams = {}) { - let chan = new Channel(topic, chanParams, this); + const chan = new Channel(topic, chanParams, this); this.channels.push(chan); return chan; } @@ -1546,7 +1590,7 @@ var Socket = class { */ push(data) { if (this.hasLogger()) { - let { topic, event, payload, ref, join_ref } = data; + const { topic, event, payload, ref, join_ref } = data; this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload); } if (this.isConnected()) { @@ -1560,7 +1604,7 @@ var Socket = class { * @returns {string} */ makeRef() { - let newRef = this.ref + 1; + const newRef = this.ref + 1; if (newRef === this.ref) { this.ref = 0; } else { @@ -1568,23 +1612,40 @@ var Socket = class { } return this.ref.toString(); } + /** + * @internal + * @private + */ sendHeartbeat() { if (this.pendingHeartbeatRef && !this.isConnected()) { return; } this.pendingHeartbeatRef = this.makeRef(); - this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef }); + this.push({ + topic: "phoenix", + event: "heartbeat", + payload: {}, + ref: this.pendingHeartbeatRef + }); this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs); } + /** + * @internal + * @private + */ flushSendBuffer() { if (this.isConnected() && this.sendBuffer.length > 0) { this.sendBuffer.forEach((callback) => callback()); this.sendBuffer = []; } } + /** + * @internal + * @private + */ onConnMessage(rawMessage) { this.decode(rawMessage.data, (msg) => { - let { topic, event, payload, ref, join_ref } = msg; + const { topic, event, payload, ref, join_ref } = msg; if (ref && ref === this.pendingHeartbeatRef) { this.clearHeartbeats(); this.pendingHeartbeatRef = null; @@ -1600,13 +1661,17 @@ var Socket = class { channel.trigger(event, payload, ref, join_ref); } for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) { - let [, callback] = this.stateChangeCallbacks.message[i]; + const [, callback] = this.stateChangeCallbacks.message[i]; callback(msg); } }); } + /** + * @internal + * @private + */ leaveOpenTopic(topic) { - let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining())); + const dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining())); if (dupChannel) { if (this.hasLogger()) this.log("transport", `leaving duplicate topic "${topic}"`); diff --git a/priv/static/phoenix.cjs.js.map b/priv/static/phoenix.cjs.js.map index a281d44eb9..294992a6d2 100644 --- a/priv/static/phoenix.cjs.js.map +++ b/priv/static/phoenix.cjs.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../../assets/js/phoenix/index.js", "../../assets/js/phoenix/utils.js", "../../assets/js/phoenix/constants.js", "../../assets/js/phoenix/push.js", "../../assets/js/phoenix/timer.js", "../../assets/js/phoenix/channel.js", "../../assets/js/phoenix/ajax.js", "../../assets/js/phoenix/longpoll.js", "../../assets/js/phoenix/presence.js", "../../assets/js/phoenix/serializer.js", "../../assets/js/phoenix/socket.js"], - "sourcesContent": ["/**\n * Phoenix Channels JavaScript client\n *\n * ## Socket Connection\n *\n * A single connection is established to the server and\n * channels are multiplexed over the connection.\n * Connect to the server using the `Socket` class:\n *\n * ```javascript\n * let socket = new Socket(\"/socket\", {params: {userToken: \"123\"}})\n * socket.connect()\n * ```\n *\n * The `Socket` constructor takes the mount point of the socket,\n * the authentication params, as well as options that can be found in\n * the Socket docs, such as configuring the `LongPoll` transport, and\n * heartbeat.\n *\n * ## Channels\n *\n * Channels are isolated, concurrent processes on the server that\n * subscribe to topics and broker events between the client and server.\n * To join a channel, you must provide the topic, and channel params for\n * authorization. Here's an example chat room example where `\"new_msg\"`\n * events are listened for, messages are pushed to the server, and\n * the channel is joined with ok/error/timeout matches:\n *\n * ```\n * let channel = socket.channel(\"room:123\", {token: roomToken})\n * channel.on(\"new_msg\", msg => console.log(\"Got message\", msg) )\n * $input.onEnter( e => {\n * channel.push(\"new_msg\", {body: e.target.val}, 10000)\n * .receive(\"ok\", (msg) => console.log(\"created message\", msg) )\n * .receive(\"error\", (reasons) => console.log(\"create failed\", reasons) )\n * .receive(\"timeout\", () => console.log(\"Networking issue...\") )\n * })\n *\n * channel.join()\n * .receive(\"ok\", ({messages}) => console.log(\"catching up\", messages) )\n * .receive(\"error\", ({reason}) => console.log(\"failed join\", reason) )\n * .receive(\"timeout\", () => console.log(\"Networking issue. Still waiting...\"))\n *```\n *\n * ## Joining\n *\n * Creating a channel with `socket.channel(topic, params)`, binds the params to\n * `channel.params`, which are sent up on `channel.join()`.\n * Subsequent rejoins will send up the modified params for\n * updating authorization params, or passing up last_message_id information.\n * Successful joins receive an \"ok\" status, while unsuccessful joins\n * receive \"error\".\n *\n * With the default serializers and WebSocket transport, JSON text frames are\n * used for pushing a JSON object literal. If an `ArrayBuffer` instance is provided,\n * binary encoding will be used and the message will be sent with the binary\n * opcode.\n *\n * *Note*: binary messages are only supported on the WebSocket transport.\n *\n * ## Duplicate Join Subscriptions\n *\n * While the client may join any number of topics on any number of channels,\n * the client may only hold a single subscription for each unique topic at any\n * given time. When attempting to create a duplicate subscription,\n * the server will close the existing channel, log a warning, and\n * spawn a new channel for the topic. The client will have their\n * `channel.onClose` callbacks fired for the existing channel, and the new\n * channel join will have its receive hooks processed as normal.\n *\n * ## Pushing Messages\n *\n * From the previous example, we can see that pushing messages to the server\n * can be done with `channel.push(eventName, payload)` and we can optionally\n * receive responses from the push. Additionally, we can use\n * `receive(\"timeout\", callback)` to abort waiting for our other `receive` hooks\n * and take action after some period of waiting. The default timeout is 10000ms.\n *\n *\n * ## Socket Hooks\n *\n * Lifecycle events of the multiplexed connection can be hooked into via\n * `socket.onError()` and `socket.onClose()` events, ie:\n *\n * ```\n * socket.onError( () => console.log(\"there was an error with the connection!\") )\n * socket.onClose( () => console.log(\"the connection dropped\") )\n * ```\n *\n *\n * ## Channel Hooks\n *\n * For each joined channel, you can bind to `onError` and `onClose` events\n * to monitor the channel lifecycle, ie:\n *\n * ```\n * channel.onError( () => console.log(\"there was an error!\") )\n * channel.onClose( () => console.log(\"the channel has gone away gracefully\") )\n * ```\n *\n * ### onError hooks\n *\n * `onError` hooks are invoked if the socket connection drops, or the channel\n * crashes on the server. In either case, a channel rejoin is attempted\n * automatically in an exponential backoff manner.\n *\n * ### onClose hooks\n *\n * `onClose` hooks are invoked only in two cases. 1) the channel explicitly\n * closed on the server, or 2). The client explicitly closed, by calling\n * `channel.leave()`\n *\n *\n * ## Presence\n *\n * The `Presence` object provides features for syncing presence information\n * from the server with the client and handling presences joining and leaving.\n *\n * ### Syncing state from the server\n *\n * To sync presence state from the server, first instantiate an object and\n * pass your channel in to track lifecycle events:\n *\n * ```\n * let channel = socket.channel(\"some:topic\")\n * let presence = new Presence(channel)\n * ```\n *\n * Next, use the `presence.onSync` callback to react to state changes\n * from the server. For example, to render the list of users every time\n * the list changes, you could write:\n *\n * ```\n * presence.onSync(() => {\n * myRenderUsersFunction(presence.list())\n * })\n * ```\n *\n * ### Listing Presences\n *\n * `presence.list` is used to return a list of presence information\n * based on the local state of metadata. By default, all presence\n * metadata is returned, but a `listBy` function can be supplied to\n * allow the client to select which metadata to use for a given presence.\n * For example, you may have a user online from different devices with\n * a metadata status of \"online\", but they have set themselves to \"away\"\n * on another device. In this case, the app may choose to use the \"away\"\n * status for what appears on the UI. The example below defines a `listBy`\n * function which prioritizes the first metadata which was registered for\n * each user. This could be the first tab they opened, or the first device\n * they came online from:\n *\n * ```\n * let listBy = (id, {metas: [first, ...rest]}) => {\n * first.count = rest.length + 1 // count of this user's presences\n * first.id = id\n * return first\n * }\n * let onlineUsers = presence.list(listBy)\n * ```\n *\n * ### Handling individual presence join and leave events\n *\n * The `presence.onJoin` and `presence.onLeave` callbacks can be used to\n * react to individual presences joining and leaving the app. For example:\n *\n * ```\n * let presence = new Presence(channel)\n *\n * // detect if user has joined for the 1st time or from another tab/device\n * presence.onJoin((id, current, newPres) => {\n * if(!current){\n * console.log(\"user has entered for the first time\", newPres)\n * } else {\n * console.log(\"user additional presence\", newPres)\n * }\n * })\n *\n * // detect if user has left from all tabs/devices, or is still present\n * presence.onLeave((id, current, leftPres) => {\n * if(current.metas.length === 0){\n * console.log(\"user has left from all devices\", leftPres)\n * } else {\n * console.log(\"user left from a device\", leftPres)\n * }\n * })\n * // receive presence data from server\n * presence.onSync(() => {\n * displayUsers(presence.list())\n * })\n * ```\n * @module phoenix\n */\n\nimport Channel from \"./channel\"\nimport LongPoll from \"./longpoll\"\nimport Presence from \"./presence\"\nimport Serializer from \"./serializer\"\nimport Socket from \"./socket\"\n\nexport {\n Channel,\n LongPoll,\n Presence,\n Serializer,\n Socket\n}\n", "// wraps value in closure or returns closure\nexport let closure = (value) => {\n if(typeof value === \"function\"){\n return value\n } else {\n let closure = function (){ return value }\n return closure\n }\n}\n", "export const globalSelf = typeof self !== \"undefined\" ? self : null\nexport const phxWindow = typeof window !== \"undefined\" ? window : null\nexport const global = globalSelf || phxWindow || globalThis\nexport const DEFAULT_VSN = \"2.0.0\"\nexport const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}\nexport const DEFAULT_TIMEOUT = 10000\nexport const WS_CLOSE_NORMAL = 1000\nexport const CHANNEL_STATES = {\n closed: \"closed\",\n errored: \"errored\",\n joined: \"joined\",\n joining: \"joining\",\n leaving: \"leaving\",\n}\nexport const CHANNEL_EVENTS = {\n close: \"phx_close\",\n error: \"phx_error\",\n join: \"phx_join\",\n reply: \"phx_reply\",\n leave: \"phx_leave\"\n}\n\nexport const TRANSPORTS = {\n longpoll: \"longpoll\",\n websocket: \"websocket\"\n}\nexport const XHR_STATES = {\n complete: 4\n}\nexport const AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\"\n", "/**\n * Initializes the Push\n * @param {Channel} channel - The Channel\n * @param {string} event - The event, for example `\"phx_join\"`\n * @param {Object} payload - The payload, for example `{user_id: 123}`\n * @param {number} timeout - The push timeout in milliseconds\n */\nexport default class Push {\n constructor(channel, event, payload, timeout){\n this.channel = channel\n this.event = event\n this.payload = payload || function (){ return {} }\n this.receivedResp = null\n this.timeout = timeout\n this.timeoutTimer = null\n this.recHooks = []\n this.sent = false\n }\n\n /**\n *\n * @param {number} timeout\n */\n resend(timeout){\n this.timeout = timeout\n this.reset()\n this.send()\n }\n\n /**\n *\n */\n send(){\n if(this.hasReceived(\"timeout\")){ return }\n this.startTimeout()\n this.sent = true\n this.channel.socket.push({\n topic: this.channel.topic,\n event: this.event,\n payload: this.payload(),\n ref: this.ref,\n join_ref: this.channel.joinRef()\n })\n }\n\n /**\n *\n * @param {*} status\n * @param {*} callback\n */\n receive(status, callback){\n if(this.hasReceived(status)){\n callback(this.receivedResp.response)\n }\n\n this.recHooks.push({status, callback})\n return this\n }\n\n /**\n * @private\n */\n reset(){\n this.cancelRefEvent()\n this.ref = null\n this.refEvent = null\n this.receivedResp = null\n this.sent = false\n }\n\n /**\n * @private\n */\n matchReceive({status, response, _ref}){\n this.recHooks.filter(h => h.status === status)\n .forEach(h => h.callback(response))\n }\n\n /**\n * @private\n */\n cancelRefEvent(){\n if(!this.refEvent){ return }\n this.channel.off(this.refEvent)\n }\n\n /**\n * @private\n */\n cancelTimeout(){\n clearTimeout(this.timeoutTimer)\n this.timeoutTimer = null\n }\n\n /**\n * @private\n */\n startTimeout(){\n if(this.timeoutTimer){ this.cancelTimeout() }\n this.ref = this.channel.socket.makeRef()\n this.refEvent = this.channel.replyEventName(this.ref)\n\n this.channel.on(this.refEvent, payload => {\n this.cancelRefEvent()\n this.cancelTimeout()\n this.receivedResp = payload\n this.matchReceive(payload)\n })\n\n this.timeoutTimer = setTimeout(() => {\n this.trigger(\"timeout\", {})\n }, this.timeout)\n }\n\n /**\n * @private\n */\n hasReceived(status){\n return this.receivedResp && this.receivedResp.status === status\n }\n\n /**\n * @private\n */\n trigger(status, response){\n this.channel.trigger(this.refEvent, {status, response})\n }\n}\n", "/**\n *\n * Creates a timer that accepts a `timerCalc` function to perform\n * calculated timeout retries, such as exponential backoff.\n *\n * @example\n * let reconnectTimer = new Timer(() => this.connect(), function(tries){\n * return [1000, 5000, 10000][tries - 1] || 10000\n * })\n * reconnectTimer.scheduleTimeout() // fires after 1000\n * reconnectTimer.scheduleTimeout() // fires after 5000\n * reconnectTimer.reset()\n * reconnectTimer.scheduleTimeout() // fires after 1000\n *\n * @param {Function} callback\n * @param {Function} timerCalc\n */\nexport default class Timer {\n constructor(callback, timerCalc){\n this.callback = callback\n this.timerCalc = timerCalc\n this.timer = null\n this.tries = 0\n }\n\n reset(){\n this.tries = 0\n clearTimeout(this.timer)\n }\n\n /**\n * Cancels any previous scheduleTimeout and schedules callback\n */\n scheduleTimeout(){\n clearTimeout(this.timer)\n\n this.timer = setTimeout(() => {\n this.tries = this.tries + 1\n this.callback()\n }, this.timerCalc(this.tries + 1))\n }\n}\n", "import {closure} from \"./utils\"\nimport {\n CHANNEL_EVENTS,\n CHANNEL_STATES,\n} from \"./constants\"\n\nimport Push from \"./push\"\nimport Timer from \"./timer\"\n\n/**\n *\n * @param {string} topic\n * @param {(Object|function)} params\n * @param {Socket} socket\n */\nexport default class Channel {\n constructor(topic, params, socket){\n this.state = CHANNEL_STATES.closed\n this.topic = topic\n this.params = closure(params || {})\n this.socket = socket\n this.bindings = []\n this.bindingRef = 0\n this.timeout = this.socket.timeout\n this.joinedOnce = false\n this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout)\n this.pushBuffer = []\n this.stateChangeRefs = []\n\n this.rejoinTimer = new Timer(() => {\n if(this.socket.isConnected()){ this.rejoin() }\n }, this.socket.rejoinAfterMs)\n this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()))\n this.stateChangeRefs.push(this.socket.onOpen(() => {\n this.rejoinTimer.reset()\n if(this.isErrored()){ this.rejoin() }\n })\n )\n this.joinPush.receive(\"ok\", () => {\n this.state = CHANNEL_STATES.joined\n this.rejoinTimer.reset()\n this.pushBuffer.forEach(pushEvent => pushEvent.send())\n this.pushBuffer = []\n })\n this.joinPush.receive(\"error\", () => {\n this.state = CHANNEL_STATES.errored\n if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n })\n this.onClose(() => {\n this.rejoinTimer.reset()\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`)\n this.state = CHANNEL_STATES.closed\n this.socket.remove(this)\n })\n this.onError(reason => {\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `error ${this.topic}`, reason)\n if(this.isJoining()){ this.joinPush.reset() }\n this.state = CHANNEL_STATES.errored\n if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n })\n this.joinPush.receive(\"timeout\", () => {\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout)\n let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout)\n leavePush.send()\n this.state = CHANNEL_STATES.errored\n this.joinPush.reset()\n if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n })\n this.on(CHANNEL_EVENTS.reply, (payload, ref) => {\n this.trigger(this.replyEventName(ref), payload)\n })\n }\n\n /**\n * Join the channel\n * @param {integer} timeout\n * @returns {Push}\n */\n join(timeout = this.timeout){\n if(this.joinedOnce){\n throw new Error(\"tried to join multiple times. 'join' can only be called a single time per channel instance\")\n } else {\n this.timeout = timeout\n this.joinedOnce = true\n this.rejoin()\n return this.joinPush\n }\n }\n\n /**\n * Hook into channel close\n * @param {Function} callback\n */\n onClose(callback){\n this.on(CHANNEL_EVENTS.close, callback)\n }\n\n /**\n * Hook into channel errors\n * @param {Function} callback\n */\n onError(callback){\n return this.on(CHANNEL_EVENTS.error, reason => callback(reason))\n }\n\n /**\n * Subscribes on channel events\n *\n * Subscription returns a ref counter, which can be used later to\n * unsubscribe the exact event listener\n *\n * @example\n * const ref1 = channel.on(\"event\", do_stuff)\n * const ref2 = channel.on(\"event\", do_other_stuff)\n * channel.off(\"event\", ref1)\n * // Since unsubscription, do_stuff won't fire,\n * // while do_other_stuff will keep firing on the \"event\"\n *\n * @param {string} event\n * @param {Function} callback\n * @returns {integer} ref\n */\n on(event, callback){\n let ref = this.bindingRef++\n this.bindings.push({event, ref, callback})\n return ref\n }\n\n /**\n * Unsubscribes off of channel events\n *\n * Use the ref returned from a channel.on() to unsubscribe one\n * handler, or pass nothing for the ref to unsubscribe all\n * handlers for the given event.\n *\n * @example\n * // Unsubscribe the do_stuff handler\n * const ref1 = channel.on(\"event\", do_stuff)\n * channel.off(\"event\", ref1)\n *\n * // Unsubscribe all handlers from event\n * channel.off(\"event\")\n *\n * @param {string} event\n * @param {integer} ref\n */\n off(event, ref){\n this.bindings = this.bindings.filter((bind) => {\n return !(bind.event === event && (typeof ref === \"undefined\" || ref === bind.ref))\n })\n }\n\n /**\n * @private\n */\n canPush(){ return this.socket.isConnected() && this.isJoined() }\n\n /**\n * Sends a message `event` to phoenix with the payload `payload`.\n * Phoenix receives this in the `handle_in(event, payload, socket)`\n * function. if phoenix replies or it times out (default 10000ms),\n * then optionally the reply can be received.\n *\n * @example\n * channel.push(\"event\")\n * .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n * .receive(\"error\", err => console.log(\"phoenix errored\", err))\n * .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n * @param {string} event\n * @param {Object} payload\n * @param {number} [timeout]\n * @returns {Push}\n */\n push(event, payload, timeout = this.timeout){\n payload = payload || {}\n if(!this.joinedOnce){\n throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`)\n }\n let pushEvent = new Push(this, event, function (){ return payload }, timeout)\n if(this.canPush()){\n pushEvent.send()\n } else {\n pushEvent.startTimeout()\n this.pushBuffer.push(pushEvent)\n }\n\n return pushEvent\n }\n\n /** Leaves the channel\n *\n * Unsubscribes from server events, and\n * instructs channel to terminate on server\n *\n * Triggers onClose() hooks\n *\n * To receive leave acknowledgements, use the `receive`\n * hook to bind to the server ack, ie:\n *\n * @example\n * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n *\n * @param {integer} timeout\n * @returns {Push}\n */\n leave(timeout = this.timeout){\n this.rejoinTimer.reset()\n this.joinPush.cancelTimeout()\n\n this.state = CHANNEL_STATES.leaving\n let onClose = () => {\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `leave ${this.topic}`)\n this.trigger(CHANNEL_EVENTS.close, \"leave\")\n }\n let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout)\n leavePush.receive(\"ok\", () => onClose())\n .receive(\"timeout\", () => onClose())\n leavePush.send()\n if(!this.canPush()){ leavePush.trigger(\"ok\", {}) }\n\n return leavePush\n }\n\n /**\n * Overridable message hook\n *\n * Receives all events for specialized message handling\n * before dispatching to the channel callbacks.\n *\n * Must return the payload, modified or unmodified\n * @param {string} event\n * @param {Object} payload\n * @param {integer} ref\n * @returns {Object}\n */\n onMessage(_event, payload, _ref){ return payload }\n\n /**\n * @private\n */\n isMember(topic, event, payload, joinRef){\n if(this.topic !== topic){ return false }\n\n if(joinRef && joinRef !== this.joinRef()){\n if(this.socket.hasLogger()) this.socket.log(\"channel\", \"dropping outdated message\", {topic, event, payload, joinRef})\n return false\n } else {\n return true\n }\n }\n\n /**\n * @private\n */\n joinRef(){ return this.joinPush.ref }\n\n /**\n * @private\n */\n rejoin(timeout = this.timeout){\n if(this.isLeaving()){ return }\n this.socket.leaveOpenTopic(this.topic)\n this.state = CHANNEL_STATES.joining\n this.joinPush.resend(timeout)\n }\n\n /**\n * @private\n */\n trigger(event, payload, ref, joinRef){\n let handledPayload = this.onMessage(event, payload, ref, joinRef)\n if(payload && !handledPayload){ throw new Error(\"channel onMessage callbacks must return the payload, modified or unmodified\") }\n\n let eventBindings = this.bindings.filter(bind => bind.event === event)\n\n for(let i = 0; i < eventBindings.length; i++){\n let bind = eventBindings[i]\n bind.callback(handledPayload, ref, joinRef || this.joinRef())\n }\n }\n\n /**\n * @private\n */\n replyEventName(ref){ return `chan_reply_${ref}` }\n\n /**\n * @private\n */\n isClosed(){ return this.state === CHANNEL_STATES.closed }\n\n /**\n * @private\n */\n isErrored(){ return this.state === CHANNEL_STATES.errored }\n\n /**\n * @private\n */\n isJoined(){ return this.state === CHANNEL_STATES.joined }\n\n /**\n * @private\n */\n isJoining(){ return this.state === CHANNEL_STATES.joining }\n\n /**\n * @private\n */\n isLeaving(){ return this.state === CHANNEL_STATES.leaving }\n}\n", "import {\n global,\n XHR_STATES\n} from \"./constants\"\n\nexport default class Ajax {\n\n static request(method, endPoint, headers, body, timeout, ontimeout, callback){\n if(global.XDomainRequest){\n let req = new global.XDomainRequest() // IE8, IE9\n return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback)\n } else if(global.XMLHttpRequest){\n let req = new global.XMLHttpRequest() // IE7+, Firefox, Chrome, Opera, Safari\n return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback)\n } else if(global.fetch && global.AbortController){\n // Fetch with AbortController for modern browsers\n return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback)\n } else {\n throw new Error(\"No suitable XMLHttpRequest implementation found\")\n }\n }\n\n static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback){\n let options = {\n method,\n headers,\n body,\n }\n let controller = null\n if(timeout){\n controller = new AbortController()\n const _timeoutId = setTimeout(() => controller.abort(), timeout)\n options.signal = controller.signal\n }\n global.fetch(endPoint, options)\n .then(response => response.text())\n .then(data => this.parseJSON(data))\n .then(data => callback && callback(data))\n .catch(err => {\n if(err.name === \"AbortError\" && ontimeout){\n ontimeout()\n } else {\n callback && callback(null)\n }\n })\n return controller\n }\n\n static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback){\n req.timeout = timeout\n req.open(method, endPoint)\n req.onload = () => {\n let response = this.parseJSON(req.responseText)\n callback && callback(response)\n }\n if(ontimeout){ req.ontimeout = ontimeout }\n\n // Work around bug in IE9 that requires an attached onprogress handler\n req.onprogress = () => { }\n\n req.send(body)\n return req\n }\n\n static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback){\n req.open(method, endPoint, true)\n req.timeout = timeout\n for(let [key, value] of Object.entries(headers)){\n req.setRequestHeader(key, value)\n }\n req.onerror = () => callback && callback(null)\n req.onreadystatechange = () => {\n if(req.readyState === XHR_STATES.complete && callback){\n let response = this.parseJSON(req.responseText)\n callback(response)\n }\n }\n if(ontimeout){ req.ontimeout = ontimeout }\n\n req.send(body)\n return req\n }\n\n static parseJSON(resp){\n if(!resp || resp === \"\"){ return null }\n\n try {\n return JSON.parse(resp)\n } catch {\n console && console.log(\"failed to parse JSON response\", resp)\n return null\n }\n }\n\n static serialize(obj, parentKey){\n let queryStr = []\n for(var key in obj){\n if(!Object.prototype.hasOwnProperty.call(obj, key)){ continue }\n let paramKey = parentKey ? `${parentKey}[${key}]` : key\n let paramVal = obj[key]\n if(typeof paramVal === \"object\"){\n queryStr.push(this.serialize(paramVal, paramKey))\n } else {\n queryStr.push(encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal))\n }\n }\n return queryStr.join(\"&\")\n }\n\n static appendParams(url, params){\n if(Object.keys(params).length === 0){ return url }\n\n let prefix = url.match(/\\?/) ? \"&\" : \"?\"\n return `${url}${prefix}${this.serialize(params)}`\n }\n}\n", "import {\n SOCKET_STATES,\n TRANSPORTS,\n AUTH_TOKEN_PREFIX\n} from \"./constants\"\n\nimport Ajax from \"./ajax\"\n\nlet arrayBufferToBase64 = (buffer) => {\n let binary = \"\"\n let bytes = new Uint8Array(buffer)\n let len = bytes.byteLength\n for(let i = 0; i < len; i++){ binary += String.fromCharCode(bytes[i]) }\n return btoa(binary)\n}\n\nexport default class LongPoll {\n\n constructor(endPoint, protocols){\n // we only support subprotocols for authToken\n // [\"phoenix\", \"base64url.bearer.phx.BASE64_ENCODED_TOKEN\"]\n if(protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)){\n this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length))\n }\n this.endPoint = null\n this.token = null\n this.skipHeartbeat = true\n this.reqs = new Set()\n this.awaitingBatchAck = false\n this.currentBatch = null\n this.currentBatchTimer = null\n this.batchBuffer = []\n this.onopen = function (){ } // noop\n this.onerror = function (){ } // noop\n this.onmessage = function (){ } // noop\n this.onclose = function (){ } // noop\n this.pollEndpoint = this.normalizeEndpoint(endPoint)\n this.readyState = SOCKET_STATES.connecting\n // we must wait for the caller to finish setting up our callbacks and timeout properties\n setTimeout(() => this.poll(), 0)\n }\n\n normalizeEndpoint(endPoint){\n return (endPoint\n .replace(\"ws://\", \"http://\")\n .replace(\"wss://\", \"https://\")\n .replace(new RegExp(\"(.*)\\/\" + TRANSPORTS.websocket), \"$1/\" + TRANSPORTS.longpoll))\n }\n\n endpointURL(){\n return Ajax.appendParams(this.pollEndpoint, {token: this.token})\n }\n\n closeAndRetry(code, reason, wasClean){\n this.close(code, reason, wasClean)\n this.readyState = SOCKET_STATES.connecting\n }\n\n ontimeout(){\n this.onerror(\"timeout\")\n this.closeAndRetry(1005, \"timeout\", false)\n }\n\n isActive(){ return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting }\n\n poll(){\n const headers = {\"Accept\": \"application/json\"}\n if(this.authToken){\n headers[\"X-Phoenix-AuthToken\"] = this.authToken\n }\n this.ajax(\"GET\", headers, null, () => this.ontimeout(), resp => {\n if(resp){\n var {status, token, messages} = resp\n this.token = token\n } else {\n status = 0\n }\n\n switch(status){\n case 200:\n messages.forEach(msg => {\n // Tasks are what things like event handlers, setTimeout callbacks,\n // promise resolves and more are run within.\n // In modern browsers, there are two different kinds of tasks,\n // microtasks and macrotasks.\n // Microtasks are mainly used for Promises, while macrotasks are\n // used for everything else.\n // Microtasks always have priority over macrotasks. If the JS engine\n // is looking for a task to run, it will always try to empty the\n // microtask queue before attempting to run anything from the\n // macrotask queue.\n //\n // For the WebSocket transport, messages always arrive in their own\n // event. This means that if any promises are resolved from within,\n // their callbacks will always finish execution by the time the\n // next message event handler is run.\n //\n // In order to emulate this behaviour, we need to make sure each\n // onmessage handler is run within its own macrotask.\n setTimeout(() => this.onmessage({data: msg}), 0)\n })\n this.poll()\n break\n case 204:\n this.poll()\n break\n case 410:\n this.readyState = SOCKET_STATES.open\n this.onopen({})\n this.poll()\n break\n case 403:\n this.onerror(403)\n this.close(1008, \"forbidden\", false)\n break\n case 0:\n case 500:\n this.onerror(500)\n this.closeAndRetry(1011, \"internal server error\", 500)\n break\n default: throw new Error(`unhandled poll status ${status}`)\n }\n })\n }\n\n // we collect all pushes within the current event loop by\n // setTimeout 0, which optimizes back-to-back procedural\n // pushes against an empty buffer\n\n send(body){\n if(typeof(body) !== \"string\"){ body = arrayBufferToBase64(body) }\n if(this.currentBatch){\n this.currentBatch.push(body)\n } else if(this.awaitingBatchAck){\n this.batchBuffer.push(body)\n } else {\n this.currentBatch = [body]\n this.currentBatchTimer = setTimeout(() => {\n this.batchSend(this.currentBatch)\n this.currentBatch = null\n }, 0)\n }\n }\n\n batchSend(messages){\n this.awaitingBatchAck = true\n this.ajax(\"POST\", {\"Content-Type\": \"application/x-ndjson\"}, messages.join(\"\\n\"), () => this.onerror(\"timeout\"), resp => {\n this.awaitingBatchAck = false\n if(!resp || resp.status !== 200){\n this.onerror(resp && resp.status)\n this.closeAndRetry(1011, \"internal server error\", false)\n } else if(this.batchBuffer.length > 0){\n this.batchSend(this.batchBuffer)\n this.batchBuffer = []\n }\n })\n }\n\n close(code, reason, wasClean){\n for(let req of this.reqs){ req.abort() }\n this.readyState = SOCKET_STATES.closed\n let opts = Object.assign({code: 1000, reason: undefined, wasClean: true}, {code, reason, wasClean})\n this.batchBuffer = []\n clearTimeout(this.currentBatchTimer)\n this.currentBatchTimer = null\n if(typeof(CloseEvent) !== \"undefined\"){\n this.onclose(new CloseEvent(\"close\", opts))\n } else {\n this.onclose(opts)\n }\n }\n\n ajax(method, headers, body, onCallerTimeout, callback){\n let req\n let ontimeout = () => {\n this.reqs.delete(req)\n onCallerTimeout()\n }\n req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, resp => {\n this.reqs.delete(req)\n if(this.isActive()){ callback(resp) }\n })\n this.reqs.add(req)\n }\n}\n", "/**\n * Initializes the Presence\n * @param {Channel} channel - The Channel\n * @param {Object} opts - The options,\n * for example `{events: {state: \"state\", diff: \"diff\"}}`\n */\nexport default class Presence {\n\n constructor(channel, opts = {}){\n let events = opts.events || {state: \"presence_state\", diff: \"presence_diff\"}\n this.state = {}\n this.pendingDiffs = []\n this.channel = channel\n this.joinRef = null\n this.caller = {\n onJoin: function (){ },\n onLeave: function (){ },\n onSync: function (){ }\n }\n\n this.channel.on(events.state, newState => {\n let {onJoin, onLeave, onSync} = this.caller\n\n this.joinRef = this.channel.joinRef()\n this.state = Presence.syncState(this.state, newState, onJoin, onLeave)\n\n this.pendingDiffs.forEach(diff => {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)\n })\n this.pendingDiffs = []\n onSync()\n })\n\n this.channel.on(events.diff, diff => {\n let {onJoin, onLeave, onSync} = this.caller\n\n if(this.inPendingSyncState()){\n this.pendingDiffs.push(diff)\n } else {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)\n onSync()\n }\n })\n }\n\n onJoin(callback){ this.caller.onJoin = callback }\n\n onLeave(callback){ this.caller.onLeave = callback }\n\n onSync(callback){ this.caller.onSync = callback }\n\n list(by){ return Presence.list(this.state, by) }\n\n inPendingSyncState(){\n return !this.joinRef || (this.joinRef !== this.channel.joinRef())\n }\n\n // lower-level public static API\n\n /**\n * Used to sync the list of presences on the server\n * with the client's state. An optional `onJoin` and `onLeave` callback can\n * be provided to react to changes in the client's local presences across\n * disconnects and reconnects with the server.\n *\n * @returns {Presence}\n */\n static syncState(currentState, newState, onJoin, onLeave){\n let state = this.clone(currentState)\n let joins = {}\n let leaves = {}\n\n this.map(state, (key, presence) => {\n if(!newState[key]){\n leaves[key] = presence\n }\n })\n this.map(newState, (key, newPresence) => {\n let currentPresence = state[key]\n if(currentPresence){\n let newRefs = newPresence.metas.map(m => m.phx_ref)\n let curRefs = currentPresence.metas.map(m => m.phx_ref)\n let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0)\n let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0)\n if(joinedMetas.length > 0){\n joins[key] = newPresence\n joins[key].metas = joinedMetas\n }\n if(leftMetas.length > 0){\n leaves[key] = this.clone(currentPresence)\n leaves[key].metas = leftMetas\n }\n } else {\n joins[key] = newPresence\n }\n })\n return this.syncDiff(state, {joins: joins, leaves: leaves}, onJoin, onLeave)\n }\n\n /**\n *\n * Used to sync a diff of presence join and leave\n * events from the server, as they happen. Like `syncState`, `syncDiff`\n * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n * joining or leaving from a device.\n *\n * @returns {Presence}\n */\n static syncDiff(state, diff, onJoin, onLeave){\n let {joins, leaves} = this.clone(diff)\n if(!onJoin){ onJoin = function (){ } }\n if(!onLeave){ onLeave = function (){ } }\n\n this.map(joins, (key, newPresence) => {\n let currentPresence = state[key]\n state[key] = this.clone(newPresence)\n if(currentPresence){\n let joinedRefs = state[key].metas.map(m => m.phx_ref)\n let curMetas = currentPresence.metas.filter(m => joinedRefs.indexOf(m.phx_ref) < 0)\n state[key].metas.unshift(...curMetas)\n }\n onJoin(key, currentPresence, newPresence)\n })\n this.map(leaves, (key, leftPresence) => {\n let currentPresence = state[key]\n if(!currentPresence){ return }\n let refsToRemove = leftPresence.metas.map(m => m.phx_ref)\n currentPresence.metas = currentPresence.metas.filter(p => {\n return refsToRemove.indexOf(p.phx_ref) < 0\n })\n onLeave(key, currentPresence, leftPresence)\n if(currentPresence.metas.length === 0){\n delete state[key]\n }\n })\n return state\n }\n\n /**\n * Returns the array of presences, with selected metadata.\n *\n * @param {Object} presences\n * @param {Function} chooser\n *\n * @returns {Presence}\n */\n static list(presences, chooser){\n if(!chooser){ chooser = function (key, pres){ return pres } }\n\n return this.map(presences, (key, presence) => {\n return chooser(key, presence)\n })\n }\n\n // private\n\n static map(obj, func){\n return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key]))\n }\n\n static clone(obj){ return JSON.parse(JSON.stringify(obj)) }\n}\n", "/* The default serializer for encoding and decoding messages */\nimport {\n CHANNEL_EVENTS\n} from \"./constants\"\n\nexport default {\n HEADER_LENGTH: 1,\n META_LENGTH: 4,\n KINDS: {push: 0, reply: 1, broadcast: 2},\n\n encode(msg, callback){\n if(msg.payload.constructor === ArrayBuffer){\n return callback(this.binaryEncode(msg))\n } else {\n let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]\n return callback(JSON.stringify(payload))\n }\n },\n\n decode(rawPayload, callback){\n if(rawPayload.constructor === ArrayBuffer){\n return callback(this.binaryDecode(rawPayload))\n } else {\n let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload)\n return callback({join_ref, ref, topic, event, payload})\n }\n },\n\n // private\n\n binaryEncode(message){\n let {join_ref, ref, event, topic, payload} = message\n let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length\n let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength)\n let view = new DataView(header)\n let offset = 0\n\n view.setUint8(offset++, this.KINDS.push) // kind\n view.setUint8(offset++, join_ref.length)\n view.setUint8(offset++, ref.length)\n view.setUint8(offset++, topic.length)\n view.setUint8(offset++, event.length)\n Array.from(join_ref, char => view.setUint8(offset++, char.charCodeAt(0)))\n Array.from(ref, char => view.setUint8(offset++, char.charCodeAt(0)))\n Array.from(topic, char => view.setUint8(offset++, char.charCodeAt(0)))\n Array.from(event, char => view.setUint8(offset++, char.charCodeAt(0)))\n\n var combined = new Uint8Array(header.byteLength + payload.byteLength)\n combined.set(new Uint8Array(header), 0)\n combined.set(new Uint8Array(payload), header.byteLength)\n\n return combined.buffer\n },\n\n binaryDecode(buffer){\n let view = new DataView(buffer)\n let kind = view.getUint8(0)\n let decoder = new TextDecoder()\n switch(kind){\n case this.KINDS.push: return this.decodePush(buffer, view, decoder)\n case this.KINDS.reply: return this.decodeReply(buffer, view, decoder)\n case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder)\n }\n },\n\n decodePush(buffer, view, decoder){\n let joinRefSize = view.getUint8(1)\n let topicSize = view.getUint8(2)\n let eventSize = view.getUint8(3)\n let offset = this.HEADER_LENGTH + this.META_LENGTH - 1 // pushes have no ref\n let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize))\n offset = offset + joinRefSize\n let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n offset = offset + topicSize\n let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n offset = offset + eventSize\n let data = buffer.slice(offset, buffer.byteLength)\n return {join_ref: joinRef, ref: null, topic: topic, event: event, payload: data}\n },\n\n decodeReply(buffer, view, decoder){\n let joinRefSize = view.getUint8(1)\n let refSize = view.getUint8(2)\n let topicSize = view.getUint8(3)\n let eventSize = view.getUint8(4)\n let offset = this.HEADER_LENGTH + this.META_LENGTH\n let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize))\n offset = offset + joinRefSize\n let ref = decoder.decode(buffer.slice(offset, offset + refSize))\n offset = offset + refSize\n let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n offset = offset + topicSize\n let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n offset = offset + eventSize\n let data = buffer.slice(offset, buffer.byteLength)\n let payload = {status: event, response: data}\n return {join_ref: joinRef, ref: ref, topic: topic, event: CHANNEL_EVENTS.reply, payload: payload}\n },\n\n decodeBroadcast(buffer, view, decoder){\n let topicSize = view.getUint8(1)\n let eventSize = view.getUint8(2)\n let offset = this.HEADER_LENGTH + 2\n let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n offset = offset + topicSize\n let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n offset = offset + eventSize\n let data = buffer.slice(offset, buffer.byteLength)\n\n return {join_ref: null, ref: null, topic: topic, event: event, payload: data}\n }\n}\n", "import {\n global,\n phxWindow,\n CHANNEL_EVENTS,\n DEFAULT_TIMEOUT,\n DEFAULT_VSN,\n SOCKET_STATES,\n TRANSPORTS,\n WS_CLOSE_NORMAL,\n AUTH_TOKEN_PREFIX\n} from \"./constants\"\n\nimport {\n closure\n} from \"./utils\"\n\nimport Ajax from \"./ajax\"\nimport Channel from \"./channel\"\nimport LongPoll from \"./longpoll\"\nimport Serializer from \"./serializer\"\nimport Timer from \"./timer\"\n\n/** Initializes the Socket *\n *\n * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"ws://example.com/socket\"`,\n * `\"wss://example.com\"`\n * `\"/socket\"` (inherited host & protocol)\n * @param {Object} [opts] - Optional configuration\n * @param {Function} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.\n *\n * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined.\n * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`.\n *\n * @param {number} [opts.longPollFallbackMs] - The millisecond time to attempt the primary transport\n * before falling back to the LongPoll transport. Disabled by default.\n *\n * @param {boolean} [opts.debug] - When true, enables debug logging. Default false.\n *\n * @param {Function} [opts.encode] - The function to encode outgoing messages.\n *\n * Defaults to JSON encoder.\n *\n * @param {Function} [opts.decode] - The function to decode incoming messages.\n *\n * Defaults to JSON:\n *\n * ```javascript\n * (payload, callback) => callback(JSON.parse(payload))\n * ```\n *\n * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.\n *\n * Defaults `DEFAULT_TIMEOUT`\n * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message\n * @param {Function} [opts.reconnectAfterMs] - The optional function that returns the\n * socket reconnect interval, in milliseconds.\n *\n * Defaults to stepped backoff of:\n *\n * ```javascript\n * function(tries){\n * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n * }\n * ````\n *\n * @param {Function} [opts.rejoinAfterMs] - The optional function that returns the millisec\n * rejoin interval for individual channels.\n *\n * ```javascript\n * function(tries){\n * return [1000, 2000, 5000][tries - 1] || 10000\n * }\n * ````\n *\n * @param {Function} [opts.logger] - The optional function for specialized logging, ie:\n *\n * ```javascript\n * function(kind, msg, data) {\n * console.log(`${kind}: ${msg}`, data)\n * }\n * ```\n *\n * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.\n *\n * Defaults to 20s (double the server long poll timer).\n *\n * @param {(Object|function)} [opts.params] - The optional params to pass when connecting\n * @param {string} [opts.authToken] - the optional authentication token to be exposed on the server\n * under the `:auth_token` connect_info key.\n * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.\n *\n * Defaults to \"arraybuffer\"\n *\n * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect.\n *\n * Defaults to DEFAULT_VSN.\n *\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is\n * useful when Phoenix won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain channel in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] || null }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n*/\nexport default class Socket {\n constructor(endPoint, opts = {}){\n this.stateChangeCallbacks = {open: [], close: [], error: [], message: []}\n this.channels = []\n this.sendBuffer = []\n this.ref = 0\n this.timeout = opts.timeout || DEFAULT_TIMEOUT\n this.transport = opts.transport || global.WebSocket || LongPoll\n this.primaryPassedHealthCheck = false\n this.longPollFallbackMs = opts.longPollFallbackMs\n this.fallbackTimer = null\n this.sessionStore = opts.sessionStorage || (global && global.sessionStorage)\n this.establishedConnections = 0\n this.defaultEncoder = Serializer.encode.bind(Serializer)\n this.defaultDecoder = Serializer.decode.bind(Serializer)\n this.closeWasClean = false\n this.disconnecting = false\n this.binaryType = opts.binaryType || \"arraybuffer\"\n this.connectClock = 1\n if(this.transport !== LongPoll){\n this.encode = opts.encode || this.defaultEncoder\n this.decode = opts.decode || this.defaultDecoder\n } else {\n this.encode = this.defaultEncoder\n this.decode = this.defaultDecoder\n }\n let awaitingConnectionOnPageShow = null\n if(phxWindow && phxWindow.addEventListener){\n phxWindow.addEventListener(\"pagehide\", _e => {\n if(this.conn){\n this.disconnect()\n awaitingConnectionOnPageShow = this.connectClock\n }\n })\n phxWindow.addEventListener(\"pageshow\", _e => {\n if(awaitingConnectionOnPageShow === this.connectClock){\n awaitingConnectionOnPageShow = null\n this.connect()\n }\n })\n }\n this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000\n this.rejoinAfterMs = (tries) => {\n if(opts.rejoinAfterMs){\n return opts.rejoinAfterMs(tries)\n } else {\n return [1000, 2000, 5000][tries - 1] || 10000\n }\n }\n this.reconnectAfterMs = (tries) => {\n if(opts.reconnectAfterMs){\n return opts.reconnectAfterMs(tries)\n } else {\n return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n }\n }\n this.logger = opts.logger || null\n if(!this.logger && opts.debug){\n this.logger = (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }\n }\n this.longpollerTimeout = opts.longpollerTimeout || 20000\n this.params = closure(opts.params || {})\n this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`\n this.vsn = opts.vsn || DEFAULT_VSN\n this.heartbeatTimeoutTimer = null\n this.heartbeatTimer = null\n this.pendingHeartbeatRef = null\n this.reconnectTimer = new Timer(() => {\n this.teardown(() => this.connect())\n }, this.reconnectAfterMs)\n this.authToken = opts.authToken\n }\n\n /**\n * Returns the LongPoll transport reference\n */\n getLongPollTransport(){ return LongPoll }\n\n /**\n * Disconnects and replaces the active transport\n *\n * @param {Function} newTransport - The new transport class to instantiate\n *\n */\n replaceTransport(newTransport){\n this.connectClock++\n this.closeWasClean = true\n clearTimeout(this.fallbackTimer)\n this.reconnectTimer.reset()\n if(this.conn){\n this.conn.close()\n this.conn = null\n }\n this.transport = newTransport\n }\n\n /**\n * Returns the socket protocol\n *\n * @returns {string}\n */\n protocol(){ return location.protocol.match(/^https/) ? \"wss\" : \"ws\" }\n\n /**\n * The fully qualified socket url\n *\n * @returns {string}\n */\n endPointURL(){\n let uri = Ajax.appendParams(\n Ajax.appendParams(this.endPoint, this.params()), {vsn: this.vsn})\n if(uri.charAt(0) !== \"/\"){ return uri }\n if(uri.charAt(1) === \"/\"){ return `${this.protocol()}:${uri}` }\n\n return `${this.protocol()}://${location.host}${uri}`\n }\n\n /**\n * Disconnects the socket\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n *\n * @param {Function} callback - Optional callback which is called after socket is disconnected.\n * @param {integer} code - A status code for disconnection (Optional).\n * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n */\n disconnect(callback, code, reason){\n this.connectClock++\n this.disconnecting = true\n this.closeWasClean = true\n clearTimeout(this.fallbackTimer)\n this.reconnectTimer.reset()\n this.teardown(() => {\n this.disconnecting = false\n callback && callback()\n }, code, reason)\n }\n\n /**\n *\n * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n *\n * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n */\n connect(params){\n if(params){\n console && console.log(\"passing params to connect is deprecated. Instead pass :params to the Socket constructor\")\n this.params = closure(params)\n }\n if(this.conn && !this.disconnecting){ return }\n if(this.longPollFallbackMs && this.transport !== LongPoll){\n this.connectWithFallback(LongPoll, this.longPollFallbackMs)\n } else {\n this.transportConnect()\n }\n }\n\n /**\n * Logs the message. Override `this.logger` for specialized logging. noops by default\n * @param {string} kind\n * @param {string} msg\n * @param {Object} data\n */\n log(kind, msg, data){ this.logger && this.logger(kind, msg, data) }\n\n /**\n * Returns true if a logger has been set on this socket.\n */\n hasLogger(){ return this.logger !== null }\n\n /**\n * Registers callbacks for connection open events\n *\n * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n *\n * @param {Function} callback\n */\n onOpen(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.open.push([ref, callback])\n return ref\n }\n\n /**\n * Registers callbacks for connection close events\n * @param {Function} callback\n */\n onClose(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.close.push([ref, callback])\n return ref\n }\n\n /**\n * Registers callbacks for connection error events\n *\n * @example socket.onError(function(error){ alert(\"An error occurred\") })\n *\n * @param {Function} callback\n */\n onError(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.error.push([ref, callback])\n return ref\n }\n\n /**\n * Registers callbacks for connection message events\n * @param {Function} callback\n */\n onMessage(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.message.push([ref, callback])\n return ref\n }\n\n /**\n * Pings the server and invokes the callback with the RTT in milliseconds\n * @param {Function} callback\n *\n * Returns true if the ping was pushed or false if unable to be pushed.\n */\n ping(callback){\n if(!this.isConnected()){ return false }\n let ref = this.makeRef()\n let startTime = Date.now()\n this.push({topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: ref})\n let onMsgRef = this.onMessage(msg => {\n if(msg.ref === ref){\n this.off([onMsgRef])\n callback(Date.now() - startTime)\n }\n })\n return true\n }\n\n /**\n * @private\n */\n\n transportConnect(){\n this.connectClock++\n this.closeWasClean = false\n let protocols = undefined\n // Sec-WebSocket-Protocol based token\n // (longpoll uses Authorization header instead)\n if(this.authToken){\n protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`]\n }\n this.conn = new this.transport(this.endPointURL(), protocols)\n this.conn.binaryType = this.binaryType\n this.conn.timeout = this.longpollerTimeout\n this.conn.onopen = () => this.onConnOpen()\n this.conn.onerror = error => this.onConnError(error)\n this.conn.onmessage = event => this.onConnMessage(event)\n this.conn.onclose = event => this.onConnClose(event)\n }\n\n getSession(key){ return this.sessionStore && this.sessionStore.getItem(key) }\n\n storeSession(key, val){ this.sessionStore && this.sessionStore.setItem(key, val) }\n\n connectWithFallback(fallbackTransport, fallbackThreshold = 2500){\n clearTimeout(this.fallbackTimer)\n let established = false\n let primaryTransport = true\n let openRef, errorRef\n let fallback = (reason) => {\n this.log(\"transport\", `falling back to ${fallbackTransport.name}...`, reason)\n this.off([openRef, errorRef])\n primaryTransport = false\n this.replaceTransport(fallbackTransport)\n this.transportConnect()\n }\n if(this.getSession(`phx:fallback:${fallbackTransport.name}`)){ return fallback(\"memorized\") }\n\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold)\n\n errorRef = this.onError(reason => {\n this.log(\"transport\", \"error\", reason)\n if(primaryTransport && !established){\n clearTimeout(this.fallbackTimer)\n fallback(reason)\n }\n })\n this.onOpen(() => {\n established = true\n if(!primaryTransport){\n // only memorize LP if we never connected to primary\n if(!this.primaryPassedHealthCheck){ this.storeSession(`phx:fallback:${fallbackTransport.name}`, \"true\") }\n return this.log(\"transport\", `established ${fallbackTransport.name} fallback`)\n }\n // if we've established primary, give the fallback a new period to attempt ping\n clearTimeout(this.fallbackTimer)\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold)\n this.ping(rtt => {\n this.log(\"transport\", \"connected to primary after\", rtt)\n this.primaryPassedHealthCheck = true\n clearTimeout(this.fallbackTimer)\n })\n })\n this.transportConnect()\n }\n\n clearHeartbeats(){\n clearTimeout(this.heartbeatTimer)\n clearTimeout(this.heartbeatTimeoutTimer)\n }\n\n onConnOpen(){\n if(this.hasLogger()) this.log(\"transport\", `${this.transport.name} connected to ${this.endPointURL()}`)\n this.closeWasClean = false\n this.disconnecting = false\n this.establishedConnections++\n this.flushSendBuffer()\n this.reconnectTimer.reset()\n this.resetHeartbeat()\n this.stateChangeCallbacks.open.forEach(([, callback]) => callback())\n }\n\n /**\n * @private\n */\n\n heartbeatTimeout(){\n if(this.pendingHeartbeatRef){\n this.pendingHeartbeatRef = null\n if(this.hasLogger()){ this.log(\"transport\", \"heartbeat timeout. Attempting to re-establish connection\") }\n this.triggerChanError()\n this.closeWasClean = false\n this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, \"heartbeat timeout\")\n }\n }\n\n resetHeartbeat(){\n if(this.conn && this.conn.skipHeartbeat){ return }\n this.pendingHeartbeatRef = null\n this.clearHeartbeats()\n this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs)\n }\n\n teardown(callback, code, reason){\n if(!this.conn){\n return callback && callback()\n }\n let connectClock = this.connectClock\n\n this.waitForBufferDone(() => {\n if(connectClock !== this.connectClock){ return }\n if(this.conn){\n if(code){ this.conn.close(code, reason || \"\") } else { this.conn.close() }\n }\n\n this.waitForSocketClosed(() => {\n if(connectClock !== this.connectClock){ return }\n if(this.conn){\n this.conn.onopen = function (){ } // noop\n this.conn.onerror = function (){ } // noop\n this.conn.onmessage = function (){ } // noop\n this.conn.onclose = function (){ } // noop\n this.conn = null\n }\n\n callback && callback()\n })\n })\n }\n\n waitForBufferDone(callback, tries = 1){\n if(tries === 5 || !this.conn || !this.conn.bufferedAmount){\n callback()\n return\n }\n\n setTimeout(() => {\n this.waitForBufferDone(callback, tries + 1)\n }, 150 * tries)\n }\n\n waitForSocketClosed(callback, tries = 1){\n if(tries === 5 || !this.conn || this.conn.readyState === SOCKET_STATES.closed){\n callback()\n return\n }\n\n setTimeout(() => {\n this.waitForSocketClosed(callback, tries + 1)\n }, 150 * tries)\n }\n\n onConnClose(event){\n let closeCode = event && event.code\n if(this.hasLogger()) this.log(\"transport\", \"close\", event)\n this.triggerChanError()\n this.clearHeartbeats()\n if(!this.closeWasClean && closeCode !== 1000){\n this.reconnectTimer.scheduleTimeout()\n }\n this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event))\n }\n\n /**\n * @private\n */\n onConnError(error){\n if(this.hasLogger()) this.log(\"transport\", error)\n let transportBefore = this.transport\n let establishedBefore = this.establishedConnections\n this.stateChangeCallbacks.error.forEach(([, callback]) => {\n callback(error, transportBefore, establishedBefore)\n })\n if(transportBefore === this.transport || establishedBefore > 0){\n this.triggerChanError()\n }\n }\n\n /**\n * @private\n */\n triggerChanError(){\n this.channels.forEach(channel => {\n if(!(channel.isErrored() || channel.isLeaving() || channel.isClosed())){\n channel.trigger(CHANNEL_EVENTS.error)\n }\n })\n }\n\n /**\n * @returns {string}\n */\n connectionState(){\n switch(this.conn && this.conn.readyState){\n case SOCKET_STATES.connecting: return \"connecting\"\n case SOCKET_STATES.open: return \"open\"\n case SOCKET_STATES.closing: return \"closing\"\n default: return \"closed\"\n }\n }\n\n /**\n * @returns {boolean}\n */\n isConnected(){ return this.connectionState() === \"open\" }\n\n /**\n * @private\n *\n * @param {Channel}\n */\n remove(channel){\n this.off(channel.stateChangeRefs)\n this.channels = this.channels.filter(c => c !== channel)\n }\n\n /**\n * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n *\n * @param {refs} - list of refs returned by calls to\n * `onOpen`, `onClose`, `onError,` and `onMessage`\n */\n off(refs){\n for(let key in this.stateChangeCallbacks){\n this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {\n return refs.indexOf(ref) === -1\n })\n }\n }\n\n /**\n * Initiates a new channel for the given topic\n *\n * @param {string} topic\n * @param {Object} chanParams - Parameters for the channel\n * @returns {Channel}\n */\n channel(topic, chanParams = {}){\n let chan = new Channel(topic, chanParams, this)\n this.channels.push(chan)\n return chan\n }\n\n /**\n * @param {Object} data\n */\n push(data){\n if(this.hasLogger()){\n let {topic, event, payload, ref, join_ref} = data\n this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload)\n }\n\n if(this.isConnected()){\n this.encode(data, result => this.conn.send(result))\n } else {\n this.sendBuffer.push(() => this.encode(data, result => this.conn.send(result)))\n }\n }\n\n /**\n * Return the next message ref, accounting for overflows\n * @returns {string}\n */\n makeRef(){\n let newRef = this.ref + 1\n if(newRef === this.ref){ this.ref = 0 } else { this.ref = newRef }\n\n return this.ref.toString()\n }\n\n sendHeartbeat(){\n if(this.pendingHeartbeatRef && !this.isConnected()){ return }\n this.pendingHeartbeatRef = this.makeRef()\n this.push({topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: this.pendingHeartbeatRef})\n this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs)\n }\n\n flushSendBuffer(){\n if(this.isConnected() && this.sendBuffer.length > 0){\n this.sendBuffer.forEach(callback => callback())\n this.sendBuffer = []\n }\n }\n\n onConnMessage(rawMessage){\n this.decode(rawMessage.data, msg => {\n let {topic, event, payload, ref, join_ref} = msg\n if(ref && ref === this.pendingHeartbeatRef){\n this.clearHeartbeats()\n this.pendingHeartbeatRef = null\n this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs)\n }\n\n if(this.hasLogger()) this.log(\"receive\", `${payload.status || \"\"} ${topic} ${event} ${ref && \"(\" + ref + \")\" || \"\"}`, payload)\n\n for(let i = 0; i < this.channels.length; i++){\n const channel = this.channels[i]\n if(!channel.isMember(topic, event, payload, join_ref)){ continue }\n channel.trigger(event, payload, ref, join_ref)\n }\n\n for(let i = 0; i < this.stateChangeCallbacks.message.length; i++){\n let [, callback] = this.stateChangeCallbacks.message[i]\n callback(msg)\n }\n })\n }\n\n leaveOpenTopic(topic){\n let dupChannel = this.channels.find(c => c.topic === topic && (c.isJoined() || c.isJoining()))\n if(dupChannel){\n if(this.hasLogger()) this.log(\"transport\", `leaving duplicate topic \"${topic}\"`)\n dupChannel.leave()\n }\n }\n}\n"], - "mappings": "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", - "names": ["closure"] + "sources": ["../../assets/js/phoenix/index.ts", "../../assets/js/phoenix/utils.ts", "../../assets/js/phoenix/constants.ts", "../../assets/js/phoenix/push.ts", "../../assets/js/phoenix/timer.ts", "../../assets/js/phoenix/channel.ts", "../../assets/js/phoenix/ajax.ts", "../../assets/js/phoenix/longpoll.ts", "../../assets/js/phoenix/presence.ts", "../../assets/js/phoenix/serializer.ts", "../../assets/js/phoenix/socket.ts"], + "sourcesContent": ["/**\n * Phoenix Channels JavaScript client\n *\n * ## Socket Connection\n *\n * A single connection is established to the server and\n * channels are multiplexed over the connection.\n * Connect to the server using the `Socket` class:\n *\n * ```javascript\n * let socket = new Socket(\"/socket\", {params: {userToken: \"123\"}})\n * socket.connect()\n * ```\n *\n * The `Socket` constructor takes the mount point of the socket,\n * the authentication params, as well as options that can be found in\n * the Socket docs, such as configuring the `LongPoll` transport, and\n * heartbeat.\n *\n * ## Channels\n *\n * Channels are isolated, concurrent processes on the server that\n * subscribe to topics and broker events between the client and server.\n * To join a channel, you must provide the topic, and channel params for\n * authorization. Here's an example chat room example where `\"new_msg\"`\n * events are listened for, messages are pushed to the server, and\n * the channel is joined with ok/error/timeout matches:\n *\n * ```\n * let channel = socket.channel(\"room:123\", {token: roomToken})\n * channel.on(\"new_msg\", msg => console.log(\"Got message\", msg) )\n * $input.onEnter( e => {\n * channel.push(\"new_msg\", {body: e.target.val}, 10000)\n * .receive(\"ok\", (msg) => console.log(\"created message\", msg) )\n * .receive(\"error\", (reasons) => console.log(\"create failed\", reasons) )\n * .receive(\"timeout\", () => console.log(\"Networking issue...\") )\n * })\n *\n * channel.join()\n * .receive(\"ok\", ({messages}) => console.log(\"catching up\", messages) )\n * .receive(\"error\", ({reason}) => console.log(\"failed join\", reason) )\n * .receive(\"timeout\", () => console.log(\"Networking issue. Still waiting...\"))\n *```\n *\n * ## Joining\n *\n * Creating a channel with `socket.channel(topic, params)`, binds the params to\n * `channel.params`, which are sent up on `channel.join()`.\n * Subsequent rejoins will send up the modified params for\n * updating authorization params, or passing up last_message_id information.\n * Successful joins receive an \"ok\" status, while unsuccessful joins\n * receive \"error\".\n *\n * With the default serializers and WebSocket transport, JSON text frames are\n * used for pushing a JSON object literal. If an `ArrayBuffer` instance is provided,\n * binary encoding will be used and the message will be sent with the binary\n * opcode.\n *\n * *Note*: binary messages are only supported on the WebSocket transport.\n *\n * ## Duplicate Join Subscriptions\n *\n * While the client may join any number of topics on any number of channels,\n * the client may only hold a single subscription for each unique topic at any\n * given time. When attempting to create a duplicate subscription,\n * the server will close the existing channel, log a warning, and\n * spawn a new channel for the topic. The client will have their\n * `channel.onClose` callbacks fired for the existing channel, and the new\n * channel join will have its receive hooks processed as normal.\n *\n * ## Pushing Messages\n *\n * From the previous example, we can see that pushing messages to the server\n * can be done with `channel.push(eventName, payload)` and we can optionally\n * receive responses from the push. Additionally, we can use\n * `receive(\"timeout\", callback)` to abort waiting for our other `receive` hooks\n * and take action after some period of waiting. The default timeout is 10000ms.\n *\n *\n * ## Socket Hooks\n *\n * Lifecycle events of the multiplexed connection can be hooked into via\n * `socket.onError()` and `socket.onClose()` events, ie:\n *\n * ```\n * socket.onError( () => console.log(\"there was an error with the connection!\") )\n * socket.onClose( () => console.log(\"the connection dropped\") )\n * ```\n *\n *\n * ## Channel Hooks\n *\n * For each joined channel, you can bind to `onError` and `onClose` events\n * to monitor the channel lifecycle, ie:\n *\n * ```\n * channel.onError( () => console.log(\"there was an error!\") )\n * channel.onClose( () => console.log(\"the channel has gone away gracefully\") )\n * ```\n *\n * ### onError hooks\n *\n * `onError` hooks are invoked if the socket connection drops, or the channel\n * crashes on the server. In either case, a channel rejoin is attempted\n * automatically in an exponential backoff manner.\n *\n * ### onClose hooks\n *\n * `onClose` hooks are invoked only in two cases. 1) the channel explicitly\n * closed on the server, or 2). The client explicitly closed, by calling\n * `channel.leave()`\n *\n *\n * ## Presence\n *\n * The `Presence` object provides features for syncing presence information\n * from the server with the client and handling presences joining and leaving.\n *\n * ### Syncing state from the server\n *\n * To sync presence state from the server, first instantiate an object and\n * pass your channel in to track lifecycle events:\n *\n * ```\n * let channel = socket.channel(\"some:topic\")\n * let presence = new Presence(channel)\n * ```\n *\n * Next, use the `presence.onSync` callback to react to state changes\n * from the server. For example, to render the list of users every time\n * the list changes, you could write:\n *\n * ```\n * presence.onSync(() => {\n * myRenderUsersFunction(presence.list())\n * })\n * ```\n *\n * ### Listing Presences\n *\n * `presence.list` is used to return a list of presence information\n * based on the local state of metadata. By default, all presence\n * metadata is returned, but a `listBy` function can be supplied to\n * allow the client to select which metadata to use for a given presence.\n * For example, you may have a user online from different devices with\n * a metadata status of \"online\", but they have set themselves to \"away\"\n * on another device. In this case, the app may choose to use the \"away\"\n * status for what appears on the UI. The example below defines a `listBy`\n * function which prioritizes the first metadata which was registered for\n * each user. This could be the first tab they opened, or the first device\n * they came online from:\n *\n * ```\n * let listBy = (id, {metas: [first, ...rest]}) => {\n * first.count = rest.length + 1 // count of this user's presences\n * first.id = id\n * return first\n * }\n * let onlineUsers = presence.list(listBy)\n * ```\n *\n * ### Handling individual presence join and leave events\n *\n * The `presence.onJoin` and `presence.onLeave` callbacks can be used to\n * react to individual presences joining and leaving the app. For example:\n *\n * ```\n * let presence = new Presence(channel)\n *\n * // detect if user has joined for the 1st time or from another tab/device\n * presence.onJoin((id, current, newPres) => {\n * if(!current){\n * console.log(\"user has entered for the first time\", newPres)\n * } else {\n * console.log(\"user additional presence\", newPres)\n * }\n * })\n *\n * // detect if user has left from all tabs/devices, or is still present\n * presence.onLeave((id, current, leftPres) => {\n * if(current.metas.length === 0){\n * console.log(\"user has left from all devices\", leftPres)\n * } else {\n * console.log(\"user left from a device\", leftPres)\n * }\n * })\n * // receive presence data from server\n * presence.onSync(() => {\n * displayUsers(presence.list())\n * })\n * ```\n * @module phoenix\n */\n\nimport Channel from \"./channel\";\nimport LongPoll from \"./longpoll\";\nimport Presence from \"./presence\";\nimport Serializer from \"./serializer\";\nimport Socket from \"./socket\";\n\nexport { Channel, LongPoll, Presence, Serializer, Socket };\n", "// wraps value in closure or returns closure\nexport function closure(value: T | (() => T)): () => T {\n if (typeof value === \"function\") {\n return value as () => T;\n } else {\n return () => value;\n }\n}\n", "export const globalSelf = typeof self !== \"undefined\" ? self : null;\nexport const phxWindow = typeof window !== \"undefined\" ? window : null;\nexport const global = globalSelf || phxWindow || globalThis;\nexport const DEFAULT_VSN = \"2.0.0\";\n\nexport const SOCKET_STATES = {\n connecting: 0,\n open: 1,\n closing: 2,\n closed: 3,\n} as const;\n\nexport type SocketState = (typeof SOCKET_STATES)[keyof typeof SOCKET_STATES];\n\nexport const DEFAULT_TIMEOUT = 10000;\nexport const WS_CLOSE_NORMAL = 1000;\n\nexport const CHANNEL_STATES = {\n closed: \"closed\",\n errored: \"errored\",\n joined: \"joined\",\n joining: \"joining\",\n leaving: \"leaving\",\n} as const;\n\nexport type ChannelState = (typeof CHANNEL_STATES)[keyof typeof CHANNEL_STATES];\n\nexport const CHANNEL_EVENTS = {\n close: \"phx_close\",\n error: \"phx_error\",\n join: \"phx_join\",\n reply: \"phx_reply\",\n leave: \"phx_leave\",\n} as const;\n\nexport type ChannelEvent = (typeof CHANNEL_EVENTS)[keyof typeof CHANNEL_EVENTS];\n\nexport const TRANSPORTS = {\n longpoll: \"longpoll\",\n websocket: \"websocket\",\n} as const;\n\nexport type Transport = (typeof TRANSPORTS)[keyof typeof TRANSPORTS];\n\nexport const XHR_STATES = {\n complete: 4,\n} as const;\n\nexport const AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\";\n", "import type Channel from \"./channel\";\n\nexport interface PushResponse {\n status: string;\n response: any;\n _ref?: string;\n}\n\nexport interface ReceiveHook {\n status: string;\n callback: (response: any) => void;\n}\n\n/**\n * Initializes the Push\n * @param channel - The Channel\n * @param event - The event, for example `\"phx_join\"`\n * @param payload - The payload, for example `{user_id: 123}`\n * @param timeout - The push timeout in milliseconds\n * @internal\n */\nexport default class Push {\n public channel: Channel;\n public event: string;\n public payload: () => any;\n public receivedResp: PushResponse | null;\n public timeout: number;\n public timeoutTimer: number | null;\n public recHooks: ReceiveHook[];\n public sent: boolean;\n public ref: string | null;\n public refEvent: string | null;\n\n constructor(\n channel: Channel,\n event: string,\n payload: any | (() => any),\n timeout: number,\n ) {\n this.channel = channel;\n this.event = event;\n this.payload =\n typeof payload === \"function\" ? payload : () => payload || {};\n this.receivedResp = null;\n this.timeout = timeout;\n this.timeoutTimer = null;\n this.recHooks = [];\n this.sent = false;\n this.ref = null;\n this.refEvent = null;\n }\n\n /**\n * Resend the push with a new timeout\n */\n resend(timeout: number): void {\n this.timeout = timeout;\n this.reset();\n this.send();\n }\n\n /**\n * Send the push\n */\n send(): void {\n if (this.hasReceived(\"timeout\")) {\n return;\n }\n this.startTimeout();\n this.sent = true;\n this.channel.socket.push({\n topic: this.channel.topic,\n event: this.event,\n payload: this.payload(),\n ref: this.ref,\n join_ref: this.channel.joinRef(),\n });\n }\n\n /**\n * Register a callback for a specific response status\n */\n receive(status: string, callback: (response: any) => void): Push {\n if (this.hasReceived(status)) {\n callback(this.receivedResp!.response);\n }\n\n this.recHooks.push({ status, callback });\n return this;\n }\n\n /**\n * @private\n */\n reset(): void {\n this.cancelRefEvent();\n this.ref = null;\n this.refEvent = null;\n this.receivedResp = null;\n this.sent = false;\n }\n\n /**\n * @private\n */\n matchReceive({ status, response, _ref }: PushResponse): void {\n this.recHooks\n .filter((h) => h.status === status)\n .forEach((h) => h.callback(response));\n }\n\n /**\n * @private\n */\n cancelRefEvent(): void {\n if (!this.refEvent) {\n return;\n }\n this.channel.off(this.refEvent);\n }\n\n /**\n * @private\n */\n cancelTimeout(): void {\n if (this.timeoutTimer !== null) {\n clearTimeout(this.timeoutTimer);\n this.timeoutTimer = null;\n }\n }\n\n /**\n * @private\n */\n startTimeout(): void {\n if (this.timeoutTimer) {\n this.cancelTimeout();\n }\n this.ref = this.channel.socket.makeRef();\n this.refEvent = this.channel.replyEventName(this.ref);\n\n this.channel.on(this.refEvent, (payload: PushResponse) => {\n this.cancelRefEvent();\n this.cancelTimeout();\n this.receivedResp = payload;\n this.matchReceive(payload);\n });\n\n this.timeoutTimer = setTimeout(() => {\n this.trigger(\"timeout\", {});\n }, this.timeout) as any;\n }\n\n /**\n * @private\n */\n hasReceived(status: string): boolean {\n return this.receivedResp && this.receivedResp.status === status;\n }\n\n /**\n * @private\n */\n trigger(status: string, response: any): void {\n this.channel.trigger(this.refEvent!, { status, response });\n }\n}\n", "/**\n *\n * Creates a timer that accepts a `timerCalc` function to perform\n * calculated timeout retries, such as exponential backoff.\n *\n * @example\n * let reconnectTimer = new Timer(() => this.connect(), function(tries){\n * return [1000, 5000, 10000][tries - 1] || 10000\n * })\n * reconnectTimer.scheduleTimeout() // fires after 1000\n * reconnectTimer.scheduleTimeout() // fires after 5000\n * reconnectTimer.reset()\n * reconnectTimer.scheduleTimeout() // fires after 1000\n */\nexport default class Timer {\n private callback: () => void;\n private timerCalc: (tries: number) => number;\n private timer: number | null;\n private tries: number;\n\n constructor(callback: () => void, timerCalc: (tries: number) => number) {\n this.callback = callback;\n this.timerCalc = timerCalc;\n this.timer = null;\n this.tries = 0;\n }\n\n reset(): void {\n this.tries = 0;\n if (this.timer !== null) {\n clearTimeout(this.timer);\n }\n }\n\n /**\n * Cancels any previous scheduleTimeout and schedules callback\n */\n scheduleTimeout(): void {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n }\n\n this.timer = setTimeout(\n () => {\n this.tries = this.tries + 1;\n this.callback();\n },\n this.timerCalc(this.tries + 1),\n ) as any;\n }\n}\n", "import { closure } from \"./utils\";\nimport { CHANNEL_EVENTS, CHANNEL_STATES, type ChannelState } from \"./constants\";\n\nimport Push from \"./push\";\nimport Timer from \"./timer\";\nimport type Socket from \"./socket\";\n\nexport interface ChannelBinding {\n event: string;\n ref: number;\n callback: (payload: any, ref?: string, joinRef?: string) => void;\n}\n\n/**\n * Channel class for Phoenix WebSocket communication\n */\nexport default class Channel {\n public state: ChannelState;\n public topic: string;\n public params: () => any;\n public socket: Socket;\n public bindings: ChannelBinding[];\n public bindingRef: number;\n public timeout: number;\n public joinedOnce: boolean;\n public joinPush: Push;\n public pushBuffer: Push[];\n public stateChangeRefs: string[];\n public rejoinTimer: Timer;\n\n constructor(topic: string, params: any | (() => any), socket: Socket) {\n this.state = CHANNEL_STATES.closed;\n this.topic = topic;\n this.params = closure(params || {});\n this.socket = socket;\n this.bindings = [];\n this.bindingRef = 0;\n this.timeout = this.socket.timeout;\n this.joinedOnce = false;\n this.joinPush = new Push(\n this,\n CHANNEL_EVENTS.join,\n this.params,\n this.timeout,\n );\n this.pushBuffer = [];\n this.stateChangeRefs = [];\n\n this.rejoinTimer = new Timer(() => {\n if (this.socket.isConnected()) {\n this.rejoin();\n }\n }, this.socket.rejoinAfterMs);\n this.stateChangeRefs.push(\n this.socket.onError(() => this.rejoinTimer.reset()),\n );\n this.stateChangeRefs.push(\n this.socket.onOpen(() => {\n this.rejoinTimer.reset();\n if (this.isErrored()) {\n this.rejoin();\n }\n }),\n );\n this.joinPush.receive(\"ok\", () => {\n this.state = CHANNEL_STATES.joined;\n this.rejoinTimer.reset();\n this.pushBuffer.forEach((pushEvent) => pushEvent.send());\n this.pushBuffer = [];\n });\n this.joinPush.receive(\"error\", () => {\n this.state = CHANNEL_STATES.errored;\n if (this.socket.isConnected()) {\n this.rejoinTimer.scheduleTimeout();\n }\n });\n this.onClose(() => {\n this.rejoinTimer.reset();\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`);\n this.state = CHANNEL_STATES.closed;\n this.socket.remove(this);\n });\n this.onError((reason: any) => {\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", `error ${this.topic}`, reason);\n if (this.isJoining()) {\n this.joinPush.reset();\n }\n this.state = CHANNEL_STATES.errored;\n if (this.socket.isConnected()) {\n this.rejoinTimer.scheduleTimeout();\n }\n });\n this.joinPush.receive(\"timeout\", () => {\n if (this.socket.hasLogger())\n this.socket.log(\n \"channel\",\n `timeout ${this.topic} (${this.joinRef()})`,\n this.joinPush.timeout,\n );\n const leavePush = new Push(\n this,\n CHANNEL_EVENTS.leave,\n closure({}),\n this.timeout,\n );\n leavePush.send();\n this.state = CHANNEL_STATES.errored;\n this.joinPush.reset();\n if (this.socket.isConnected()) {\n this.rejoinTimer.scheduleTimeout();\n }\n });\n this.on(CHANNEL_EVENTS.reply, (payload: any, ref?: string) => {\n this.trigger(this.replyEventName(ref!), payload);\n });\n }\n\n /**\n * Join the channel\n */\n join(timeout: number = this.timeout): Push {\n if (this.joinedOnce) {\n throw new Error(\n \"tried to join multiple times. 'join' can only be called a single time per channel instance\",\n );\n } else {\n this.timeout = timeout;\n this.joinedOnce = true;\n this.rejoin();\n return this.joinPush;\n }\n }\n\n /**\n * Hook into channel close\n */\n onClose(\n callback: (payload?: any, ref?: string, joinRef?: string) => void,\n ): number {\n return this.on(CHANNEL_EVENTS.close, callback);\n }\n\n /**\n * Hook into channel errors\n */\n onError(\n callback: (reason: any, ref?: string, joinRef?: string) => void,\n ): number {\n return this.on(CHANNEL_EVENTS.error, (reason: any) => callback(reason));\n }\n\n /**\n * Subscribes on channel events\n *\n * Subscription returns a ref counter, which can be used later to\n * unsubscribe the exact event listener\n *\n * @example\n * const ref1 = channel.on(\"event\", do_stuff)\n * const ref2 = channel.on(\"event\", do_other_stuff)\n * channel.off(\"event\", ref1)\n * // Since unsubscription, do_stuff won't fire,\n * // while do_other_stuff will keep firing on the \"event\"\n */\n on(\n event: string,\n callback: (payload: any, ref?: string, joinRef?: string) => void,\n ): number {\n const ref = this.bindingRef++;\n this.bindings.push({ event, ref, callback });\n return ref;\n }\n\n /**\n * Unsubscribes off of channel events\n *\n * Use the ref returned from a channel.on() to unsubscribe one\n * handler, or pass nothing for the ref to unsubscribe all\n * handlers for the given event.\n *\n * @example\n * // Unsubscribe the do_stuff handler\n * const ref1 = channel.on(\"event\", do_stuff)\n * channel.off(\"event\", ref1)\n *\n * // Unsubscribe all handlers from event\n * channel.off(\"event\")\n */\n off(event: string, ref?: number): void {\n this.bindings = this.bindings.filter((bind) => {\n return !(\n bind.event === event &&\n (typeof ref === \"undefined\" || ref === bind.ref)\n );\n });\n }\n\n /**\n * @internal\n * @private\n */\n canPush(): boolean {\n return this.socket.isConnected() && this.isJoined();\n }\n\n /**\n * Sends a message `event` to phoenix with the payload `payload`.\n * Phoenix receives this in the `handle_in(event, payload, socket)`\n * function. if phoenix replies or it times out (default 10000ms),\n * then optionally the reply can be received.\n *\n * @example\n * channel.push(\"event\")\n * .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n * .receive(\"error\", err => console.log(\"phoenix errored\", err))\n * .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n */\n push(event: string, payload: any = {}, timeout: number = this.timeout): Push {\n if (!this.joinedOnce) {\n throw new Error(\n `tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`,\n );\n }\n const pushEvent = new Push(this, event, () => payload, timeout);\n if (this.canPush()) {\n pushEvent.send();\n } else {\n pushEvent.startTimeout();\n this.pushBuffer.push(pushEvent);\n }\n\n return pushEvent;\n }\n\n /**\n * Leaves the channel\n *\n * Unsubscribes from server events, and\n * instructs channel to terminate on server\n *\n * Triggers onClose() hooks\n *\n * To receive leave acknowledgements, use the `receive`\n * hook to bind to the server ack, ie:\n *\n * @example\n * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n */\n leave(timeout: number = this.timeout): Push {\n this.rejoinTimer.reset();\n this.joinPush.cancelTimeout();\n\n this.state = CHANNEL_STATES.leaving;\n const onClose = () => {\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", `leave ${this.topic}`);\n this.trigger(CHANNEL_EVENTS.close, \"leave\");\n };\n const leavePush = new Push(\n this,\n CHANNEL_EVENTS.leave,\n closure({}),\n timeout,\n );\n leavePush\n .receive(\"ok\", () => onClose())\n .receive(\"timeout\", () => onClose());\n leavePush.send();\n if (!this.canPush()) {\n leavePush.trigger(\"ok\", {});\n }\n\n return leavePush;\n }\n\n /**\n * Overridable message hook\n *\n * Receives all events for specialized message handling\n * before dispatching to the channel callbacks.\n *\n * Must return the payload, modified or unmodified\n */\n onMessage(\n _event: string,\n payload: any,\n _ref?: string,\n _joinRef?: string,\n ): any {\n return payload;\n }\n\n /**\n * @internal\n * @private\n */\n isMember(\n topic: string,\n event: string,\n payload: any,\n joinRef?: string,\n ): boolean {\n if (this.topic !== topic) {\n return false;\n }\n\n if (joinRef && joinRef !== this.joinRef()) {\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", \"dropping outdated message\", {\n topic,\n event,\n payload,\n joinRef,\n });\n return false;\n } else {\n return true;\n }\n }\n\n /**\n * @internal\n * @private\n */\n joinRef(): string | null {\n return this.joinPush.ref;\n }\n\n /**\n * @internal\n * @private\n */\n rejoin(timeout: number = this.timeout): void {\n if (this.isLeaving()) {\n return;\n }\n this.socket.leaveOpenTopic(this.topic);\n this.state = CHANNEL_STATES.joining;\n this.joinPush.resend(timeout);\n }\n\n /**\n * @internal\n * @private\n */\n trigger(event: string, payload: any, ref?: string, joinRef?: string): void {\n const handledPayload = this.onMessage(event, payload, ref, joinRef);\n if (payload && !handledPayload) {\n throw new Error(\n \"channel onMessage callbacks must return the payload, modified or unmodified\",\n );\n }\n\n const eventBindings = this.bindings.filter((bind) => bind.event === event);\n\n for (let i = 0; i < eventBindings.length; i++) {\n const bind = eventBindings[i]!;\n bind.callback(handledPayload, ref, joinRef || this.joinRef());\n }\n }\n\n /**\n * @internal\n * @private\n */\n replyEventName(ref: string): string {\n return `chan_reply_${ref}`;\n }\n\n /**\n * @internal\n * @private\n */\n isClosed(): boolean {\n return this.state === CHANNEL_STATES.closed;\n }\n\n /**\n * @internal\n * @private\n */\n isErrored(): boolean {\n return this.state === CHANNEL_STATES.errored;\n }\n\n /**\n * @internal\n * @private\n */\n isJoined(): boolean {\n return this.state === CHANNEL_STATES.joined;\n }\n\n /**\n * @internal\n * @private\n */\n isJoining(): boolean {\n return this.state === CHANNEL_STATES.joining;\n }\n\n /**\n * @internal\n * @private\n */\n isLeaving(): boolean {\n return this.state === CHANNEL_STATES.leaving;\n }\n}\n", "import { global, XHR_STATES } from \"./constants\";\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\ntype Headers = Record;\ntype RequestBody = string | null;\ntype AjaxCallback = (response: any) => void;\ntype TimeoutCallback = () => void;\n\ninterface XDomainRequest {\n timeout: number;\n open(method: string, url: string): void;\n send(body?: string | null): void;\n onload: (() => void) | null;\n ontimeout: (() => void) | null;\n onprogress: (() => void) | null;\n responseText: string;\n}\n\ninterface XMLHttpRequestLike {\n open(method: string, url: string, async?: boolean): void;\n send(body?: string | null): void;\n setRequestHeader(name: string, value: string): void;\n timeout: number;\n readyState: number;\n responseText: string;\n onreadystatechange: ((ev?: any) => any) | null;\n onerror: (() => void) | null;\n ontimeout: (() => void) | null;\n}\n\nexport default class Ajax {\n static request(\n method: HttpMethod,\n endPoint: string,\n headers: Headers,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): XMLHttpRequestLike | XDomainRequest | AbortController {\n if ((global as any).XDomainRequest) {\n const req = new (global as any).XDomainRequest(); // IE8, IE9\n return this.xdomainRequest(\n req,\n method,\n endPoint,\n body,\n timeout,\n ontimeout,\n callback,\n );\n } else if ((global as any).XMLHttpRequest) {\n const req = new (global as any).XMLHttpRequest(); // IE7+, Firefox, Chrome, Opera, Safari\n return this.xhrRequest(\n req,\n method,\n endPoint,\n headers,\n body,\n timeout,\n ontimeout,\n callback,\n );\n } else if (\n typeof global.fetch === \"function\" &&\n typeof global.AbortController === \"function\"\n ) {\n // Fetch with AbortController for modern browsers\n return this.fetchRequest(\n method,\n endPoint,\n headers,\n body,\n timeout,\n ontimeout,\n callback,\n );\n } else {\n throw new Error(\"No suitable XMLHttpRequest implementation found\");\n }\n }\n\n static fetchRequest(\n method: HttpMethod,\n endPoint: string,\n headers: Headers,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): AbortController {\n const options: RequestInit = {\n method,\n headers,\n body,\n };\n const controller = new AbortController();\n if (timeout) {\n setTimeout(() => controller.abort(), timeout);\n options.signal = controller.signal;\n }\n global\n .fetch(endPoint, options)\n .then((response) => response.text())\n .then((data) => this.parseJSON(data))\n .then((data) => callback && callback(data))\n .catch((err) => {\n if (err.name === \"AbortError\" && ontimeout) {\n ontimeout();\n } else {\n callback && callback(null);\n }\n });\n return controller;\n }\n\n static xdomainRequest(\n req: XDomainRequest,\n method: HttpMethod,\n endPoint: string,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): XDomainRequest {\n req.timeout = timeout;\n req.open(method, endPoint);\n req.onload = () => {\n const response = this.parseJSON(req.responseText);\n callback && callback(response);\n };\n if (ontimeout) {\n req.ontimeout = ontimeout;\n }\n\n // Work around bug in IE9 that requires an attached onprogress handler\n req.onprogress = () => {};\n\n req.send(body);\n return req;\n }\n\n static xhrRequest(\n req: XMLHttpRequestLike,\n method: HttpMethod,\n endPoint: string,\n headers: Headers,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): XMLHttpRequestLike {\n req.open(method, endPoint, true);\n req.timeout = timeout;\n for (const [key, value] of Object.entries(headers)) {\n req.setRequestHeader(key, value);\n }\n req.onerror = () => callback && callback(null);\n req.onreadystatechange = () => {\n if (req.readyState === XHR_STATES.complete && callback) {\n const response = this.parseJSON(req.responseText);\n callback(response);\n }\n };\n if (ontimeout) {\n req.ontimeout = ontimeout;\n }\n\n req.send(body);\n return req;\n }\n\n static parseJSON(resp: string | null | undefined): any {\n if (!resp || resp === \"\") {\n return null;\n }\n\n try {\n return JSON.parse(resp);\n } catch {\n console && console.log(\"failed to parse JSON response\", resp);\n return null;\n }\n }\n\n static serialize(obj: Record, parentKey?: string): string {\n const queryStr: string[] = [];\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n continue;\n }\n const paramKey = parentKey ? `${parentKey}[${key}]` : key;\n const paramVal = obj[key];\n if (typeof paramVal === \"object\") {\n queryStr.push(this.serialize(paramVal, paramKey));\n } else {\n queryStr.push(\n encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal),\n );\n }\n }\n return queryStr.join(\"&\");\n }\n\n static appendParams(url: string, params: Record): string {\n if (Object.keys(params).length === 0) {\n return url;\n }\n\n const prefix = url.match(/\\?/) ? \"&\" : \"?\";\n return `${url}${prefix}${this.serialize(params)}`;\n }\n}\n", "import {\n SOCKET_STATES,\n TRANSPORTS,\n AUTH_TOKEN_PREFIX,\n type SocketState,\n} from \"./constants\";\n\nimport Ajax from \"./ajax\";\n\nfunction arrayBufferToBase64(buffer: ArrayBuffer): string {\n let binary = \"\";\n const bytes = new Uint8Array(buffer);\n const len = bytes.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n\ninterface LongPollResponse {\n status: number;\n token?: string;\n messages?: string[];\n}\n\ninterface MessageEvent {\n data: string;\n}\n\ninterface CloseEventInit {\n code?: number;\n reason?: string;\n wasClean?: boolean;\n}\n\nexport default class LongPoll {\n public endPoint: string | null;\n public token: string | null;\n public skipHeartbeat: boolean;\n public reqs: Set;\n public awaitingBatchAck: boolean;\n public currentBatch: string[] | null;\n public currentBatchTimer: number | null;\n public batchBuffer: string[];\n public onopen: (event: any) => void;\n public onerror: (error: any) => void;\n public onmessage: (event: MessageEvent) => void;\n public onclose: (event: CloseEvent | CloseEventInit) => void;\n public pollEndpoint: string;\n public readyState: SocketState;\n public timeout: number;\n public authToken?: string;\n\n constructor(endPoint: string, protocols?: string[]) {\n // we only support subprotocols for authToken\n // [\"phoenix\", \"base64url.bearer.phx.BASE64_ENCODED_TOKEN\"]\n if (\n protocols &&\n protocols.length === 2 &&\n protocols[1]!.startsWith(AUTH_TOKEN_PREFIX)\n ) {\n this.authToken = atob(protocols[1]!.slice(AUTH_TOKEN_PREFIX.length));\n }\n this.endPoint = null;\n this.token = null;\n this.skipHeartbeat = true;\n this.reqs = new Set();\n this.awaitingBatchAck = false;\n this.currentBatch = null;\n this.currentBatchTimer = null;\n this.batchBuffer = [];\n this.onopen = function () {}; // noop\n this.onerror = function () {}; // noop\n this.onmessage = function () {}; // noop\n this.onclose = function () {}; // noop\n this.pollEndpoint = this.normalizeEndpoint(endPoint);\n this.readyState = SOCKET_STATES.connecting;\n this.timeout = 20000; // will be set by Socket\n // we must wait for the caller to finish setting up our callbacks and timeout properties\n setTimeout(() => this.poll(), 0);\n }\n\n normalizeEndpoint(endPoint: string): string {\n return endPoint\n .replace(\"ws://\", \"http://\")\n .replace(\"wss://\", \"https://\")\n .replace(\n new RegExp(\"(.*)/\" + TRANSPORTS.websocket),\n \"$1/\" + TRANSPORTS.longpoll,\n );\n }\n\n endpointURL(): string {\n return Ajax.appendParams(this.pollEndpoint, { token: this.token });\n }\n\n closeAndRetry(code: number, reason: string, wasClean: boolean): void {\n this.close(code, reason, wasClean);\n this.readyState = SOCKET_STATES.connecting;\n }\n\n ontimeout(): void {\n this.onerror(\"timeout\");\n this.closeAndRetry(1005, \"timeout\", false);\n }\n\n isActive(): boolean {\n return (\n this.readyState === SOCKET_STATES.open ||\n this.readyState === SOCKET_STATES.connecting\n );\n }\n\n poll(): void {\n const headers: Record = { Accept: \"application/json\" };\n if (this.authToken) {\n headers[\"X-Phoenix-AuthToken\"] = this.authToken;\n }\n this.ajax(\n \"GET\",\n headers,\n null,\n () => this.ontimeout(),\n (resp: LongPollResponse | null) => {\n let status: number;\n if (resp) {\n const { status: respStatus, token } = resp;\n status = respStatus;\n this.token = token || null;\n } else {\n status = 0;\n }\n\n switch (status) {\n case 200:\n resp!.messages!.forEach((msg) => {\n // Tasks are what things like event handlers, setTimeout callbacks,\n // promise resolves and more are run within.\n // In modern browsers, there are two different kinds of tasks,\n // microtasks and macrotasks.\n // Microtasks are mainly used for Promises, while macrotasks are\n // used for everything else.\n // Microtasks always have priority over macrotasks. If the JS engine\n // is looking for a task to run, it will always try to empty the\n // microtask queue before attempting to run anything from the\n // macrotask queue.\n //\n // For the WebSocket transport, messages always arrive in their own\n // event. This means that if any promises are resolved from within,\n // their callbacks will always finish execution by the time the\n // next message event handler is run.\n //\n // In order to emulate this behaviour, we need to make sure each\n // onmessage handler is run within its own macrotask.\n setTimeout(() => this.onmessage({ data: msg }), 0);\n });\n this.poll();\n break;\n case 204:\n this.poll();\n break;\n case 410:\n this.readyState = SOCKET_STATES.open;\n this.onopen({});\n this.poll();\n break;\n case 403:\n this.onerror(403);\n this.close(1008, \"forbidden\", false);\n break;\n case 0:\n case 500:\n this.onerror(500);\n this.closeAndRetry(1011, \"internal server error\", false);\n break;\n default:\n throw new Error(`unhandled poll status ${status}`);\n }\n },\n );\n }\n\n // we collect all pushes within the current event loop by\n // setTimeout 0, which optimizes back-to-back procedural\n // pushes against an empty buffer\n\n send(body: string | ArrayBuffer): void {\n let bodyStr: string;\n if (typeof body !== \"string\") {\n bodyStr = arrayBufferToBase64(body);\n } else {\n bodyStr = body;\n }\n if (this.currentBatch) {\n this.currentBatch.push(bodyStr);\n } else if (this.awaitingBatchAck) {\n this.batchBuffer.push(bodyStr);\n } else {\n this.currentBatch = [bodyStr];\n this.currentBatchTimer = setTimeout(() => {\n this.batchSend(this.currentBatch!);\n this.currentBatch = null;\n }, 0) as any;\n }\n }\n\n batchSend(messages: string[]): void {\n this.awaitingBatchAck = true;\n this.ajax(\n \"POST\",\n { \"Content-Type\": \"application/x-ndjson\" },\n messages.join(\"\\n\"),\n () => this.onerror(\"timeout\"),\n (resp: any) => {\n this.awaitingBatchAck = false;\n if (!resp || resp.status !== 200) {\n this.onerror(resp && resp.status);\n this.closeAndRetry(1011, \"internal server error\", false);\n } else if (this.batchBuffer.length > 0) {\n this.batchSend(this.batchBuffer);\n this.batchBuffer = [];\n }\n },\n );\n }\n\n close(code?: number, reason?: string, wasClean?: boolean): void {\n for (const req of this.reqs) {\n req.abort();\n }\n this.readyState = SOCKET_STATES.closed;\n const opts = Object.assign(\n { code: 1000, reason: undefined, wasClean: true },\n { code, reason, wasClean },\n );\n this.batchBuffer = [];\n if (this.currentBatchTimer !== null) {\n clearTimeout(this.currentBatchTimer);\n this.currentBatchTimer = null;\n }\n if (typeof CloseEvent !== \"undefined\") {\n this.onclose(new CloseEvent(\"close\", opts));\n } else {\n this.onclose(opts);\n }\n }\n\n ajax(\n method: \"GET\" | \"POST\",\n headers: Record,\n body: string | null,\n onCallerTimeout: () => void,\n callback: (resp: any) => void,\n ): void {\n const ontimeout = () => {\n this.reqs.delete(req);\n onCallerTimeout();\n };\n const req = Ajax.request(\n method,\n this.endpointURL(),\n headers,\n body,\n this.timeout,\n ontimeout,\n (resp: any) => {\n this.reqs.delete(req);\n if (this.isActive()) {\n callback(resp);\n }\n },\n );\n this.reqs.add(req);\n }\n}\n", "import type Channel from \"./channel\";\n\nexport interface PresenceMeta {\n phx_ref: string;\n [key: string]: any;\n}\n\nexport interface PresenceState {\n metas: PresenceMeta[];\n [key: string]: any;\n}\n\nexport interface PresenceMap {\n [key: string]: PresenceState;\n}\n\nexport interface PresenceDiff {\n joins: PresenceMap;\n leaves: PresenceMap;\n}\n\nexport interface PresenceOptions {\n events?: {\n state: string;\n diff: string;\n };\n}\n\nexport type PresenceCallback = (\n key: string,\n current: PresenceState | undefined,\n newPres: PresenceState,\n) => void;\nexport type PresenceSyncCallback = () => void;\nexport type PresenceChooser = (\n key: string,\n presence: PresenceState,\n) => T;\n\n/**\n * Initializes the Presence\n * @param channel - The Channel\n * @param opts - The options, for example `{events: {state: \"state\", diff: \"diff\"}}`\n */\nexport default class Presence {\n public state: PresenceMap;\n public pendingDiffs: PresenceDiff[];\n public channel: Channel;\n public joinRef: string | null;\n public caller: {\n onJoin: PresenceCallback;\n onLeave: PresenceCallback;\n onSync: PresenceSyncCallback;\n };\n\n constructor(channel: Channel, opts: PresenceOptions = {}) {\n const events = opts.events || {\n state: \"presence_state\",\n diff: \"presence_diff\",\n };\n this.state = {};\n this.pendingDiffs = [];\n this.channel = channel;\n this.joinRef = null;\n this.caller = {\n onJoin: function () {},\n onLeave: function () {},\n onSync: function () {},\n };\n\n this.channel.on(events.state, (newState: PresenceMap) => {\n const { onJoin, onLeave, onSync } = this.caller;\n\n this.joinRef = this.channel.joinRef();\n this.state = Presence.syncState(this.state, newState, onJoin, onLeave);\n\n this.pendingDiffs.forEach((diff) => {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);\n });\n this.pendingDiffs = [];\n onSync();\n });\n\n this.channel.on(events.diff, (diff: PresenceDiff) => {\n const { onJoin, onLeave, onSync } = this.caller;\n\n if (this.inPendingSyncState()) {\n this.pendingDiffs.push(diff);\n } else {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);\n onSync();\n }\n });\n }\n\n /**\n * @internal\n * @private\n */\n onJoin(callback: PresenceCallback): void {\n this.caller.onJoin = callback;\n }\n\n /**\n * @internal\n * @private\n */\n onLeave(callback: PresenceCallback): void {\n this.caller.onLeave = callback;\n }\n\n /**\n * @internal\n * @private\n */\n onSync(callback: PresenceSyncCallback): void {\n this.caller.onSync = callback;\n }\n\n /**\n * @internal\n * @private\n */\n list(by?: PresenceChooser): T[] {\n return Presence.list(this.state, by);\n }\n\n /**\n * @internal\n * @private\n */\n inPendingSyncState(): boolean {\n return !this.joinRef || this.joinRef !== this.channel.joinRef();\n }\n\n // lower-level public static API\n\n /**\n * Used to sync the list of presences on the server\n * with the client's state. An optional `onJoin` and `onLeave` callback can\n * be provided to react to changes in the client's local presences across\n * disconnects and reconnects with the server.\n */\n static syncState(\n currentState: PresenceMap,\n newState: PresenceMap,\n onJoin?: PresenceCallback,\n onLeave?: PresenceCallback,\n ): PresenceMap {\n const state = this.clone(currentState);\n const joins: PresenceMap = {};\n const leaves: PresenceMap = {};\n\n this.map(state, (key, presence) => {\n if (!newState[key]) {\n leaves[key] = presence;\n }\n });\n this.map(newState, (key, newPresence) => {\n const currentPresence = state[key];\n if (currentPresence) {\n const newRefs = newPresence.metas.map((m) => m.phx_ref);\n const curRefs = currentPresence.metas.map((m) => m.phx_ref);\n const joinedMetas = newPresence.metas.filter(\n (m) => curRefs.indexOf(m.phx_ref) < 0,\n );\n const leftMetas = currentPresence.metas.filter(\n (m) => newRefs.indexOf(m.phx_ref) < 0,\n );\n if (joinedMetas.length > 0) {\n joins[key] = newPresence;\n joins[key]!.metas = joinedMetas;\n }\n if (leftMetas.length > 0) {\n leaves[key] = this.clone(currentPresence);\n leaves[key]!.metas = leftMetas;\n }\n } else {\n joins[key] = newPresence;\n }\n });\n return this.syncDiff(\n state,\n { joins: joins, leaves: leaves },\n onJoin,\n onLeave,\n );\n }\n\n /**\n * Used to sync a diff of presence join and leave\n * events from the server, as they happen. Like `syncState`, `syncDiff`\n * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n * joining or leaving from a device.\n */\n static syncDiff(\n state: PresenceMap,\n diff: PresenceDiff,\n onJoin?: PresenceCallback,\n onLeave?: PresenceCallback,\n ): PresenceMap {\n const { joins, leaves } = this.clone(diff);\n if (!onJoin) {\n onJoin = function () {};\n }\n if (!onLeave) {\n onLeave = function () {};\n }\n\n this.map(joins, (key, newPresence) => {\n const currentPresence = state[key];\n state[key] = this.clone(newPresence);\n if (currentPresence) {\n const joinedRefs = state[key]!.metas.map((m) => m.phx_ref);\n const curMetas = currentPresence.metas.filter(\n (m) => joinedRefs.indexOf(m.phx_ref) < 0,\n );\n state[key]!.metas.unshift(...curMetas);\n }\n onJoin!(key, currentPresence, newPresence);\n });\n this.map(leaves, (key, leftPresence) => {\n const currentPresence = state[key];\n if (!currentPresence) {\n return;\n }\n const refsToRemove = leftPresence.metas.map((m) => m.phx_ref);\n currentPresence.metas = currentPresence.metas.filter((p) => {\n return refsToRemove.indexOf(p.phx_ref) < 0;\n });\n onLeave!(key, currentPresence, leftPresence);\n if (currentPresence.metas.length === 0) {\n delete state[key];\n }\n });\n return state;\n }\n\n /**\n * Returns the array of presences, with selected metadata.\n */\n static list(\n presences: PresenceMap,\n chooser?: PresenceChooser,\n ): T[] {\n if (!chooser) {\n chooser = function (key, pres) {\n return pres as any;\n };\n }\n\n return this.map(presences, (key, presence) => {\n return chooser!(key, presence);\n });\n }\n\n // private\n\n private static map(\n obj: PresenceMap,\n func: (key: string, presence: PresenceState) => T,\n ): T[] {\n return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]!));\n }\n\n private static clone(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n }\n}\n", "/* The default serializer for encoding and decoding messages */\nimport { CHANNEL_EVENTS } from \"./constants\";\n\nexport interface Message {\n join_ref: string | null;\n ref: string | null;\n topic: string;\n event: string;\n payload: any;\n}\n\nexport interface BinaryMessage {\n join_ref: string;\n ref: string;\n event: string;\n topic: string;\n payload: ArrayBuffer;\n}\n\nexport interface ReplyPayload {\n status: string;\n response: ArrayBuffer;\n}\n\nconst Serializer = {\n HEADER_LENGTH: 1,\n META_LENGTH: 4,\n KINDS: { push: 0, reply: 1, broadcast: 2 } as const,\n\n encode(\n msg: Message | BinaryMessage,\n callback: (encoded: string | ArrayBuffer) => void,\n ): void {\n if (msg.payload.constructor === ArrayBuffer) {\n return callback(this.binaryEncode(msg as BinaryMessage));\n } else {\n const payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];\n return callback(JSON.stringify(payload));\n }\n },\n\n decode(\n rawPayload: string | ArrayBuffer,\n callback: (decoded: Message) => void,\n ): void {\n if (rawPayload.constructor === ArrayBuffer) {\n return callback(this.binaryDecode(rawPayload as ArrayBuffer));\n } else {\n const [join_ref, ref, topic, event, payload] = JSON.parse(\n rawPayload as string,\n );\n return callback({ join_ref, ref, topic, event, payload });\n }\n },\n\n // private\n\n binaryEncode(message: BinaryMessage): ArrayBuffer {\n const { join_ref, ref, event, topic, payload } = message;\n const metaLength =\n this.META_LENGTH +\n join_ref.length +\n ref.length +\n topic.length +\n event.length;\n const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);\n const view = new DataView(header);\n let offset = 0;\n\n view.setUint8(offset++, this.KINDS.push); // kind\n view.setUint8(offset++, join_ref.length);\n view.setUint8(offset++, ref.length);\n view.setUint8(offset++, topic.length);\n view.setUint8(offset++, event.length);\n Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n\n const combined = new Uint8Array(header.byteLength + payload.byteLength);\n combined.set(new Uint8Array(header), 0);\n combined.set(new Uint8Array(payload), header.byteLength);\n\n return combined.buffer;\n },\n\n binaryDecode(buffer: ArrayBuffer): Message {\n const view = new DataView(buffer);\n const kind = view.getUint8(0);\n const decoder = new TextDecoder();\n switch (kind) {\n case this.KINDS.push:\n return this.decodePush(buffer, view, decoder);\n case this.KINDS.reply:\n return this.decodeReply(buffer, view, decoder);\n case this.KINDS.broadcast:\n return this.decodeBroadcast(buffer, view, decoder);\n default:\n throw new Error(`Unknown message kind: ${kind}`);\n }\n },\n\n decodePush(\n buffer: ArrayBuffer,\n view: DataView,\n decoder: TextDecoder,\n ): Message {\n const joinRefSize = view.getUint8(1);\n const topicSize = view.getUint8(2);\n const eventSize = view.getUint8(3);\n let offset = this.HEADER_LENGTH + this.META_LENGTH - 1; // pushes have no ref\n const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n offset = offset + joinRefSize;\n const topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n offset = offset + topicSize;\n const event = decoder.decode(buffer.slice(offset, offset + eventSize));\n offset = offset + eventSize;\n const data = buffer.slice(offset, buffer.byteLength);\n return {\n join_ref: joinRef,\n ref: null,\n topic: topic,\n event: event,\n payload: data,\n };\n },\n\n decodeReply(\n buffer: ArrayBuffer,\n view: DataView,\n decoder: TextDecoder,\n ): Message {\n const joinRefSize = view.getUint8(1);\n const refSize = view.getUint8(2);\n const topicSize = view.getUint8(3);\n const eventSize = view.getUint8(4);\n let offset = this.HEADER_LENGTH + this.META_LENGTH;\n const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n offset = offset + joinRefSize;\n const ref = decoder.decode(buffer.slice(offset, offset + refSize));\n offset = offset + refSize;\n const topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n offset = offset + topicSize;\n const event = decoder.decode(buffer.slice(offset, offset + eventSize));\n offset = offset + eventSize;\n const data = buffer.slice(offset, buffer.byteLength);\n const payload: ReplyPayload = { status: event, response: data };\n return {\n join_ref: joinRef,\n ref: ref,\n topic: topic,\n event: CHANNEL_EVENTS.reply,\n payload: payload,\n };\n },\n\n decodeBroadcast(\n buffer: ArrayBuffer,\n view: DataView,\n decoder: TextDecoder,\n ): Message {\n const topicSize = view.getUint8(1);\n const eventSize = view.getUint8(2);\n let offset = this.HEADER_LENGTH + 2;\n const topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n offset = offset + topicSize;\n const event = decoder.decode(buffer.slice(offset, offset + eventSize));\n offset = offset + eventSize;\n const data = buffer.slice(offset, buffer.byteLength);\n\n return {\n join_ref: null,\n ref: null,\n topic: topic,\n event: event,\n payload: data,\n };\n },\n};\n\nexport default Serializer;\n", "import {\n global,\n phxWindow,\n CHANNEL_EVENTS,\n DEFAULT_TIMEOUT,\n DEFAULT_VSN,\n SOCKET_STATES,\n TRANSPORTS,\n WS_CLOSE_NORMAL,\n AUTH_TOKEN_PREFIX,\n} from \"./constants\";\n\nimport { closure } from \"./utils\";\n\nimport Ajax from \"./ajax\";\nimport Channel from \"./channel\";\nimport LongPoll from \"./longpoll\";\nimport Serializer from \"./serializer\";\nimport Timer from \"./timer\";\n\n// Type definitions for WebSocket-like transport\ninterface Transport {\n new (url: string, protocols?: string | string[]): TransportInstance;\n name?: string;\n}\n\ntype TransportClass = Transport | typeof LongPoll;\n\n// Generic timer type that works in both Node.js and browser environments\ntype TimerHandle = ReturnType;\n\ninterface TransportInstance {\n binaryType?: string;\n timeout?: number;\n readyState: number;\n bufferedAmount?: number;\n skipHeartbeat?: boolean;\n onopen: ((event: Event) => void) | null;\n onerror: ((event: Event) => void) | null;\n onmessage: ((event: MessageEvent) => void) | null;\n onclose: ((event: CloseEvent) => void) | null;\n send(data: string | ArrayBuffer): void;\n close(code?: number, reason?: string): void;\n}\n\n// Message structure interfaces\ninterface Message {\n topic: string;\n event: string;\n payload: any;\n ref: string | null;\n join_ref?: string | null;\n}\n\ninterface PushData {\n topic: string;\n event: string;\n payload: any;\n ref: string;\n join_ref?: string;\n}\n\n// Callback types\ntype StateChangeCallback = () => void;\ntype ErrorCallback = (\n error: any,\n transport?: Transport,\n establishedConnections?: number,\n) => void;\ntype MessageCallback = (message: Message) => void;\ntype CloseCallback = (event: CloseEvent) => void;\ntype LoggerFunction = (kind: string, msg: string, data?: any) => void;\ntype ReconnectFunction = (tries: number) => number;\ntype EncodeFunction = (\n payload: any,\n callback: (encoded: string | ArrayBuffer) => void,\n) => void;\ntype DecodeFunction = (\n payload: string | ArrayBuffer,\n callback: (decoded: Message) => void,\n) => void;\ntype ParamsFunction = () => Record;\ntype PingCallback = (rtt: number) => void;\n\n// Storage interface\ninterface Storage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n// Socket options interface\ninterface SocketOptions {\n transport?: Transport;\n longPollFallbackMs?: number;\n debug?: boolean;\n encode?: EncodeFunction;\n decode?: DecodeFunction;\n timeout?: number;\n heartbeatIntervalMs?: number;\n reconnectAfterMs?: ReconnectFunction;\n rejoinAfterMs?: ReconnectFunction;\n logger?: LoggerFunction;\n longpollerTimeout?: number;\n params?: Record | ParamsFunction;\n authToken?: string;\n binaryType?: string;\n vsn?: string;\n sessionStorage?: Storage;\n}\n\n// State change callbacks structure\ninterface StateChangeCallbacks {\n open: Array<[string, StateChangeCallback]>;\n close: Array<[string, CloseCallback]>;\n error: Array<[string, ErrorCallback]>;\n message: Array<[string, MessageCallback]>;\n}\n\n/** Initializes the Socket *\n *\n * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"ws://example.com/socket\"`,\n * `\"wss://example.com\"`\n * `\"/socket\"` (inherited host & protocol)\n * @param {Object} [opts] - Optional configuration\n * @param {Function} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.\n *\n * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined.\n * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`.\n *\n * @param {number} [opts.longPollFallbackMs] - The millisecond time to attempt the primary transport\n * before falling back to the LongPoll transport. Disabled by default.\n *\n * @param {boolean} [opts.debug] - When true, enables debug logging. Default false.\n *\n * @param {Function} [opts.encode] - The function to encode outgoing messages.\n *\n * Defaults to JSON encoder.\n *\n * @param {Function} [opts.decode] - The function to decode incoming messages.\n *\n * Defaults to JSON:\n *\n * ```javascript\n * (payload, callback) => callback(JSON.parse(payload))\n * ```\n *\n * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.\n *\n * Defaults `DEFAULT_TIMEOUT`\n * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message\n * @param {Function} [opts.reconnectAfterMs] - The optional function that returns the\n * socket reconnect interval, in milliseconds.\n *\n * Defaults to stepped backoff of:\n *\n * ```javascript\n * function(tries){\n * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n * }\n * ````\n *\n * @param {Function} [opts.rejoinAfterMs] - The optional function that returns the millisec\n * rejoin interval for individual channels.\n *\n * ```javascript\n * function(tries){\n * return [1000, 2000, 5000][tries - 1] || 10000\n * }\n * ````\n *\n * @param {Function} [opts.logger] - The optional function for specialized logging, ie:\n *\n * ```javascript\n * function(kind, msg, data) {\n * console.log(`${kind}: ${msg}`, data)\n * }\n * ```\n *\n * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.\n *\n * Defaults to 20s (double the server long poll timer).\n *\n * @param {(Object|function)} [opts.params] - The optional params to pass when connecting\n * @param {string} [opts.authToken] - the optional authentication token to be exposed on the server\n * under the `:auth_token` connect_info key.\n * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.\n *\n * Defaults to \"arraybuffer\"\n *\n * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect.\n *\n * Defaults to DEFAULT_VSN.\n *\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is\n * useful when Phoenix won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain channel in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] || null }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n */\nexport default class Socket {\n private stateChangeCallbacks: StateChangeCallbacks;\n private channels: Channel[];\n private sendBuffer: Array<() => void>;\n private ref: number;\n private transport: TransportClass;\n private primaryPassedHealthCheck: boolean;\n private longPollFallbackMs: number | null;\n private fallbackTimer: TimerHandle | null;\n private sessionStore: Storage | null;\n private establishedConnections: number;\n private defaultEncoder: EncodeFunction;\n private defaultDecoder: DecodeFunction;\n private closeWasClean: boolean;\n private disconnecting: boolean;\n private binaryType: string;\n private connectClock: number;\n private encode: EncodeFunction;\n private decode: DecodeFunction;\n private heartbeatIntervalMs: number;\n private reconnectAfterMs: ReconnectFunction;\n private logger: LoggerFunction | null;\n private longpollerTimeout: number;\n private params: ParamsFunction;\n private endPoint: string;\n private vsn: string;\n private heartbeatTimeoutTimer: TimerHandle | null;\n private heartbeatTimer: TimerHandle | null;\n private pendingHeartbeatRef: string | null;\n private reconnectTimer: Timer;\n private authToken?: string;\n private conn: TransportInstance | null;\n\n /**\n * @internal\n * @private\n */\n timeout: number;\n\n /**\n * @internal\n * @private\n */\n rejoinAfterMs: ReconnectFunction;\n\n constructor(endPoint: string, opts: SocketOptions = {}) {\n this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };\n this.channels = [];\n this.sendBuffer = [];\n this.ref = 0;\n this.timeout = opts.timeout || DEFAULT_TIMEOUT;\n this.transport = opts.transport || global.WebSocket || LongPoll;\n this.primaryPassedHealthCheck = false;\n this.longPollFallbackMs = opts.longPollFallbackMs ?? null;\n this.fallbackTimer = null;\n this.sessionStore =\n opts.sessionStorage || (global && global.sessionStorage);\n this.establishedConnections = 0;\n this.defaultEncoder = Serializer.encode.bind(Serializer);\n this.defaultDecoder = Serializer.decode.bind(Serializer);\n this.closeWasClean = false;\n this.disconnecting = false;\n this.binaryType = opts.binaryType || \"arraybuffer\";\n this.connectClock = 1;\n this.conn = null;\n\n if (this.transport !== LongPoll) {\n this.encode = opts.encode || this.defaultEncoder;\n this.decode = opts.decode || this.defaultDecoder;\n } else {\n this.encode = this.defaultEncoder;\n this.decode = this.defaultDecoder;\n }\n\n let awaitingConnectionOnPageShow: number | null = null;\n if (phxWindow && phxWindow.addEventListener) {\n phxWindow.addEventListener(\"pagehide\", (_e: Event) => {\n if (this.conn) {\n this.disconnect();\n awaitingConnectionOnPageShow = this.connectClock;\n }\n });\n phxWindow.addEventListener(\"pageshow\", (_e: Event) => {\n if (awaitingConnectionOnPageShow === this.connectClock) {\n awaitingConnectionOnPageShow = null;\n this.connect();\n }\n });\n }\n\n this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;\n this.rejoinAfterMs = (tries: number) => {\n if (opts.rejoinAfterMs) {\n return opts.rejoinAfterMs(tries);\n } else {\n return [1000, 2000, 5000][tries - 1] || 10000;\n }\n };\n this.reconnectAfterMs = (tries: number) => {\n if (opts.reconnectAfterMs) {\n return opts.reconnectAfterMs(tries);\n } else {\n return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000;\n }\n };\n this.logger = opts.logger || null;\n if (!this.logger && opts.debug) {\n this.logger = (kind: string, msg: string, data?: any) => {\n console.log(`${kind}: ${msg}`, data);\n };\n }\n this.longpollerTimeout = opts.longpollerTimeout || 20000;\n this.params = closure(opts.params || {}) as ParamsFunction;\n this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;\n this.vsn = opts.vsn || DEFAULT_VSN;\n this.heartbeatTimeoutTimer = null;\n this.heartbeatTimer = null;\n this.pendingHeartbeatRef = null;\n this.reconnectTimer = new Timer(() => {\n this.teardown(() => this.connect());\n }, this.reconnectAfterMs);\n this.authToken = opts.authToken;\n }\n\n /**\n * Returns the LongPoll transport reference\n */\n getLongPollTransport(): typeof LongPoll {\n return LongPoll;\n }\n\n /**\n * Disconnects and replaces the active transport\n *\n * @param {Function} newTransport - The new transport class to instantiate\n *\n */\n replaceTransport(newTransport: Transport): void {\n this.connectClock++;\n this.closeWasClean = true;\n clearTimeout(this.fallbackTimer!);\n this.reconnectTimer.reset();\n if (this.conn) {\n this.conn.close();\n this.conn = null;\n }\n this.transport = newTransport;\n }\n\n /**\n * Returns the socket protocol\n *\n * @returns {string}\n */\n protocol(): string {\n return location.protocol.match(/^https/) ? \"wss\" : \"ws\";\n }\n\n /**\n * The fully qualified socket url\n *\n * @returns {string}\n */\n endPointURL(): string {\n const uri = Ajax.appendParams(\n Ajax.appendParams(this.endPoint, this.params()),\n { vsn: this.vsn },\n );\n if (uri.charAt(0) !== \"/\") {\n return uri;\n }\n if (uri.charAt(1) === \"/\") {\n return `${this.protocol()}:${uri}`;\n }\n\n return `${this.protocol()}://${location.host}${uri}`;\n }\n\n /**\n * Disconnects the socket\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n *\n * @param {Function} callback - Optional callback which is called after socket is disconnected.\n * @param {integer} code - A status code for disconnection (Optional).\n * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n */\n disconnect(callback?: () => void, code?: number, reason?: string): void {\n this.connectClock++;\n this.disconnecting = true;\n this.closeWasClean = true;\n clearTimeout(this.fallbackTimer!);\n this.reconnectTimer.reset();\n this.teardown(\n () => {\n this.disconnecting = false;\n callback && callback();\n },\n code,\n reason,\n );\n }\n\n /**\n *\n * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n *\n * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n */\n connect(params?: Record): void {\n if (params) {\n console &&\n console.log(\n \"passing params to connect is deprecated. Instead pass :params to the Socket constructor\",\n );\n this.params = closure(params) as ParamsFunction;\n }\n if (this.conn && !this.disconnecting) {\n return;\n }\n if (this.longPollFallbackMs && this.transport !== LongPoll) {\n this.connectWithFallback(LongPoll, this.longPollFallbackMs);\n } else {\n this.transportConnect();\n }\n }\n\n /**\n * Logs the message. Override `this.logger` for specialized logging. noops by default\n * @param {string} kind\n * @param {string} msg\n * @param {Object} data\n */\n log(kind: string, msg: string, data?: any): void {\n this.logger && this.logger(kind, msg, data);\n }\n\n /**\n * Returns true if a logger has been set on this socket.\n */\n hasLogger(): boolean {\n return this.logger !== null;\n }\n\n /**\n * Registers callbacks for connection open events\n *\n * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n *\n * @param {Function} callback\n */\n onOpen(callback: StateChangeCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.open.push([ref, callback]);\n return ref;\n }\n\n /**\n * Registers callbacks for connection close events\n * @param {Function} callback\n */\n onClose(callback: CloseCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.close.push([ref, callback]);\n return ref;\n }\n\n /**\n * Registers callbacks for connection error events\n *\n * @example socket.onError(function(error){ alert(\"An error occurred\") })\n *\n * @param {Function} callback\n */\n onError(callback: ErrorCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.error.push([ref, callback]);\n return ref;\n }\n\n /**\n * Registers callbacks for connection message events\n * @param {Function} callback\n */\n onMessage(callback: MessageCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.message.push([ref, callback]);\n return ref;\n }\n\n /**\n * Pings the server and invokes the callback with the RTT in milliseconds\n * @param {Function} callback\n *\n * Returns true if the ping was pushed or false if unable to be pushed.\n */\n ping(callback: PingCallback): boolean {\n if (!this.isConnected()) {\n return false;\n }\n const ref = this.makeRef();\n const startTime = Date.now();\n this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: ref });\n const onMsgRef = this.onMessage((msg) => {\n if (msg.ref === ref) {\n this.off([onMsgRef]);\n callback(Date.now() - startTime);\n }\n });\n return true;\n }\n\n /**\n * @internal\n * @private\n */\n transportConnect(): void {\n this.connectClock++;\n this.closeWasClean = false;\n let protocols: string[] | undefined = undefined;\n // Sec-WebSocket-Protocol based token\n // (longpoll uses Authorization header instead)\n if (this.authToken) {\n protocols = [\n \"phoenix\",\n `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`,\n ];\n }\n this.conn = new this.transport(this.endPointURL(), protocols);\n this.conn.binaryType = this.binaryType;\n this.conn.timeout = this.longpollerTimeout;\n this.conn.onopen = () => this.onConnOpen();\n this.conn.onerror = (error) => this.onConnError(error);\n this.conn.onmessage = (event) => this.onConnMessage(event);\n this.conn.onclose = (event) => this.onConnClose(event);\n }\n\n private getSession(key: string): string | null {\n return this.sessionStore && this.sessionStore.getItem(key);\n }\n\n private storeSession(key: string, val: string): void {\n this.sessionStore && this.sessionStore.setItem(key, val);\n }\n\n private connectWithFallback(\n fallbackTransport: Transport,\n fallbackThreshold: number = 2500,\n ): void {\n clearTimeout(this.fallbackTimer!);\n let established = false;\n let primaryTransport = true;\n let openRef: string;\n const fallback = (reason: any) => {\n this.log(\n \"transport\",\n `falling back to ${fallbackTransport.name}...`,\n reason,\n );\n this.off([openRef, errorRef]);\n primaryTransport = false;\n this.replaceTransport(fallbackTransport);\n this.transportConnect();\n };\n if (this.getSession(`phx:fallback:${fallbackTransport.name}`)) {\n return fallback(\"memorized\");\n }\n\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n\n const errorRef = this.onError((reason) => {\n this.log(\"transport\", \"error\", reason);\n if (primaryTransport && !established) {\n clearTimeout(this.fallbackTimer!);\n fallback(reason);\n }\n });\n this.onOpen(() => {\n established = true;\n if (!primaryTransport) {\n // only memorize LP if we never connected to primary\n if (!this.primaryPassedHealthCheck) {\n this.storeSession(`phx:fallback:${fallbackTransport.name}`, \"true\");\n }\n return this.log(\n \"transport\",\n `established ${fallbackTransport.name} fallback`,\n );\n }\n // if we've established primary, give the fallback a new period to attempt ping\n clearTimeout(this.fallbackTimer!);\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n this.ping((rtt) => {\n this.log(\"transport\", \"connected to primary after\", rtt);\n this.primaryPassedHealthCheck = true;\n clearTimeout(this.fallbackTimer!);\n });\n });\n this.transportConnect();\n }\n\n private clearHeartbeats(): void {\n clearTimeout(this.heartbeatTimer!);\n clearTimeout(this.heartbeatTimeoutTimer!);\n }\n\n /**\n * @internal\n * @private\n */\n onConnOpen(): void {\n if (this.hasLogger())\n this.log(\n \"transport\",\n `${this.transport.name} connected to ${this.endPointURL()}`,\n );\n this.closeWasClean = false;\n this.disconnecting = false;\n this.establishedConnections++;\n this.flushSendBuffer();\n this.reconnectTimer.reset();\n this.resetHeartbeat();\n this.stateChangeCallbacks.open.forEach(([, callback]) => callback());\n }\n\n private heartbeatTimeout(): void {\n if (this.pendingHeartbeatRef) {\n this.pendingHeartbeatRef = null;\n if (this.hasLogger()) {\n this.log(\n \"transport\",\n \"heartbeat timeout. Attempting to re-establish connection\",\n );\n }\n this.triggerChanError();\n this.closeWasClean = false;\n this.teardown(\n () => this.reconnectTimer.scheduleTimeout(),\n WS_CLOSE_NORMAL,\n \"heartbeat timeout\",\n );\n }\n }\n\n private resetHeartbeat(): void {\n if (this.conn && this.conn.skipHeartbeat) {\n return;\n }\n this.pendingHeartbeatRef = null;\n this.clearHeartbeats();\n this.heartbeatTimer = setTimeout(\n () => this.sendHeartbeat(),\n this.heartbeatIntervalMs,\n );\n }\n\n private teardown(\n callback?: () => void,\n code?: number,\n reason?: string,\n ): void {\n if (!this.conn) {\n return callback && callback();\n }\n const connectClock = this.connectClock;\n\n this.waitForBufferDone(() => {\n if (connectClock !== this.connectClock) {\n return;\n }\n if (this.conn) {\n if (code) {\n this.conn.close(code, reason || \"\");\n } else {\n this.conn.close();\n }\n }\n\n this.waitForSocketClosed(() => {\n if (connectClock !== this.connectClock) {\n return;\n }\n if (this.conn) {\n this.conn.onopen = function () {}; // noop\n this.conn.onerror = function () {}; // noop\n this.conn.onmessage = function () {}; // noop\n this.conn.onclose = function () {}; // noop\n this.conn = null;\n }\n\n callback && callback();\n });\n });\n }\n\n private waitForBufferDone(callback: () => void, tries: number = 1): void {\n if (tries === 5 || !this.conn || !this.conn.bufferedAmount) {\n callback();\n return;\n }\n\n setTimeout(() => {\n this.waitForBufferDone(callback, tries + 1);\n }, 150 * tries);\n }\n\n private waitForSocketClosed(callback: () => void, tries: number = 1): void {\n if (\n tries === 5 ||\n !this.conn ||\n this.conn.readyState === SOCKET_STATES.closed\n ) {\n callback();\n return;\n }\n\n setTimeout(() => {\n this.waitForSocketClosed(callback, tries + 1);\n }, 150 * tries);\n }\n\n /**\n * @internal\n * @private\n */\n onConnClose(event: CloseEvent): void {\n const closeCode = event && event.code;\n if (this.hasLogger()) this.log(\"transport\", \"close\", event);\n this.triggerChanError();\n this.clearHeartbeats();\n if (!this.closeWasClean && closeCode !== 1000) {\n this.reconnectTimer.scheduleTimeout();\n }\n this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));\n }\n\n /**\n * @internal\n * @private\n */\n onConnError(error: Event): void {\n if (this.hasLogger()) this.log(\"transport\", \"error\", error);\n const transportBefore = this.transport;\n const establishedBefore = this.establishedConnections;\n this.stateChangeCallbacks.error.forEach(([, callback]) => {\n callback(error, transportBefore, establishedBefore);\n });\n if (transportBefore === this.transport || establishedBefore > 0) {\n this.triggerChanError();\n }\n }\n\n private triggerChanError(): void {\n this.channels.forEach((channel) => {\n if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {\n channel.trigger(CHANNEL_EVENTS.error, {});\n }\n });\n }\n\n /**\n * @returns {string}\n */\n connectionState(): string {\n switch (this.conn && this.conn.readyState) {\n case SOCKET_STATES.connecting:\n return \"connecting\";\n case SOCKET_STATES.open:\n return \"open\";\n case SOCKET_STATES.closing:\n return \"closing\";\n default:\n return \"closed\";\n }\n }\n\n /**\n * @returns {boolean}\n */\n isConnected(): boolean {\n return this.connectionState() === \"open\";\n }\n\n /**\n * @internal\n * @private\n *\n * @param {Channel}\n */\n remove(channel: Channel): void {\n this.off(channel.stateChangeRefs);\n this.channels = this.channels.filter((c) => c !== channel);\n }\n\n /**\n * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n *\n * @param {refs} - list of refs returned by calls to\n * `onOpen`, `onClose`, `onError,` and `onMessage`\n */\n off(refs: string[]): void {\n const filter = (callbacks: Array<[string, any]>) =>\n callbacks.filter(([ref]) => refs.indexOf(ref) === -1);\n\n this.stateChangeCallbacks = {\n open: filter(this.stateChangeCallbacks.open),\n close: filter(this.stateChangeCallbacks.close),\n error: filter(this.stateChangeCallbacks.error),\n message: filter(this.stateChangeCallbacks.message),\n };\n }\n\n /**\n * Initiates a new channel for the given topic\n *\n * @param {string} topic\n * @param {Object} chanParams - Parameters for the channel\n * @returns {Channel}\n */\n channel(topic: string, chanParams: Record = {}): Channel {\n const chan = new Channel(topic, chanParams, this);\n this.channels.push(chan);\n return chan;\n }\n\n /**\n * @param {Object} data\n */\n push(data: PushData): void {\n if (this.hasLogger()) {\n const { topic, event, payload, ref, join_ref } = data;\n this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload);\n }\n\n if (this.isConnected()) {\n this.encode(data, (result) => this.conn!.send(result));\n } else {\n this.sendBuffer.push(() =>\n this.encode(data, (result) => this.conn!.send(result)),\n );\n }\n }\n\n /**\n * Return the next message ref, accounting for overflows\n * @returns {string}\n */\n makeRef(): string {\n const newRef = this.ref + 1;\n if (newRef === this.ref) {\n this.ref = 0;\n } else {\n this.ref = newRef;\n }\n\n return this.ref.toString();\n }\n\n /**\n * @internal\n * @private\n */\n sendHeartbeat(): void {\n if (this.pendingHeartbeatRef && !this.isConnected()) {\n return;\n }\n this.pendingHeartbeatRef = this.makeRef();\n this.push({\n topic: \"phoenix\",\n event: \"heartbeat\",\n payload: {},\n ref: this.pendingHeartbeatRef,\n });\n this.heartbeatTimeoutTimer = setTimeout(\n () => this.heartbeatTimeout(),\n this.heartbeatIntervalMs,\n );\n }\n\n /**\n * @internal\n * @private\n */\n flushSendBuffer(): void {\n if (this.isConnected() && this.sendBuffer.length > 0) {\n this.sendBuffer.forEach((callback) => callback());\n this.sendBuffer = [];\n }\n }\n\n /**\n * @internal\n * @private\n */\n onConnMessage(rawMessage: MessageEvent): void {\n this.decode(rawMessage.data, (msg) => {\n const { topic, event, payload, ref, join_ref } = msg;\n if (ref && ref === this.pendingHeartbeatRef) {\n this.clearHeartbeats();\n this.pendingHeartbeatRef = null;\n this.heartbeatTimer = setTimeout(\n () => this.sendHeartbeat(),\n this.heartbeatIntervalMs,\n );\n }\n\n if (this.hasLogger())\n this.log(\n \"receive\",\n `${payload.status || \"\"} ${topic} ${event} ${(ref && \"(\" + ref + \")\") || \"\"}`,\n payload,\n );\n\n for (let i = 0; i < this.channels.length; i++) {\n const channel = this.channels[i];\n if (!channel.isMember(topic, event, payload, join_ref)) {\n continue;\n }\n channel.trigger(event, payload, ref, join_ref);\n }\n\n for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {\n const [, callback] = this.stateChangeCallbacks.message[i];\n callback(msg);\n }\n });\n }\n\n /**\n * @internal\n * @private\n */\n leaveOpenTopic(topic: string): void {\n const dupChannel = this.channels.find(\n (c) => c.topic === topic && (c.isJoined() || c.isJoining()),\n );\n if (dupChannel) {\n if (this.hasLogger())\n this.log(\"transport\", `leaving duplicate topic \"${topic}\"`);\n dupChannel.leave();\n }\n }\n}\n"], + "mappings": "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", + "names": [] } diff --git a/priv/static/phoenix.js b/priv/static/phoenix.js index cdca59e04c..8430e64958 100644 --- a/priv/static/phoenix.js +++ b/priv/static/phoenix.js @@ -17,7 +17,7 @@ var Phoenix = (() => { }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - // js/phoenix/index.js + // dist/phoenix/index.js var phoenix_exports = {}; __export(phoenix_exports, { Channel: () => Channel, @@ -27,24 +27,26 @@ var Phoenix = (() => { Socket: () => Socket }); - // js/phoenix/utils.js - var closure = (value) => { + // dist/phoenix/utils.js + function closure(value) { if (typeof value === "function") { return value; } else { - let closure2 = function() { - return value; - }; - return closure2; + return () => value; } - }; + } - // js/phoenix/constants.js + // dist/phoenix/constants.js var globalSelf = typeof self !== "undefined" ? self : null; var phxWindow = typeof window !== "undefined" ? window : null; var global = globalSelf || phxWindow || globalThis; var DEFAULT_VSN = "2.0.0"; - var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; + var SOCKET_STATES = { + connecting: 0, + open: 1, + closing: 2, + closed: 3 + }; var DEFAULT_TIMEOUT = 1e4; var WS_CLOSE_NORMAL = 1e3; var CHANNEL_STATES = { @@ -70,23 +72,22 @@ var Phoenix = (() => { }; var AUTH_TOKEN_PREFIX = "base64url.bearer.phx."; - // js/phoenix/push.js + // dist/phoenix/push.js var Push = class { constructor(channel, event, payload, timeout) { this.channel = channel; this.event = event; - this.payload = payload || function() { - return {}; - }; + this.payload = typeof payload === "function" ? payload : () => payload || {}; this.receivedResp = null; this.timeout = timeout; this.timeoutTimer = null; this.recHooks = []; this.sent = false; + this.ref = null; + this.refEvent = null; } /** - * - * @param {number} timeout + * Resend the push with a new timeout */ resend(timeout) { this.timeout = timeout; @@ -94,7 +95,7 @@ var Phoenix = (() => { this.send(); } /** - * + * Send the push */ send() { if (this.hasReceived("timeout")) { @@ -111,9 +112,7 @@ var Phoenix = (() => { }); } /** - * - * @param {*} status - * @param {*} callback + * Register a callback for a specific response status */ receive(status, callback) { if (this.hasReceived(status)) { @@ -151,8 +150,10 @@ var Phoenix = (() => { * @private */ cancelTimeout() { - clearTimeout(this.timeoutTimer); - this.timeoutTimer = null; + if (this.timeoutTimer !== null) { + clearTimeout(this.timeoutTimer); + this.timeoutTimer = null; + } } /** * @private @@ -187,7 +188,7 @@ var Phoenix = (() => { } }; - // js/phoenix/timer.js + // dist/phoenix/timer.js var Timer = class { constructor(callback, timerCalc) { this.callback = callback; @@ -197,13 +198,17 @@ var Phoenix = (() => { } reset() { this.tries = 0; - clearTimeout(this.timer); + if (this.timer !== null) { + clearTimeout(this.timer); + } } /** * Cancels any previous scheduleTimeout and schedules callback */ scheduleTimeout() { - clearTimeout(this.timer); + if (this.timer !== null) { + clearTimeout(this.timer); + } this.timer = setTimeout(() => { this.tries = this.tries + 1; this.callback(); @@ -211,7 +216,7 @@ var Phoenix = (() => { } }; - // js/phoenix/channel.js + // dist/phoenix/channel.js var Channel = class { constructor(topic, params, socket) { this.state = CHANNEL_STATES.closed; @@ -231,14 +236,12 @@ var Phoenix = (() => { } }, this.socket.rejoinAfterMs); this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset())); - this.stateChangeRefs.push( - this.socket.onOpen(() => { - this.rejoinTimer.reset(); - if (this.isErrored()) { - this.rejoin(); - } - }) - ); + this.stateChangeRefs.push(this.socket.onOpen(() => { + this.rejoinTimer.reset(); + if (this.isErrored()) { + this.rejoin(); + } + })); this.joinPush.receive("ok", () => { this.state = CHANNEL_STATES.joined; this.rejoinTimer.reset(); @@ -272,7 +275,7 @@ var Phoenix = (() => { this.joinPush.receive("timeout", () => { if (this.socket.hasLogger()) this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout); - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout); + const leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout); leavePush.send(); this.state = CHANNEL_STATES.errored; this.joinPush.reset(); @@ -286,8 +289,6 @@ var Phoenix = (() => { } /** * Join the channel - * @param {integer} timeout - * @returns {Push} */ join(timeout = this.timeout) { if (this.joinedOnce) { @@ -301,14 +302,12 @@ var Phoenix = (() => { } /** * Hook into channel close - * @param {Function} callback */ onClose(callback) { - this.on(CHANNEL_EVENTS.close, callback); + return this.on(CHANNEL_EVENTS.close, callback); } /** * Hook into channel errors - * @param {Function} callback */ onError(callback) { return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason)); @@ -325,13 +324,9 @@ var Phoenix = (() => { * channel.off("event", ref1) * // Since unsubscription, do_stuff won't fire, * // while do_other_stuff will keep firing on the "event" - * - * @param {string} event - * @param {Function} callback - * @returns {integer} ref */ on(event, callback) { - let ref = this.bindingRef++; + const ref = this.bindingRef++; this.bindings.push({ event, ref, callback }); return ref; } @@ -349,9 +344,6 @@ var Phoenix = (() => { * * // Unsubscribe all handlers from event * channel.off("event") - * - * @param {string} event - * @param {integer} ref */ off(event, ref) { this.bindings = this.bindings.filter((bind) => { @@ -359,6 +351,7 @@ var Phoenix = (() => { }); } /** + * @internal * @private */ canPush() { @@ -375,19 +368,12 @@ var Phoenix = (() => { * .receive("ok", payload => console.log("phoenix replied:", payload)) * .receive("error", err => console.log("phoenix errored", err)) * .receive("timeout", () => console.log("timed out pushing")) - * @param {string} event - * @param {Object} payload - * @param {number} [timeout] - * @returns {Push} */ - push(event, payload, timeout = this.timeout) { - payload = payload || {}; + push(event, payload = {}, timeout = this.timeout) { if (!this.joinedOnce) { throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`); } - let pushEvent = new Push(this, event, function() { - return payload; - }, timeout); + const pushEvent = new Push(this, event, () => payload, timeout); if (this.canPush()) { pushEvent.send(); } else { @@ -396,7 +382,8 @@ var Phoenix = (() => { } return pushEvent; } - /** Leaves the channel + /** + * Leaves the channel * * Unsubscribes from server events, and * instructs channel to terminate on server @@ -408,20 +395,17 @@ var Phoenix = (() => { * * @example * channel.leave().receive("ok", () => alert("left!") ) - * - * @param {integer} timeout - * @returns {Push} */ leave(timeout = this.timeout) { this.rejoinTimer.reset(); this.joinPush.cancelTimeout(); this.state = CHANNEL_STATES.leaving; - let onClose = () => { + const onClose = () => { if (this.socket.hasLogger()) this.socket.log("channel", `leave ${this.topic}`); this.trigger(CHANNEL_EVENTS.close, "leave"); }; - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout); + const leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout); leavePush.receive("ok", () => onClose()).receive("timeout", () => onClose()); leavePush.send(); if (!this.canPush()) { @@ -436,15 +420,12 @@ var Phoenix = (() => { * before dispatching to the channel callbacks. * * Must return the payload, modified or unmodified - * @param {string} event - * @param {Object} payload - * @param {integer} ref - * @returns {Object} */ - onMessage(_event, payload, _ref) { + onMessage(_event, payload, _ref, _joinRef) { return payload; } /** + * @internal * @private */ isMember(topic, event, payload, joinRef) { @@ -453,19 +434,26 @@ var Phoenix = (() => { } if (joinRef && joinRef !== this.joinRef()) { if (this.socket.hasLogger()) - this.socket.log("channel", "dropping outdated message", { topic, event, payload, joinRef }); + this.socket.log("channel", "dropping outdated message", { + topic, + event, + payload, + joinRef + }); return false; } else { return true; } } /** + * @internal * @private */ joinRef() { return this.joinPush.ref; } /** + * @internal * @private */ rejoin(timeout = this.timeout) { @@ -477,50 +465,57 @@ var Phoenix = (() => { this.joinPush.resend(timeout); } /** + * @internal * @private */ trigger(event, payload, ref, joinRef) { - let handledPayload = this.onMessage(event, payload, ref, joinRef); + const handledPayload = this.onMessage(event, payload, ref, joinRef); if (payload && !handledPayload) { throw new Error("channel onMessage callbacks must return the payload, modified or unmodified"); } - let eventBindings = this.bindings.filter((bind) => bind.event === event); + const eventBindings = this.bindings.filter((bind) => bind.event === event); for (let i = 0; i < eventBindings.length; i++) { - let bind = eventBindings[i]; + const bind = eventBindings[i]; bind.callback(handledPayload, ref, joinRef || this.joinRef()); } } /** + * @internal * @private */ replyEventName(ref) { return `chan_reply_${ref}`; } /** + * @internal * @private */ isClosed() { return this.state === CHANNEL_STATES.closed; } /** + * @internal * @private */ isErrored() { return this.state === CHANNEL_STATES.errored; } /** + * @internal * @private */ isJoined() { return this.state === CHANNEL_STATES.joined; } /** + * @internal * @private */ isJoining() { return this.state === CHANNEL_STATES.joining; } /** + * @internal * @private */ isLeaving() { @@ -528,31 +523,30 @@ var Phoenix = (() => { } }; - // js/phoenix/ajax.js + // dist/phoenix/ajax.js var Ajax = class { static request(method, endPoint, headers, body, timeout, ontimeout, callback) { if (global.XDomainRequest) { - let req = new global.XDomainRequest(); + const req = new global.XDomainRequest(); return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback); } else if (global.XMLHttpRequest) { - let req = new global.XMLHttpRequest(); + const req = new global.XMLHttpRequest(); return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback); - } else if (global.fetch && global.AbortController) { + } else if (typeof global.fetch === "function" && typeof global.AbortController === "function") { return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback); } else { throw new Error("No suitable XMLHttpRequest implementation found"); } } static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) { - let options = { + const options = { method, headers, body }; - let controller = null; + const controller = new AbortController(); if (timeout) { - controller = new AbortController(); - const _timeoutId = setTimeout(() => controller.abort(), timeout); + setTimeout(() => controller.abort(), timeout); options.signal = controller.signal; } global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => { @@ -568,7 +562,7 @@ var Phoenix = (() => { req.timeout = timeout; req.open(method, endPoint); req.onload = () => { - let response = this.parseJSON(req.responseText); + const response = this.parseJSON(req.responseText); callback && callback(response); }; if (ontimeout) { @@ -582,13 +576,13 @@ var Phoenix = (() => { static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) { req.open(method, endPoint, true); req.timeout = timeout; - for (let [key, value] of Object.entries(headers)) { + for (const [key, value] of Object.entries(headers)) { req.setRequestHeader(key, value); } req.onerror = () => callback && callback(null); req.onreadystatechange = () => { if (req.readyState === XHR_STATES.complete && callback) { - let response = this.parseJSON(req.responseText); + const response = this.parseJSON(req.responseText); callback(response); } }; @@ -604,19 +598,19 @@ var Phoenix = (() => { } try { return JSON.parse(resp); - } catch (e) { + } catch (_a) { console && console.log("failed to parse JSON response", resp); return null; } } static serialize(obj, parentKey) { - let queryStr = []; - for (var key in obj) { + const queryStr = []; + for (const key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) { continue; } - let paramKey = parentKey ? `${parentKey}[${key}]` : key; - let paramVal = obj[key]; + const paramKey = parentKey ? `${parentKey}[${key}]` : key; + const paramVal = obj[key]; if (typeof paramVal === "object") { queryStr.push(this.serialize(paramVal, paramKey)); } else { @@ -629,21 +623,21 @@ var Phoenix = (() => { if (Object.keys(params).length === 0) { return url; } - let prefix = url.match(/\?/) ? "&" : "?"; + const prefix = url.match(/\?/) ? "&" : "?"; return `${url}${prefix}${this.serialize(params)}`; } }; - // js/phoenix/longpoll.js - var arrayBufferToBase64 = (buffer) => { + // dist/phoenix/longpoll.js + function arrayBufferToBase64(buffer) { let binary = ""; - let bytes = new Uint8Array(buffer); - let len = bytes.byteLength; + const bytes = new Uint8Array(buffer); + const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); - }; + } var LongPoll = class { constructor(endPoint, protocols) { if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) { @@ -667,6 +661,7 @@ var Phoenix = (() => { }; this.pollEndpoint = this.normalizeEndpoint(endPoint); this.readyState = SOCKET_STATES.connecting; + this.timeout = 2e4; setTimeout(() => this.poll(), 0); } normalizeEndpoint(endPoint) { @@ -687,20 +682,22 @@ var Phoenix = (() => { return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting; } poll() { - const headers = { "Accept": "application/json" }; + const headers = { Accept: "application/json" }; if (this.authToken) { headers["X-Phoenix-AuthToken"] = this.authToken; } this.ajax("GET", headers, null, () => this.ontimeout(), (resp) => { + let status; if (resp) { - var { status, token, messages } = resp; - this.token = token; + const { status: respStatus, token } = resp; + status = respStatus; + this.token = token || null; } else { status = 0; } switch (status) { case 200: - messages.forEach((msg) => { + resp.messages.forEach((msg) => { setTimeout(() => this.onmessage({ data: msg }), 0); }); this.poll(); @@ -720,7 +717,7 @@ var Phoenix = (() => { case 0: case 500: this.onerror(500); - this.closeAndRetry(1011, "internal server error", 500); + this.closeAndRetry(1011, "internal server error", false); break; default: throw new Error(`unhandled poll status ${status}`); @@ -731,15 +728,18 @@ var Phoenix = (() => { // setTimeout 0, which optimizes back-to-back procedural // pushes against an empty buffer send(body) { + let bodyStr; if (typeof body !== "string") { - body = arrayBufferToBase64(body); + bodyStr = arrayBufferToBase64(body); + } else { + bodyStr = body; } if (this.currentBatch) { - this.currentBatch.push(body); + this.currentBatch.push(bodyStr); } else if (this.awaitingBatchAck) { - this.batchBuffer.push(body); + this.batchBuffer.push(bodyStr); } else { - this.currentBatch = [body]; + this.currentBatch = [bodyStr]; this.currentBatchTimer = setTimeout(() => { this.batchSend(this.currentBatch); this.currentBatch = null; @@ -760,14 +760,16 @@ var Phoenix = (() => { }); } close(code, reason, wasClean) { - for (let req of this.reqs) { + for (const req of this.reqs) { req.abort(); } this.readyState = SOCKET_STATES.closed; - let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean }); + const opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean }); this.batchBuffer = []; - clearTimeout(this.currentBatchTimer); - this.currentBatchTimer = null; + if (this.currentBatchTimer !== null) { + clearTimeout(this.currentBatchTimer); + this.currentBatchTimer = null; + } if (typeof CloseEvent !== "undefined") { this.onclose(new CloseEvent("close", opts)); } else { @@ -775,12 +777,11 @@ var Phoenix = (() => { } } ajax(method, headers, body, onCallerTimeout, callback) { - let req; - let ontimeout = () => { + const ontimeout = () => { this.reqs.delete(req); onCallerTimeout(); }; - req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => { + const req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => { this.reqs.delete(req); if (this.isActive()) { callback(resp); @@ -790,10 +791,13 @@ var Phoenix = (() => { } }; - // js/phoenix/presence.js - var Presence = class { + // dist/phoenix/presence.js + var Presence = class _Presence { constructor(channel, opts = {}) { - let events = opts.events || { state: "presence_state", diff: "presence_diff" }; + const events = opts.events || { + state: "presence_state", + diff: "presence_diff" + }; this.state = {}; this.pendingDiffs = []; this.channel = channel; @@ -807,37 +811,57 @@ var Phoenix = (() => { } }; this.channel.on(events.state, (newState) => { - let { onJoin, onLeave, onSync } = this.caller; + const { onJoin, onLeave, onSync } = this.caller; this.joinRef = this.channel.joinRef(); - this.state = Presence.syncState(this.state, newState, onJoin, onLeave); + this.state = _Presence.syncState(this.state, newState, onJoin, onLeave); this.pendingDiffs.forEach((diff) => { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave); }); this.pendingDiffs = []; onSync(); }); this.channel.on(events.diff, (diff) => { - let { onJoin, onLeave, onSync } = this.caller; + const { onJoin, onLeave, onSync } = this.caller; if (this.inPendingSyncState()) { this.pendingDiffs.push(diff); } else { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave); onSync(); } }); } + /** + * @internal + * @private + */ onJoin(callback) { this.caller.onJoin = callback; } + /** + * @internal + * @private + */ onLeave(callback) { this.caller.onLeave = callback; } + /** + * @internal + * @private + */ onSync(callback) { this.caller.onSync = callback; } + /** + * @internal + * @private + */ list(by) { - return Presence.list(this.state, by); + return _Presence.list(this.state, by); } + /** + * @internal + * @private + */ inPendingSyncState() { return !this.joinRef || this.joinRef !== this.channel.joinRef(); } @@ -847,25 +871,23 @@ var Phoenix = (() => { * with the client's state. An optional `onJoin` and `onLeave` callback can * be provided to react to changes in the client's local presences across * disconnects and reconnects with the server. - * - * @returns {Presence} */ static syncState(currentState, newState, onJoin, onLeave) { - let state = this.clone(currentState); - let joins = {}; - let leaves = {}; + const state = this.clone(currentState); + const joins = {}; + const leaves = {}; this.map(state, (key, presence) => { if (!newState[key]) { leaves[key] = presence; } }); this.map(newState, (key, newPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; if (currentPresence) { - let newRefs = newPresence.metas.map((m) => m.phx_ref); - let curRefs = currentPresence.metas.map((m) => m.phx_ref); - let joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0); - let leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0); + const newRefs = newPresence.metas.map((m) => m.phx_ref); + const curRefs = currentPresence.metas.map((m) => m.phx_ref); + const joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0); + const leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0); if (joinedMetas.length > 0) { joins[key] = newPresence; joins[key].metas = joinedMetas; @@ -881,16 +903,13 @@ var Phoenix = (() => { return this.syncDiff(state, { joins, leaves }, onJoin, onLeave); } /** - * * Used to sync a diff of presence join and leave * events from the server, as they happen. Like `syncState`, `syncDiff` * accepts optional `onJoin` and `onLeave` callbacks to react to a user * joining or leaving from a device. - * - * @returns {Presence} */ static syncDiff(state, diff, onJoin, onLeave) { - let { joins, leaves } = this.clone(diff); + const { joins, leaves } = this.clone(diff); if (!onJoin) { onJoin = function() { }; @@ -900,21 +919,21 @@ var Phoenix = (() => { }; } this.map(joins, (key, newPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; state[key] = this.clone(newPresence); if (currentPresence) { - let joinedRefs = state[key].metas.map((m) => m.phx_ref); - let curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0); + const joinedRefs = state[key].metas.map((m) => m.phx_ref); + const curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0); state[key].metas.unshift(...curMetas); } onJoin(key, currentPresence, newPresence); }); this.map(leaves, (key, leftPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; if (!currentPresence) { return; } - let refsToRemove = leftPresence.metas.map((m) => m.phx_ref); + const refsToRemove = leftPresence.metas.map((m) => m.phx_ref); currentPresence.metas = currentPresence.metas.filter((p) => { return refsToRemove.indexOf(p.phx_ref) < 0; }); @@ -927,11 +946,6 @@ var Phoenix = (() => { } /** * Returns the array of presences, with selected metadata. - * - * @param {Object} presences - * @param {Function} chooser - * - * @returns {Presence} */ static list(presences, chooser) { if (!chooser) { @@ -952,8 +966,8 @@ var Phoenix = (() => { } }; - // js/phoenix/serializer.js - var serializer_default = { + // dist/phoenix/serializer.js + var Serializer = { HEADER_LENGTH: 1, META_LENGTH: 4, KINDS: { push: 0, reply: 1, broadcast: 2 }, @@ -961,7 +975,7 @@ var Phoenix = (() => { if (msg.payload.constructor === ArrayBuffer) { return callback(this.binaryEncode(msg)); } else { - let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; + const payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; return callback(JSON.stringify(payload)); } }, @@ -969,16 +983,16 @@ var Phoenix = (() => { if (rawPayload.constructor === ArrayBuffer) { return callback(this.binaryDecode(rawPayload)); } else { - let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); + const [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); return callback({ join_ref, ref, topic, event, payload }); } }, // private binaryEncode(message) { - let { join_ref, ref, event, topic, payload } = message; - let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length; - let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); - let view = new DataView(header); + const { join_ref, ref, event, topic, payload } = message; + const metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length; + const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); + const view = new DataView(header); let offset = 0; view.setUint8(offset++, this.KINDS.push); view.setUint8(offset++, join_ref.length); @@ -989,15 +1003,15 @@ var Phoenix = (() => { Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0))); - var combined = new Uint8Array(header.byteLength + payload.byteLength); + const combined = new Uint8Array(header.byteLength + payload.byteLength); combined.set(new Uint8Array(header), 0); combined.set(new Uint8Array(payload), header.byteLength); return combined.buffer; }, binaryDecode(buffer) { - let view = new DataView(buffer); - let kind = view.getUint8(0); - let decoder = new TextDecoder(); + const view = new DataView(buffer); + const kind = view.getUint8(0); + const decoder = new TextDecoder(); switch (kind) { case this.KINDS.push: return this.decodePush(buffer, view, decoder); @@ -1005,56 +1019,78 @@ var Phoenix = (() => { return this.decodeReply(buffer, view, decoder); case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder); + default: + throw new Error(`Unknown message kind: ${kind}`); } }, decodePush(buffer, view, decoder) { - let joinRefSize = view.getUint8(1); - let topicSize = view.getUint8(2); - let eventSize = view.getUint8(3); + const joinRefSize = view.getUint8(1); + const topicSize = view.getUint8(2); + const eventSize = view.getUint8(3); let offset = this.HEADER_LENGTH + this.META_LENGTH - 1; - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - return { join_ref: joinRef, ref: null, topic, event, payload: data }; + const data = buffer.slice(offset, buffer.byteLength); + return { + join_ref: joinRef, + ref: null, + topic, + event, + payload: data + }; }, decodeReply(buffer, view, decoder) { - let joinRefSize = view.getUint8(1); - let refSize = view.getUint8(2); - let topicSize = view.getUint8(3); - let eventSize = view.getUint8(4); + const joinRefSize = view.getUint8(1); + const refSize = view.getUint8(2); + const topicSize = view.getUint8(3); + const eventSize = view.getUint8(4); let offset = this.HEADER_LENGTH + this.META_LENGTH; - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; - let ref = decoder.decode(buffer.slice(offset, offset + refSize)); + const ref = decoder.decode(buffer.slice(offset, offset + refSize)); offset = offset + refSize; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - let payload = { status: event, response: data }; - return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload }; + const data = buffer.slice(offset, buffer.byteLength); + const payload = { status: event, response: data }; + return { + join_ref: joinRef, + ref, + topic, + event: CHANNEL_EVENTS.reply, + payload + }; }, decodeBroadcast(buffer, view, decoder) { - let topicSize = view.getUint8(1); - let eventSize = view.getUint8(2); + const topicSize = view.getUint8(1); + const eventSize = view.getUint8(2); let offset = this.HEADER_LENGTH + 2; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - return { join_ref: null, ref: null, topic, event, payload: data }; + const data = buffer.slice(offset, buffer.byteLength); + return { + join_ref: null, + ref: null, + topic, + event, + payload: data + }; } }; + var serializer_default = Serializer; - // js/phoenix/socket.js + // dist/phoenix/socket.js var Socket = class { constructor(endPoint, opts = {}) { + var _a; this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] }; this.channels = []; this.sendBuffer = []; @@ -1062,7 +1098,7 @@ var Phoenix = (() => { this.timeout = opts.timeout || DEFAULT_TIMEOUT; this.transport = opts.transport || global.WebSocket || LongPoll; this.primaryPassedHealthCheck = false; - this.longPollFallbackMs = opts.longPollFallbackMs; + this.longPollFallbackMs = (_a = opts.longPollFallbackMs) !== null && _a !== void 0 ? _a : null; this.fallbackTimer = null; this.sessionStore = opts.sessionStorage || global && global.sessionStorage; this.establishedConnections = 0; @@ -1072,6 +1108,7 @@ var Phoenix = (() => { this.disconnecting = false; this.binaryType = opts.binaryType || "arraybuffer"; this.connectClock = 1; + this.conn = null; if (this.transport !== LongPoll) { this.encode = opts.encode || this.defaultEncoder; this.decode = opts.decode || this.defaultDecoder; @@ -1164,10 +1201,7 @@ var Phoenix = (() => { * @returns {string} */ endPointURL() { - let uri = Ajax.appendParams( - Ajax.appendParams(this.endPoint, this.params()), - { vsn: this.vsn } - ); + const uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params()), { vsn: this.vsn }); if (uri.charAt(0) !== "/") { return uri; } @@ -1240,7 +1274,7 @@ var Phoenix = (() => { * @param {Function} callback */ onOpen(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.open.push([ref, callback]); return ref; } @@ -1249,7 +1283,7 @@ var Phoenix = (() => { * @param {Function} callback */ onClose(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.close.push([ref, callback]); return ref; } @@ -1261,7 +1295,7 @@ var Phoenix = (() => { * @param {Function} callback */ onError(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.error.push([ref, callback]); return ref; } @@ -1270,7 +1304,7 @@ var Phoenix = (() => { * @param {Function} callback */ onMessage(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.message.push([ref, callback]); return ref; } @@ -1284,10 +1318,10 @@ var Phoenix = (() => { if (!this.isConnected()) { return false; } - let ref = this.makeRef(); - let startTime = Date.now(); + const ref = this.makeRef(); + const startTime = Date.now(); this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref }); - let onMsgRef = this.onMessage((msg) => { + const onMsgRef = this.onMessage((msg) => { if (msg.ref === ref) { this.off([onMsgRef]); callback(Date.now() - startTime); @@ -1296,6 +1330,7 @@ var Phoenix = (() => { return true; } /** + * @internal * @private */ transportConnect() { @@ -1303,7 +1338,10 @@ var Phoenix = (() => { this.closeWasClean = false; let protocols = void 0; if (this.authToken) { - protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`]; + protocols = [ + "phoenix", + `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}` + ]; } this.conn = new this.transport(this.endPointURL(), protocols); this.conn.binaryType = this.binaryType; @@ -1323,8 +1361,8 @@ var Phoenix = (() => { clearTimeout(this.fallbackTimer); let established = false; let primaryTransport = true; - let openRef, errorRef; - let fallback = (reason) => { + let openRef; + const fallback = (reason) => { this.log("transport", `falling back to ${fallbackTransport.name}...`, reason); this.off([openRef, errorRef]); primaryTransport = false; @@ -1335,7 +1373,7 @@ var Phoenix = (() => { return fallback("memorized"); } this.fallbackTimer = setTimeout(fallback, fallbackThreshold); - errorRef = this.onError((reason) => { + const errorRef = this.onError((reason) => { this.log("transport", "error", reason); if (primaryTransport && !established) { clearTimeout(this.fallbackTimer); @@ -1364,6 +1402,10 @@ var Phoenix = (() => { clearTimeout(this.heartbeatTimer); clearTimeout(this.heartbeatTimeoutTimer); } + /** + * @internal + * @private + */ onConnOpen() { if (this.hasLogger()) this.log("transport", `${this.transport.name} connected to ${this.endPointURL()}`); @@ -1375,9 +1417,6 @@ var Phoenix = (() => { this.resetHeartbeat(); this.stateChangeCallbacks.open.forEach(([, callback]) => callback()); } - /** - * @private - */ heartbeatTimeout() { if (this.pendingHeartbeatRef) { this.pendingHeartbeatRef = null; @@ -1401,7 +1440,7 @@ var Phoenix = (() => { if (!this.conn) { return callback && callback(); } - let connectClock = this.connectClock; + const connectClock = this.connectClock; this.waitForBufferDone(() => { if (connectClock !== this.connectClock) { return; @@ -1450,8 +1489,12 @@ var Phoenix = (() => { this.waitForSocketClosed(callback, tries + 1); }, 150 * tries); } + /** + * @internal + * @private + */ onConnClose(event) { - let closeCode = event && event.code; + const closeCode = event && event.code; if (this.hasLogger()) this.log("transport", "close", event); this.triggerChanError(); @@ -1462,13 +1505,14 @@ var Phoenix = (() => { this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event)); } /** + * @internal * @private */ onConnError(error) { if (this.hasLogger()) - this.log("transport", error); - let transportBefore = this.transport; - let establishedBefore = this.establishedConnections; + this.log("transport", "error", error); + const transportBefore = this.transport; + const establishedBefore = this.establishedConnections; this.stateChangeCallbacks.error.forEach(([, callback]) => { callback(error, transportBefore, establishedBefore); }); @@ -1476,13 +1520,10 @@ var Phoenix = (() => { this.triggerChanError(); } } - /** - * @private - */ triggerChanError() { this.channels.forEach((channel) => { if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) { - channel.trigger(CHANNEL_EVENTS.error); + channel.trigger(CHANNEL_EVENTS.error, {}); } }); } @@ -1508,6 +1549,7 @@ var Phoenix = (() => { return this.connectionState() === "open"; } /** + * @internal * @private * * @param {Channel} @@ -1523,11 +1565,13 @@ var Phoenix = (() => { * `onOpen`, `onClose`, `onError,` and `onMessage` */ off(refs) { - for (let key in this.stateChangeCallbacks) { - this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => { - return refs.indexOf(ref) === -1; - }); - } + const filter = (callbacks) => callbacks.filter(([ref]) => refs.indexOf(ref) === -1); + this.stateChangeCallbacks = { + open: filter(this.stateChangeCallbacks.open), + close: filter(this.stateChangeCallbacks.close), + error: filter(this.stateChangeCallbacks.error), + message: filter(this.stateChangeCallbacks.message) + }; } /** * Initiates a new channel for the given topic @@ -1537,7 +1581,7 @@ var Phoenix = (() => { * @returns {Channel} */ channel(topic, chanParams = {}) { - let chan = new Channel(topic, chanParams, this); + const chan = new Channel(topic, chanParams, this); this.channels.push(chan); return chan; } @@ -1546,7 +1590,7 @@ var Phoenix = (() => { */ push(data) { if (this.hasLogger()) { - let { topic, event, payload, ref, join_ref } = data; + const { topic, event, payload, ref, join_ref } = data; this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload); } if (this.isConnected()) { @@ -1560,7 +1604,7 @@ var Phoenix = (() => { * @returns {string} */ makeRef() { - let newRef = this.ref + 1; + const newRef = this.ref + 1; if (newRef === this.ref) { this.ref = 0; } else { @@ -1568,23 +1612,40 @@ var Phoenix = (() => { } return this.ref.toString(); } + /** + * @internal + * @private + */ sendHeartbeat() { if (this.pendingHeartbeatRef && !this.isConnected()) { return; } this.pendingHeartbeatRef = this.makeRef(); - this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef }); + this.push({ + topic: "phoenix", + event: "heartbeat", + payload: {}, + ref: this.pendingHeartbeatRef + }); this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs); } + /** + * @internal + * @private + */ flushSendBuffer() { if (this.isConnected() && this.sendBuffer.length > 0) { this.sendBuffer.forEach((callback) => callback()); this.sendBuffer = []; } } + /** + * @internal + * @private + */ onConnMessage(rawMessage) { this.decode(rawMessage.data, (msg) => { - let { topic, event, payload, ref, join_ref } = msg; + const { topic, event, payload, ref, join_ref } = msg; if (ref && ref === this.pendingHeartbeatRef) { this.clearHeartbeats(); this.pendingHeartbeatRef = null; @@ -1600,13 +1661,17 @@ var Phoenix = (() => { channel.trigger(event, payload, ref, join_ref); } for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) { - let [, callback] = this.stateChangeCallbacks.message[i]; + const [, callback] = this.stateChangeCallbacks.message[i]; callback(msg); } }); } + /** + * @internal + * @private + */ leaveOpenTopic(topic) { - let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining())); + const dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining())); if (dupChannel) { if (this.hasLogger()) this.log("transport", `leaving duplicate topic "${topic}"`); diff --git a/priv/static/phoenix.min.js b/priv/static/phoenix.min.js index 7350f639f7..2f5891fb63 100644 --- a/priv/static/phoenix.min.js +++ b/priv/static/phoenix.min.js @@ -1,2 +1,2 @@ -var Phoenix=(()=>{var _=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var D=(a,e)=>{for(var t in e)_(a,t,{get:e[t],enumerable:!0})},I=(a,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of M(e))!U.call(a,s)&&s!==t&&_(a,s,{get:()=>e[s],enumerable:!(i=$(e,s))||i.enumerable});return a};var F=a=>I(_({},"__esModule",{value:!0}),a);var W={};D(W,{Channel:()=>k,LongPoll:()=>g,Presence:()=>T,Serializer:()=>y,Socket:()=>x});var R=a=>typeof a=="function"?a:function(){return a};var J=typeof self!="undefined"?self:null,j=typeof window!="undefined"?window:null,d=J||j||globalThis,N="2.0.0",p={connecting:0,open:1,closing:2,closed:3},O=1e4,B=1e3,u={closed:"closed",errored:"errored",joined:"joined",joining:"joining",leaving:"leaving"},m={close:"phx_close",error:"phx_error",join:"phx_join",reply:"phx_reply",leave:"phx_leave"},A={longpoll:"longpoll",websocket:"websocket"},P={complete:4},w="base64url.bearer.phx.";var E=class{constructor(e,t,i,s){this.channel=e,this.event=t,this.payload=i||function(){return{}},this.receivedResp=null,this.timeout=s,this.timeoutTimer=null,this.recHooks=[],this.sent=!1}resend(e){this.timeout=e,this.reset(),this.send()}send(){this.hasReceived("timeout")||(this.startTimeout(),this.sent=!0,this.channel.socket.push({topic:this.channel.topic,event:this.event,payload:this.payload(),ref:this.ref,join_ref:this.channel.joinRef()}))}receive(e,t){return this.hasReceived(e)&&t(this.receivedResp.response),this.recHooks.push({status:e,callback:t}),this}reset(){this.cancelRefEvent(),this.ref=null,this.refEvent=null,this.receivedResp=null,this.sent=!1}matchReceive({status:e,response:t,_ref:i}){this.recHooks.filter(s=>s.status===e).forEach(s=>s.callback(t))}cancelRefEvent(){this.refEvent&&this.channel.off(this.refEvent)}cancelTimeout(){clearTimeout(this.timeoutTimer),this.timeoutTimer=null}startTimeout(){this.timeoutTimer&&this.cancelTimeout(),this.ref=this.channel.socket.makeRef(),this.refEvent=this.channel.replyEventName(this.ref),this.channel.on(this.refEvent,e=>{this.cancelRefEvent(),this.cancelTimeout(),this.receivedResp=e,this.matchReceive(e)}),this.timeoutTimer=setTimeout(()=>{this.trigger("timeout",{})},this.timeout)}hasReceived(e){return this.receivedResp&&this.receivedResp.status===e}trigger(e,t){this.channel.trigger(this.refEvent,{status:e,response:t})}};var v=class{constructor(e,t){this.callback=e,this.timerCalc=t,this.timer=null,this.tries=0}reset(){this.tries=0,clearTimeout(this.timer)}scheduleTimeout(){clearTimeout(this.timer),this.timer=setTimeout(()=>{this.tries=this.tries+1,this.callback()},this.timerCalc(this.tries+1))}};var k=class{constructor(e,t,i){this.state=u.closed,this.topic=e,this.params=R(t||{}),this.socket=i,this.bindings=[],this.bindingRef=0,this.timeout=this.socket.timeout,this.joinedOnce=!1,this.joinPush=new E(this,m.join,this.params,this.timeout),this.pushBuffer=[],this.stateChangeRefs=[],this.rejoinTimer=new v(()=>{this.socket.isConnected()&&this.rejoin()},this.socket.rejoinAfterMs),this.stateChangeRefs.push(this.socket.onError(()=>this.rejoinTimer.reset())),this.stateChangeRefs.push(this.socket.onOpen(()=>{this.rejoinTimer.reset(),this.isErrored()&&this.rejoin()})),this.joinPush.receive("ok",()=>{this.state=u.joined,this.rejoinTimer.reset(),this.pushBuffer.forEach(s=>s.send()),this.pushBuffer=[]}),this.joinPush.receive("error",()=>{this.state=u.errored,this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.onClose(()=>{this.rejoinTimer.reset(),this.socket.hasLogger()&&this.socket.log("channel",`close ${this.topic} ${this.joinRef()}`),this.state=u.closed,this.socket.remove(this)}),this.onError(s=>{this.socket.hasLogger()&&this.socket.log("channel",`error ${this.topic}`,s),this.isJoining()&&this.joinPush.reset(),this.state=u.errored,this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.joinPush.receive("timeout",()=>{this.socket.hasLogger()&&this.socket.log("channel",`timeout ${this.topic} (${this.joinRef()})`,this.joinPush.timeout),new E(this,m.leave,R({}),this.timeout).send(),this.state=u.errored,this.joinPush.reset(),this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.on(m.reply,(s,o)=>{this.trigger(this.replyEventName(o),s)})}join(e=this.timeout){if(this.joinedOnce)throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance");return this.timeout=e,this.joinedOnce=!0,this.rejoin(),this.joinPush}onClose(e){this.on(m.close,e)}onError(e){return this.on(m.error,t=>e(t))}on(e,t){let i=this.bindingRef++;return this.bindings.push({event:e,ref:i,callback:t}),i}off(e,t){this.bindings=this.bindings.filter(i=>!(i.event===e&&(typeof t=="undefined"||t===i.ref)))}canPush(){return this.socket.isConnected()&&this.isJoined()}push(e,t,i=this.timeout){if(t=t||{},!this.joinedOnce)throw new Error(`tried to push '${e}' to '${this.topic}' before joining. Use channel.join() before pushing events`);let s=new E(this,e,function(){return t},i);return this.canPush()?s.send():(s.startTimeout(),this.pushBuffer.push(s)),s}leave(e=this.timeout){this.rejoinTimer.reset(),this.joinPush.cancelTimeout(),this.state=u.leaving;let t=()=>{this.socket.hasLogger()&&this.socket.log("channel",`leave ${this.topic}`),this.trigger(m.close,"leave")},i=new E(this,m.leave,R({}),e);return i.receive("ok",()=>t()).receive("timeout",()=>t()),i.send(),this.canPush()||i.trigger("ok",{}),i}onMessage(e,t,i){return t}isMember(e,t,i,s){return this.topic!==e?!1:s&&s!==this.joinRef()?(this.socket.hasLogger()&&this.socket.log("channel","dropping outdated message",{topic:e,event:t,payload:i,joinRef:s}),!1):!0}joinRef(){return this.joinPush.ref}rejoin(e=this.timeout){this.isLeaving()||(this.socket.leaveOpenTopic(this.topic),this.state=u.joining,this.joinPush.resend(e))}trigger(e,t,i,s){let o=this.onMessage(e,t,i,s);if(t&&!o)throw new Error("channel onMessage callbacks must return the payload, modified or unmodified");let r=this.bindings.filter(n=>n.event===e);for(let n=0;nl.abort(),o);h.signal=l.signal}return d.fetch(t,h).then(c=>c.text()).then(c=>this.parseJSON(c)).then(c=>n&&n(c)).catch(c=>{c.name==="AbortError"&&r?r():n&&n(null)}),l}static xdomainRequest(e,t,i,s,o,r,n){return e.timeout=o,e.open(t,i),e.onload=()=>{let h=this.parseJSON(e.responseText);n&&n(h)},r&&(e.ontimeout=r),e.onprogress=()=>{},e.send(s),e}static xhrRequest(e,t,i,s,o,r,n,h){e.open(t,i,!0),e.timeout=r;for(let[l,c]of Object.entries(s))e.setRequestHeader(l,c);return e.onerror=()=>h&&h(null),e.onreadystatechange=()=>{if(e.readyState===P.complete&&h){let l=this.parseJSON(e.responseText);h(l)}},n&&(e.ontimeout=n),e.send(o),e}static parseJSON(e){if(!e||e==="")return null;try{return JSON.parse(e)}catch(t){return console&&console.log("failed to parse JSON response",e),null}}static serialize(e,t){let i=[];for(var s in e){if(!Object.prototype.hasOwnProperty.call(e,s))continue;let o=t?`${t}[${s}]`:s,r=e[s];typeof r=="object"?i.push(this.serialize(r,o)):i.push(encodeURIComponent(o)+"="+encodeURIComponent(r))}return i.join("&")}static appendParams(e,t){if(Object.keys(t).length===0)return e;let i=e.match(/\?/)?"&":"?";return`${e}${i}${this.serialize(t)}`}};var z=a=>{let e="",t=new Uint8Array(a),i=t.byteLength;for(let s=0;sthis.poll(),0)}normalizeEndpoint(e){return e.replace("ws://","http://").replace("wss://","https://").replace(new RegExp("(.*)/"+A.websocket),"$1/"+A.longpoll)}endpointURL(){return C.appendParams(this.pollEndpoint,{token:this.token})}closeAndRetry(e,t,i){this.close(e,t,i),this.readyState=p.connecting}ontimeout(){this.onerror("timeout"),this.closeAndRetry(1005,"timeout",!1)}isActive(){return this.readyState===p.open||this.readyState===p.connecting}poll(){let e={Accept:"application/json"};this.authToken&&(e["X-Phoenix-AuthToken"]=this.authToken),this.ajax("GET",e,null,()=>this.ontimeout(),t=>{if(t){var{status:i,token:s,messages:o}=t;this.token=s}else i=0;switch(i){case 200:o.forEach(r=>{setTimeout(()=>this.onmessage({data:r}),0)}),this.poll();break;case 204:this.poll();break;case 410:this.readyState=p.open,this.onopen({}),this.poll();break;case 403:this.onerror(403),this.close(1008,"forbidden",!1);break;case 0:case 500:this.onerror(500),this.closeAndRetry(1011,"internal server error",500);break;default:throw new Error(`unhandled poll status ${i}`)}})}send(e){typeof e!="string"&&(e=z(e)),this.currentBatch?this.currentBatch.push(e):this.awaitingBatchAck?this.batchBuffer.push(e):(this.currentBatch=[e],this.currentBatchTimer=setTimeout(()=>{this.batchSend(this.currentBatch),this.currentBatch=null},0))}batchSend(e){this.awaitingBatchAck=!0,this.ajax("POST",{"Content-Type":"application/x-ndjson"},e.join(` -`),()=>this.onerror("timeout"),t=>{this.awaitingBatchAck=!1,!t||t.status!==200?(this.onerror(t&&t.status),this.closeAndRetry(1011,"internal server error",!1)):this.batchBuffer.length>0&&(this.batchSend(this.batchBuffer),this.batchBuffer=[])})}close(e,t,i){for(let o of this.reqs)o.abort();this.readyState=p.closed;let s=Object.assign({code:1e3,reason:void 0,wasClean:!0},{code:e,reason:t,wasClean:i});this.batchBuffer=[],clearTimeout(this.currentBatchTimer),this.currentBatchTimer=null,typeof CloseEvent!="undefined"?this.onclose(new CloseEvent("close",s)):this.onclose(s)}ajax(e,t,i,s,o){let r,n=()=>{this.reqs.delete(r),s()};r=C.request(e,this.endpointURL(),t,i,this.timeout,n,h=>{this.reqs.delete(r),this.isActive()&&o(h)}),this.reqs.add(r)}};var T=class{constructor(e,t={}){let i=t.events||{state:"presence_state",diff:"presence_diff"};this.state={},this.pendingDiffs=[],this.channel=e,this.joinRef=null,this.caller={onJoin:function(){},onLeave:function(){},onSync:function(){}},this.channel.on(i.state,s=>{let{onJoin:o,onLeave:r,onSync:n}=this.caller;this.joinRef=this.channel.joinRef(),this.state=T.syncState(this.state,s,o,r),this.pendingDiffs.forEach(h=>{this.state=T.syncDiff(this.state,h,o,r)}),this.pendingDiffs=[],n()}),this.channel.on(i.diff,s=>{let{onJoin:o,onLeave:r,onSync:n}=this.caller;this.inPendingSyncState()?this.pendingDiffs.push(s):(this.state=T.syncDiff(this.state,s,o,r),n())})}onJoin(e){this.caller.onJoin=e}onLeave(e){this.caller.onLeave=e}onSync(e){this.caller.onSync=e}list(e){return T.list(this.state,e)}inPendingSyncState(){return!this.joinRef||this.joinRef!==this.channel.joinRef()}static syncState(e,t,i,s){let o=this.clone(e),r={},n={};return this.map(o,(h,l)=>{t[h]||(n[h]=l)}),this.map(t,(h,l)=>{let c=o[h];if(c){let f=l.metas.map(S=>S.phx_ref),b=c.metas.map(S=>S.phx_ref),L=l.metas.filter(S=>b.indexOf(S.phx_ref)<0),H=c.metas.filter(S=>f.indexOf(S.phx_ref)<0);L.length>0&&(r[h]=l,r[h].metas=L),H.length>0&&(n[h]=this.clone(c),n[h].metas=H)}else r[h]=l}),this.syncDiff(o,{joins:r,leaves:n},i,s)}static syncDiff(e,t,i,s){let{joins:o,leaves:r}=this.clone(t);return i||(i=function(){}),s||(s=function(){}),this.map(o,(n,h)=>{let l=e[n];if(e[n]=this.clone(h),l){let c=e[n].metas.map(b=>b.phx_ref),f=l.metas.filter(b=>c.indexOf(b.phx_ref)<0);e[n].metas.unshift(...f)}i(n,l,h)}),this.map(r,(n,h)=>{let l=e[n];if(!l)return;let c=h.metas.map(f=>f.phx_ref);l.metas=l.metas.filter(f=>c.indexOf(f.phx_ref)<0),s(n,l,h),l.metas.length===0&&delete e[n]}),e}static list(e,t){return t||(t=function(i,s){return s}),this.map(e,(i,s)=>t(i,s))}static map(e,t){return Object.getOwnPropertyNames(e).map(i=>t(i,e[i]))}static clone(e){return JSON.parse(JSON.stringify(e))}};var y={HEADER_LENGTH:1,META_LENGTH:4,KINDS:{push:0,reply:1,broadcast:2},encode(a,e){if(a.payload.constructor===ArrayBuffer)return e(this.binaryEncode(a));{let t=[a.join_ref,a.ref,a.topic,a.event,a.payload];return e(JSON.stringify(t))}},decode(a,e){if(a.constructor===ArrayBuffer)return e(this.binaryDecode(a));{let[t,i,s,o,r]=JSON.parse(a);return e({join_ref:t,ref:i,topic:s,event:o,payload:r})}},binaryEncode(a){let{join_ref:e,ref:t,event:i,topic:s,payload:o}=a,r=this.META_LENGTH+e.length+t.length+s.length+i.length,n=new ArrayBuffer(this.HEADER_LENGTH+r),h=new DataView(n),l=0;h.setUint8(l++,this.KINDS.push),h.setUint8(l++,e.length),h.setUint8(l++,t.length),h.setUint8(l++,s.length),h.setUint8(l++,i.length),Array.from(e,f=>h.setUint8(l++,f.charCodeAt(0))),Array.from(t,f=>h.setUint8(l++,f.charCodeAt(0))),Array.from(s,f=>h.setUint8(l++,f.charCodeAt(0))),Array.from(i,f=>h.setUint8(l++,f.charCodeAt(0)));var c=new Uint8Array(n.byteLength+o.byteLength);return c.set(new Uint8Array(n),0),c.set(new Uint8Array(o),n.byteLength),c.buffer},binaryDecode(a){let e=new DataView(a),t=e.getUint8(0),i=new TextDecoder;switch(t){case this.KINDS.push:return this.decodePush(a,e,i);case this.KINDS.reply:return this.decodeReply(a,e,i);case this.KINDS.broadcast:return this.decodeBroadcast(a,e,i)}},decodePush(a,e,t){let i=e.getUint8(1),s=e.getUint8(2),o=e.getUint8(3),r=this.HEADER_LENGTH+this.META_LENGTH-1,n=t.decode(a.slice(r,r+i));r=r+i;let h=t.decode(a.slice(r,r+s));r=r+s;let l=t.decode(a.slice(r,r+o));r=r+o;let c=a.slice(r,a.byteLength);return{join_ref:n,ref:null,topic:h,event:l,payload:c}},decodeReply(a,e,t){let i=e.getUint8(1),s=e.getUint8(2),o=e.getUint8(3),r=e.getUint8(4),n=this.HEADER_LENGTH+this.META_LENGTH,h=t.decode(a.slice(n,n+i));n=n+i;let l=t.decode(a.slice(n,n+s));n=n+s;let c=t.decode(a.slice(n,n+o));n=n+o;let f=t.decode(a.slice(n,n+r));n=n+r;let b=a.slice(n,a.byteLength),L={status:f,response:b};return{join_ref:h,ref:l,topic:c,event:m.reply,payload:L}},decodeBroadcast(a,e,t){let i=e.getUint8(1),s=e.getUint8(2),o=this.HEADER_LENGTH+2,r=t.decode(a.slice(o,o+i));o=o+i;let n=t.decode(a.slice(o,o+s));o=o+s;let h=a.slice(o,a.byteLength);return{join_ref:null,ref:null,topic:r,event:n,payload:h}}};var x=class{constructor(e,t={}){this.stateChangeCallbacks={open:[],close:[],error:[],message:[]},this.channels=[],this.sendBuffer=[],this.ref=0,this.timeout=t.timeout||O,this.transport=t.transport||d.WebSocket||g,this.primaryPassedHealthCheck=!1,this.longPollFallbackMs=t.longPollFallbackMs,this.fallbackTimer=null,this.sessionStore=t.sessionStorage||d&&d.sessionStorage,this.establishedConnections=0,this.defaultEncoder=y.encode.bind(y),this.defaultDecoder=y.decode.bind(y),this.closeWasClean=!1,this.disconnecting=!1,this.binaryType=t.binaryType||"arraybuffer",this.connectClock=1,this.transport!==g?(this.encode=t.encode||this.defaultEncoder,this.decode=t.decode||this.defaultDecoder):(this.encode=this.defaultEncoder,this.decode=this.defaultDecoder);let i=null;j&&j.addEventListener&&(j.addEventListener("pagehide",s=>{this.conn&&(this.disconnect(),i=this.connectClock)}),j.addEventListener("pageshow",s=>{i===this.connectClock&&(i=null,this.connect())})),this.heartbeatIntervalMs=t.heartbeatIntervalMs||3e4,this.rejoinAfterMs=s=>t.rejoinAfterMs?t.rejoinAfterMs(s):[1e3,2e3,5e3][s-1]||1e4,this.reconnectAfterMs=s=>t.reconnectAfterMs?t.reconnectAfterMs(s):[10,50,100,150,200,250,500,1e3,2e3][s-1]||5e3,this.logger=t.logger||null,!this.logger&&t.debug&&(this.logger=(s,o,r)=>{console.log(`${s}: ${o}`,r)}),this.longpollerTimeout=t.longpollerTimeout||2e4,this.params=R(t.params||{}),this.endPoint=`${e}/${A.websocket}`,this.vsn=t.vsn||N,this.heartbeatTimeoutTimer=null,this.heartbeatTimer=null,this.pendingHeartbeatRef=null,this.reconnectTimer=new v(()=>{this.teardown(()=>this.connect())},this.reconnectAfterMs),this.authToken=t.authToken}getLongPollTransport(){return g}replaceTransport(e){this.connectClock++,this.closeWasClean=!0,clearTimeout(this.fallbackTimer),this.reconnectTimer.reset(),this.conn&&(this.conn.close(),this.conn=null),this.transport=e}protocol(){return location.protocol.match(/^https/)?"wss":"ws"}endPointURL(){let e=C.appendParams(C.appendParams(this.endPoint,this.params()),{vsn:this.vsn});return e.charAt(0)!=="/"?e:e.charAt(1)==="/"?`${this.protocol()}:${e}`:`${this.protocol()}://${location.host}${e}`}disconnect(e,t,i){this.connectClock++,this.disconnecting=!0,this.closeWasClean=!0,clearTimeout(this.fallbackTimer),this.reconnectTimer.reset(),this.teardown(()=>{this.disconnecting=!1,e&&e()},t,i)}connect(e){e&&(console&&console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor"),this.params=R(e)),!(this.conn&&!this.disconnecting)&&(this.longPollFallbackMs&&this.transport!==g?this.connectWithFallback(g,this.longPollFallbackMs):this.transportConnect())}log(e,t,i){this.logger&&this.logger(e,t,i)}hasLogger(){return this.logger!==null}onOpen(e){let t=this.makeRef();return this.stateChangeCallbacks.open.push([t,e]),t}onClose(e){let t=this.makeRef();return this.stateChangeCallbacks.close.push([t,e]),t}onError(e){let t=this.makeRef();return this.stateChangeCallbacks.error.push([t,e]),t}onMessage(e){let t=this.makeRef();return this.stateChangeCallbacks.message.push([t,e]),t}ping(e){if(!this.isConnected())return!1;let t=this.makeRef(),i=Date.now();this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:t});let s=this.onMessage(o=>{o.ref===t&&(this.off([s]),e(Date.now()-i))});return!0}transportConnect(){this.connectClock++,this.closeWasClean=!1;let e;this.authToken&&(e=["phoenix",`${w}${btoa(this.authToken).replace(/=/g,"")}`]),this.conn=new this.transport(this.endPointURL(),e),this.conn.binaryType=this.binaryType,this.conn.timeout=this.longpollerTimeout,this.conn.onopen=()=>this.onConnOpen(),this.conn.onerror=t=>this.onConnError(t),this.conn.onmessage=t=>this.onConnMessage(t),this.conn.onclose=t=>this.onConnClose(t)}getSession(e){return this.sessionStore&&this.sessionStore.getItem(e)}storeSession(e,t){this.sessionStore&&this.sessionStore.setItem(e,t)}connectWithFallback(e,t=2500){clearTimeout(this.fallbackTimer);let i=!1,s=!0,o,r,n=h=>{this.log("transport",`falling back to ${e.name}...`,h),this.off([o,r]),s=!1,this.replaceTransport(e),this.transportConnect()};if(this.getSession(`phx:fallback:${e.name}`))return n("memorized");this.fallbackTimer=setTimeout(n,t),r=this.onError(h=>{this.log("transport","error",h),s&&!i&&(clearTimeout(this.fallbackTimer),n(h))}),this.onOpen(()=>{if(i=!0,!s)return this.primaryPassedHealthCheck||this.storeSession(`phx:fallback:${e.name}`,"true"),this.log("transport",`established ${e.name} fallback`);clearTimeout(this.fallbackTimer),this.fallbackTimer=setTimeout(n,t),this.ping(h=>{this.log("transport","connected to primary after",h),this.primaryPassedHealthCheck=!0,clearTimeout(this.fallbackTimer)})}),this.transportConnect()}clearHeartbeats(){clearTimeout(this.heartbeatTimer),clearTimeout(this.heartbeatTimeoutTimer)}onConnOpen(){this.hasLogger()&&this.log("transport",`${this.transport.name} connected to ${this.endPointURL()}`),this.closeWasClean=!1,this.disconnecting=!1,this.establishedConnections++,this.flushSendBuffer(),this.reconnectTimer.reset(),this.resetHeartbeat(),this.stateChangeCallbacks.open.forEach(([,e])=>e())}heartbeatTimeout(){this.pendingHeartbeatRef&&(this.pendingHeartbeatRef=null,this.hasLogger()&&this.log("transport","heartbeat timeout. Attempting to re-establish connection"),this.triggerChanError(),this.closeWasClean=!1,this.teardown(()=>this.reconnectTimer.scheduleTimeout(),B,"heartbeat timeout"))}resetHeartbeat(){this.conn&&this.conn.skipHeartbeat||(this.pendingHeartbeatRef=null,this.clearHeartbeats(),this.heartbeatTimer=setTimeout(()=>this.sendHeartbeat(),this.heartbeatIntervalMs))}teardown(e,t,i){if(!this.conn)return e&&e();let s=this.connectClock;this.waitForBufferDone(()=>{s===this.connectClock&&(this.conn&&(t?this.conn.close(t,i||""):this.conn.close()),this.waitForSocketClosed(()=>{s===this.connectClock&&(this.conn&&(this.conn.onopen=function(){},this.conn.onerror=function(){},this.conn.onmessage=function(){},this.conn.onclose=function(){},this.conn=null),e&&e())}))})}waitForBufferDone(e,t=1){if(t===5||!this.conn||!this.conn.bufferedAmount){e();return}setTimeout(()=>{this.waitForBufferDone(e,t+1)},150*t)}waitForSocketClosed(e,t=1){if(t===5||!this.conn||this.conn.readyState===p.closed){e();return}setTimeout(()=>{this.waitForSocketClosed(e,t+1)},150*t)}onConnClose(e){let t=e&&e.code;this.hasLogger()&&this.log("transport","close",e),this.triggerChanError(),this.clearHeartbeats(),!this.closeWasClean&&t!==1e3&&this.reconnectTimer.scheduleTimeout(),this.stateChangeCallbacks.close.forEach(([,i])=>i(e))}onConnError(e){this.hasLogger()&&this.log("transport",e);let t=this.transport,i=this.establishedConnections;this.stateChangeCallbacks.error.forEach(([,s])=>{s(e,t,i)}),(t===this.transport||i>0)&&this.triggerChanError()}triggerChanError(){this.channels.forEach(e=>{e.isErrored()||e.isLeaving()||e.isClosed()||e.trigger(m.error)})}connectionState(){switch(this.conn&&this.conn.readyState){case p.connecting:return"connecting";case p.open:return"open";case p.closing:return"closing";default:return"closed"}}isConnected(){return this.connectionState()==="open"}remove(e){this.off(e.stateChangeRefs),this.channels=this.channels.filter(t=>t!==e)}off(e){for(let t in this.stateChangeCallbacks)this.stateChangeCallbacks[t]=this.stateChangeCallbacks[t].filter(([i])=>e.indexOf(i)===-1)}channel(e,t={}){let i=new k(e,t,this);return this.channels.push(i),i}push(e){if(this.hasLogger()){let{topic:t,event:i,payload:s,ref:o,join_ref:r}=e;this.log("push",`${t} ${i} (${r}, ${o})`,s)}this.isConnected()?this.encode(e,t=>this.conn.send(t)):this.sendBuffer.push(()=>this.encode(e,t=>this.conn.send(t)))}makeRef(){let e=this.ref+1;return e===this.ref?this.ref=0:this.ref=e,this.ref.toString()}sendHeartbeat(){this.pendingHeartbeatRef&&!this.isConnected()||(this.pendingHeartbeatRef=this.makeRef(),this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:this.pendingHeartbeatRef}),this.heartbeatTimeoutTimer=setTimeout(()=>this.heartbeatTimeout(),this.heartbeatIntervalMs))}flushSendBuffer(){this.isConnected()&&this.sendBuffer.length>0&&(this.sendBuffer.forEach(e=>e()),this.sendBuffer=[])}onConnMessage(e){this.decode(e.data,t=>{let{topic:i,event:s,payload:o,ref:r,join_ref:n}=t;r&&r===this.pendingHeartbeatRef&&(this.clearHeartbeats(),this.pendingHeartbeatRef=null,this.heartbeatTimer=setTimeout(()=>this.sendHeartbeat(),this.heartbeatIntervalMs)),this.hasLogger()&&this.log("receive",`${o.status||""} ${i} ${s} ${r&&"("+r+")"||""}`,o);for(let h=0;hi.topic===e&&(i.isJoined()||i.isJoining()));t&&(this.hasLogger()&&this.log("transport",`leaving duplicate topic "${e}"`),t.leave())}};return F(W);})(); +var Phoenix=(()=>{var L=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var D=(a,e)=>{for(var t in e)L(a,t,{get:e[t],enumerable:!0})},I=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of M(e))!U.call(a,i)&&i!==t&&L(a,i,{get:()=>e[i],enumerable:!(s=$(e,i))||s.enumerable});return a};var F=a=>I(L({},"__esModule",{value:!0}),a);var K={};D(K,{Channel:()=>v,LongPoll:()=>g,Presence:()=>w,Serializer:()=>y,Socket:()=>x});function S(a){return typeof a=="function"?a:()=>a}var J=typeof self!="undefined"?self:null,k=typeof window!="undefined"?window:null,d=J||k||globalThis,N="2.0.0",p={connecting:0,open:1,closing:2,closed:3},O=1e4,P=1e3,u={closed:"closed",errored:"errored",joined:"joined",joining:"joining",leaving:"leaving"},m={close:"phx_close",error:"phx_error",join:"phx_join",reply:"phx_reply",leave:"phx_leave"},j={longpoll:"longpoll",websocket:"websocket"},B={complete:4},A="base64url.bearer.phx.";var C=class{constructor(e,t,s,i){this.channel=e,this.event=t,this.payload=typeof s=="function"?s:()=>s||{},this.receivedResp=null,this.timeout=i,this.timeoutTimer=null,this.recHooks=[],this.sent=!1,this.ref=null,this.refEvent=null}resend(e){this.timeout=e,this.reset(),this.send()}send(){this.hasReceived("timeout")||(this.startTimeout(),this.sent=!0,this.channel.socket.push({topic:this.channel.topic,event:this.event,payload:this.payload(),ref:this.ref,join_ref:this.channel.joinRef()}))}receive(e,t){return this.hasReceived(e)&&t(this.receivedResp.response),this.recHooks.push({status:e,callback:t}),this}reset(){this.cancelRefEvent(),this.ref=null,this.refEvent=null,this.receivedResp=null,this.sent=!1}matchReceive({status:e,response:t,_ref:s}){this.recHooks.filter(i=>i.status===e).forEach(i=>i.callback(t))}cancelRefEvent(){this.refEvent&&this.channel.off(this.refEvent)}cancelTimeout(){this.timeoutTimer!==null&&(clearTimeout(this.timeoutTimer),this.timeoutTimer=null)}startTimeout(){this.timeoutTimer&&this.cancelTimeout(),this.ref=this.channel.socket.makeRef(),this.refEvent=this.channel.replyEventName(this.ref),this.channel.on(this.refEvent,e=>{this.cancelRefEvent(),this.cancelTimeout(),this.receivedResp=e,this.matchReceive(e)}),this.timeoutTimer=setTimeout(()=>{this.trigger("timeout",{})},this.timeout)}hasReceived(e){return this.receivedResp&&this.receivedResp.status===e}trigger(e,t){this.channel.trigger(this.refEvent,{status:e,response:t})}};var R=class{constructor(e,t){this.callback=e,this.timerCalc=t,this.timer=null,this.tries=0}reset(){this.tries=0,this.timer!==null&&clearTimeout(this.timer)}scheduleTimeout(){this.timer!==null&&clearTimeout(this.timer),this.timer=setTimeout(()=>{this.tries=this.tries+1,this.callback()},this.timerCalc(this.tries+1))}};var v=class{constructor(e,t,s){this.state=u.closed,this.topic=e,this.params=S(t||{}),this.socket=s,this.bindings=[],this.bindingRef=0,this.timeout=this.socket.timeout,this.joinedOnce=!1,this.joinPush=new C(this,m.join,this.params,this.timeout),this.pushBuffer=[],this.stateChangeRefs=[],this.rejoinTimer=new R(()=>{this.socket.isConnected()&&this.rejoin()},this.socket.rejoinAfterMs),this.stateChangeRefs.push(this.socket.onError(()=>this.rejoinTimer.reset())),this.stateChangeRefs.push(this.socket.onOpen(()=>{this.rejoinTimer.reset(),this.isErrored()&&this.rejoin()})),this.joinPush.receive("ok",()=>{this.state=u.joined,this.rejoinTimer.reset(),this.pushBuffer.forEach(i=>i.send()),this.pushBuffer=[]}),this.joinPush.receive("error",()=>{this.state=u.errored,this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.onClose(()=>{this.rejoinTimer.reset(),this.socket.hasLogger()&&this.socket.log("channel",`close ${this.topic} ${this.joinRef()}`),this.state=u.closed,this.socket.remove(this)}),this.onError(i=>{this.socket.hasLogger()&&this.socket.log("channel",`error ${this.topic}`,i),this.isJoining()&&this.joinPush.reset(),this.state=u.errored,this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.joinPush.receive("timeout",()=>{this.socket.hasLogger()&&this.socket.log("channel",`timeout ${this.topic} (${this.joinRef()})`,this.joinPush.timeout),new C(this,m.leave,S({}),this.timeout).send(),this.state=u.errored,this.joinPush.reset(),this.socket.isConnected()&&this.rejoinTimer.scheduleTimeout()}),this.on(m.reply,(i,n)=>{this.trigger(this.replyEventName(n),i)})}join(e=this.timeout){if(this.joinedOnce)throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance");return this.timeout=e,this.joinedOnce=!0,this.rejoin(),this.joinPush}onClose(e){return this.on(m.close,e)}onError(e){return this.on(m.error,t=>e(t))}on(e,t){let s=this.bindingRef++;return this.bindings.push({event:e,ref:s,callback:t}),s}off(e,t){this.bindings=this.bindings.filter(s=>!(s.event===e&&(typeof t=="undefined"||t===s.ref)))}canPush(){return this.socket.isConnected()&&this.isJoined()}push(e,t={},s=this.timeout){if(!this.joinedOnce)throw new Error(`tried to push '${e}' to '${this.topic}' before joining. Use channel.join() before pushing events`);let i=new C(this,e,()=>t,s);return this.canPush()?i.send():(i.startTimeout(),this.pushBuffer.push(i)),i}leave(e=this.timeout){this.rejoinTimer.reset(),this.joinPush.cancelTimeout(),this.state=u.leaving;let t=()=>{this.socket.hasLogger()&&this.socket.log("channel",`leave ${this.topic}`),this.trigger(m.close,"leave")},s=new C(this,m.leave,S({}),e);return s.receive("ok",()=>t()).receive("timeout",()=>t()),s.send(),this.canPush()||s.trigger("ok",{}),s}onMessage(e,t,s,i){return t}isMember(e,t,s,i){return this.topic!==e?!1:i&&i!==this.joinRef()?(this.socket.hasLogger()&&this.socket.log("channel","dropping outdated message",{topic:e,event:t,payload:s,joinRef:i}),!1):!0}joinRef(){return this.joinPush.ref}rejoin(e=this.timeout){this.isLeaving()||(this.socket.leaveOpenTopic(this.topic),this.state=u.joining,this.joinPush.resend(e))}trigger(e,t,s,i){let n=this.onMessage(e,t,s,i);if(t&&!n)throw new Error("channel onMessage callbacks must return the payload, modified or unmodified");let r=this.bindings.filter(o=>o.event===e);for(let o=0;oc.abort(),n),h.signal=c.signal),d.fetch(t,h).then(l=>l.text()).then(l=>this.parseJSON(l)).then(l=>o&&o(l)).catch(l=>{l.name==="AbortError"&&r?r():o&&o(null)}),c}static xdomainRequest(e,t,s,i,n,r,o){return e.timeout=n,e.open(t,s),e.onload=()=>{let h=this.parseJSON(e.responseText);o&&o(h)},r&&(e.ontimeout=r),e.onprogress=()=>{},e.send(i),e}static xhrRequest(e,t,s,i,n,r,o,h){e.open(t,s,!0),e.timeout=r;for(let[c,l]of Object.entries(i))e.setRequestHeader(c,l);return e.onerror=()=>h&&h(null),e.onreadystatechange=()=>{if(e.readyState===B.complete&&h){let c=this.parseJSON(e.responseText);h(c)}},o&&(e.ontimeout=o),e.send(n),e}static parseJSON(e){if(!e||e==="")return null;try{return JSON.parse(e)}catch(t){return console&&console.log("failed to parse JSON response",e),null}}static serialize(e,t){let s=[];for(let i in e){if(!Object.prototype.hasOwnProperty.call(e,i))continue;let n=t?`${t}[${i}]`:i,r=e[i];typeof r=="object"?s.push(this.serialize(r,n)):s.push(encodeURIComponent(n)+"="+encodeURIComponent(r))}return s.join("&")}static appendParams(e,t){if(Object.keys(t).length===0)return e;let s=e.match(/\?/)?"&":"?";return`${e}${s}${this.serialize(t)}`}};function z(a){let e="",t=new Uint8Array(a),s=t.byteLength;for(let i=0;ithis.poll(),0)}normalizeEndpoint(e){return e.replace("ws://","http://").replace("wss://","https://").replace(new RegExp("(.*)/"+j.websocket),"$1/"+j.longpoll)}endpointURL(){return T.appendParams(this.pollEndpoint,{token:this.token})}closeAndRetry(e,t,s){this.close(e,t,s),this.readyState=p.connecting}ontimeout(){this.onerror("timeout"),this.closeAndRetry(1005,"timeout",!1)}isActive(){return this.readyState===p.open||this.readyState===p.connecting}poll(){let e={Accept:"application/json"};this.authToken&&(e["X-Phoenix-AuthToken"]=this.authToken),this.ajax("GET",e,null,()=>this.ontimeout(),t=>{let s;if(t){let{status:i,token:n}=t;s=i,this.token=n||null}else s=0;switch(s){case 200:t.messages.forEach(i=>{setTimeout(()=>this.onmessage({data:i}),0)}),this.poll();break;case 204:this.poll();break;case 410:this.readyState=p.open,this.onopen({}),this.poll();break;case 403:this.onerror(403),this.close(1008,"forbidden",!1);break;case 0:case 500:this.onerror(500),this.closeAndRetry(1011,"internal server error",!1);break;default:throw new Error(`unhandled poll status ${s}`)}})}send(e){let t;typeof e!="string"?t=z(e):t=e,this.currentBatch?this.currentBatch.push(t):this.awaitingBatchAck?this.batchBuffer.push(t):(this.currentBatch=[t],this.currentBatchTimer=setTimeout(()=>{this.batchSend(this.currentBatch),this.currentBatch=null},0))}batchSend(e){this.awaitingBatchAck=!0,this.ajax("POST",{"Content-Type":"application/x-ndjson"},e.join(` +`),()=>this.onerror("timeout"),t=>{this.awaitingBatchAck=!1,!t||t.status!==200?(this.onerror(t&&t.status),this.closeAndRetry(1011,"internal server error",!1)):this.batchBuffer.length>0&&(this.batchSend(this.batchBuffer),this.batchBuffer=[])})}close(e,t,s){for(let n of this.reqs)n.abort();this.readyState=p.closed;let i=Object.assign({code:1e3,reason:void 0,wasClean:!0},{code:e,reason:t,wasClean:s});this.batchBuffer=[],this.currentBatchTimer!==null&&(clearTimeout(this.currentBatchTimer),this.currentBatchTimer=null),typeof CloseEvent!="undefined"?this.onclose(new CloseEvent("close",i)):this.onclose(i)}ajax(e,t,s,i,n){let r=()=>{this.reqs.delete(o),i()},o=T.request(e,this.endpointURL(),t,s,this.timeout,r,h=>{this.reqs.delete(o),this.isActive()&&n(h)});this.reqs.add(o)}};var w=class a{constructor(e,t={}){let s=t.events||{state:"presence_state",diff:"presence_diff"};this.state={},this.pendingDiffs=[],this.channel=e,this.joinRef=null,this.caller={onJoin:function(){},onLeave:function(){},onSync:function(){}},this.channel.on(s.state,i=>{let{onJoin:n,onLeave:r,onSync:o}=this.caller;this.joinRef=this.channel.joinRef(),this.state=a.syncState(this.state,i,n,r),this.pendingDiffs.forEach(h=>{this.state=a.syncDiff(this.state,h,n,r)}),this.pendingDiffs=[],o()}),this.channel.on(s.diff,i=>{let{onJoin:n,onLeave:r,onSync:o}=this.caller;this.inPendingSyncState()?this.pendingDiffs.push(i):(this.state=a.syncDiff(this.state,i,n,r),o())})}onJoin(e){this.caller.onJoin=e}onLeave(e){this.caller.onLeave=e}onSync(e){this.caller.onSync=e}list(e){return a.list(this.state,e)}inPendingSyncState(){return!this.joinRef||this.joinRef!==this.channel.joinRef()}static syncState(e,t,s,i){let n=this.clone(e),r={},o={};return this.map(n,(h,c)=>{t[h]||(o[h]=c)}),this.map(t,(h,c)=>{let l=n[h];if(l){let f=c.metas.map(E=>E.phx_ref),b=l.metas.map(E=>E.phx_ref),_=c.metas.filter(E=>b.indexOf(E.phx_ref)<0),H=l.metas.filter(E=>f.indexOf(E.phx_ref)<0);_.length>0&&(r[h]=c,r[h].metas=_),H.length>0&&(o[h]=this.clone(l),o[h].metas=H)}else r[h]=c}),this.syncDiff(n,{joins:r,leaves:o},s,i)}static syncDiff(e,t,s,i){let{joins:n,leaves:r}=this.clone(t);return s||(s=function(){}),i||(i=function(){}),this.map(n,(o,h)=>{let c=e[o];if(e[o]=this.clone(h),c){let l=e[o].metas.map(b=>b.phx_ref),f=c.metas.filter(b=>l.indexOf(b.phx_ref)<0);e[o].metas.unshift(...f)}s(o,c,h)}),this.map(r,(o,h)=>{let c=e[o];if(!c)return;let l=h.metas.map(f=>f.phx_ref);c.metas=c.metas.filter(f=>l.indexOf(f.phx_ref)<0),i(o,c,h),c.metas.length===0&&delete e[o]}),e}static list(e,t){return t||(t=function(s,i){return i}),this.map(e,(s,i)=>t(s,i))}static map(e,t){return Object.getOwnPropertyNames(e).map(s=>t(s,e[s]))}static clone(e){return JSON.parse(JSON.stringify(e))}};var W={HEADER_LENGTH:1,META_LENGTH:4,KINDS:{push:0,reply:1,broadcast:2},encode(a,e){if(a.payload.constructor===ArrayBuffer)return e(this.binaryEncode(a));{let t=[a.join_ref,a.ref,a.topic,a.event,a.payload];return e(JSON.stringify(t))}},decode(a,e){if(a.constructor===ArrayBuffer)return e(this.binaryDecode(a));{let[t,s,i,n,r]=JSON.parse(a);return e({join_ref:t,ref:s,topic:i,event:n,payload:r})}},binaryEncode(a){let{join_ref:e,ref:t,event:s,topic:i,payload:n}=a,r=this.META_LENGTH+e.length+t.length+i.length+s.length,o=new ArrayBuffer(this.HEADER_LENGTH+r),h=new DataView(o),c=0;h.setUint8(c++,this.KINDS.push),h.setUint8(c++,e.length),h.setUint8(c++,t.length),h.setUint8(c++,i.length),h.setUint8(c++,s.length),Array.from(e,f=>h.setUint8(c++,f.charCodeAt(0))),Array.from(t,f=>h.setUint8(c++,f.charCodeAt(0))),Array.from(i,f=>h.setUint8(c++,f.charCodeAt(0))),Array.from(s,f=>h.setUint8(c++,f.charCodeAt(0)));let l=new Uint8Array(o.byteLength+n.byteLength);return l.set(new Uint8Array(o),0),l.set(new Uint8Array(n),o.byteLength),l.buffer},binaryDecode(a){let e=new DataView(a),t=e.getUint8(0),s=new TextDecoder;switch(t){case this.KINDS.push:return this.decodePush(a,e,s);case this.KINDS.reply:return this.decodeReply(a,e,s);case this.KINDS.broadcast:return this.decodeBroadcast(a,e,s);default:throw new Error(`Unknown message kind: ${t}`)}},decodePush(a,e,t){let s=e.getUint8(1),i=e.getUint8(2),n=e.getUint8(3),r=this.HEADER_LENGTH+this.META_LENGTH-1,o=t.decode(a.slice(r,r+s));r=r+s;let h=t.decode(a.slice(r,r+i));r=r+i;let c=t.decode(a.slice(r,r+n));r=r+n;let l=a.slice(r,a.byteLength);return{join_ref:o,ref:null,topic:h,event:c,payload:l}},decodeReply(a,e,t){let s=e.getUint8(1),i=e.getUint8(2),n=e.getUint8(3),r=e.getUint8(4),o=this.HEADER_LENGTH+this.META_LENGTH,h=t.decode(a.slice(o,o+s));o=o+s;let c=t.decode(a.slice(o,o+i));o=o+i;let l=t.decode(a.slice(o,o+n));o=o+n;let f=t.decode(a.slice(o,o+r));o=o+r;let b=a.slice(o,a.byteLength),_={status:f,response:b};return{join_ref:h,ref:c,topic:l,event:m.reply,payload:_}},decodeBroadcast(a,e,t){let s=e.getUint8(1),i=e.getUint8(2),n=this.HEADER_LENGTH+2,r=t.decode(a.slice(n,n+s));n=n+s;let o=t.decode(a.slice(n,n+i));n=n+i;let h=a.slice(n,a.byteLength);return{join_ref:null,ref:null,topic:r,event:o,payload:h}}},y=W;var x=class{constructor(e,t={}){var s;this.stateChangeCallbacks={open:[],close:[],error:[],message:[]},this.channels=[],this.sendBuffer=[],this.ref=0,this.timeout=t.timeout||O,this.transport=t.transport||d.WebSocket||g,this.primaryPassedHealthCheck=!1,this.longPollFallbackMs=(s=t.longPollFallbackMs)!==null&&s!==void 0?s:null,this.fallbackTimer=null,this.sessionStore=t.sessionStorage||d&&d.sessionStorage,this.establishedConnections=0,this.defaultEncoder=y.encode.bind(y),this.defaultDecoder=y.decode.bind(y),this.closeWasClean=!1,this.disconnecting=!1,this.binaryType=t.binaryType||"arraybuffer",this.connectClock=1,this.conn=null,this.transport!==g?(this.encode=t.encode||this.defaultEncoder,this.decode=t.decode||this.defaultDecoder):(this.encode=this.defaultEncoder,this.decode=this.defaultDecoder);let i=null;k&&k.addEventListener&&(k.addEventListener("pagehide",n=>{this.conn&&(this.disconnect(),i=this.connectClock)}),k.addEventListener("pageshow",n=>{i===this.connectClock&&(i=null,this.connect())})),this.heartbeatIntervalMs=t.heartbeatIntervalMs||3e4,this.rejoinAfterMs=n=>t.rejoinAfterMs?t.rejoinAfterMs(n):[1e3,2e3,5e3][n-1]||1e4,this.reconnectAfterMs=n=>t.reconnectAfterMs?t.reconnectAfterMs(n):[10,50,100,150,200,250,500,1e3,2e3][n-1]||5e3,this.logger=t.logger||null,!this.logger&&t.debug&&(this.logger=(n,r,o)=>{console.log(`${n}: ${r}`,o)}),this.longpollerTimeout=t.longpollerTimeout||2e4,this.params=S(t.params||{}),this.endPoint=`${e}/${j.websocket}`,this.vsn=t.vsn||N,this.heartbeatTimeoutTimer=null,this.heartbeatTimer=null,this.pendingHeartbeatRef=null,this.reconnectTimer=new R(()=>{this.teardown(()=>this.connect())},this.reconnectAfterMs),this.authToken=t.authToken}getLongPollTransport(){return g}replaceTransport(e){this.connectClock++,this.closeWasClean=!0,clearTimeout(this.fallbackTimer),this.reconnectTimer.reset(),this.conn&&(this.conn.close(),this.conn=null),this.transport=e}protocol(){return location.protocol.match(/^https/)?"wss":"ws"}endPointURL(){let e=T.appendParams(T.appendParams(this.endPoint,this.params()),{vsn:this.vsn});return e.charAt(0)!=="/"?e:e.charAt(1)==="/"?`${this.protocol()}:${e}`:`${this.protocol()}://${location.host}${e}`}disconnect(e,t,s){this.connectClock++,this.disconnecting=!0,this.closeWasClean=!0,clearTimeout(this.fallbackTimer),this.reconnectTimer.reset(),this.teardown(()=>{this.disconnecting=!1,e&&e()},t,s)}connect(e){e&&(console&&console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor"),this.params=S(e)),!(this.conn&&!this.disconnecting)&&(this.longPollFallbackMs&&this.transport!==g?this.connectWithFallback(g,this.longPollFallbackMs):this.transportConnect())}log(e,t,s){this.logger&&this.logger(e,t,s)}hasLogger(){return this.logger!==null}onOpen(e){let t=this.makeRef();return this.stateChangeCallbacks.open.push([t,e]),t}onClose(e){let t=this.makeRef();return this.stateChangeCallbacks.close.push([t,e]),t}onError(e){let t=this.makeRef();return this.stateChangeCallbacks.error.push([t,e]),t}onMessage(e){let t=this.makeRef();return this.stateChangeCallbacks.message.push([t,e]),t}ping(e){if(!this.isConnected())return!1;let t=this.makeRef(),s=Date.now();this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:t});let i=this.onMessage(n=>{n.ref===t&&(this.off([i]),e(Date.now()-s))});return!0}transportConnect(){this.connectClock++,this.closeWasClean=!1;let e;this.authToken&&(e=["phoenix",`${A}${btoa(this.authToken).replace(/=/g,"")}`]),this.conn=new this.transport(this.endPointURL(),e),this.conn.binaryType=this.binaryType,this.conn.timeout=this.longpollerTimeout,this.conn.onopen=()=>this.onConnOpen(),this.conn.onerror=t=>this.onConnError(t),this.conn.onmessage=t=>this.onConnMessage(t),this.conn.onclose=t=>this.onConnClose(t)}getSession(e){return this.sessionStore&&this.sessionStore.getItem(e)}storeSession(e,t){this.sessionStore&&this.sessionStore.setItem(e,t)}connectWithFallback(e,t=2500){clearTimeout(this.fallbackTimer);let s=!1,i=!0,n,r=h=>{this.log("transport",`falling back to ${e.name}...`,h),this.off([n,o]),i=!1,this.replaceTransport(e),this.transportConnect()};if(this.getSession(`phx:fallback:${e.name}`))return r("memorized");this.fallbackTimer=setTimeout(r,t);let o=this.onError(h=>{this.log("transport","error",h),i&&!s&&(clearTimeout(this.fallbackTimer),r(h))});this.onOpen(()=>{if(s=!0,!i)return this.primaryPassedHealthCheck||this.storeSession(`phx:fallback:${e.name}`,"true"),this.log("transport",`established ${e.name} fallback`);clearTimeout(this.fallbackTimer),this.fallbackTimer=setTimeout(r,t),this.ping(h=>{this.log("transport","connected to primary after",h),this.primaryPassedHealthCheck=!0,clearTimeout(this.fallbackTimer)})}),this.transportConnect()}clearHeartbeats(){clearTimeout(this.heartbeatTimer),clearTimeout(this.heartbeatTimeoutTimer)}onConnOpen(){this.hasLogger()&&this.log("transport",`${this.transport.name} connected to ${this.endPointURL()}`),this.closeWasClean=!1,this.disconnecting=!1,this.establishedConnections++,this.flushSendBuffer(),this.reconnectTimer.reset(),this.resetHeartbeat(),this.stateChangeCallbacks.open.forEach(([,e])=>e())}heartbeatTimeout(){this.pendingHeartbeatRef&&(this.pendingHeartbeatRef=null,this.hasLogger()&&this.log("transport","heartbeat timeout. Attempting to re-establish connection"),this.triggerChanError(),this.closeWasClean=!1,this.teardown(()=>this.reconnectTimer.scheduleTimeout(),P,"heartbeat timeout"))}resetHeartbeat(){this.conn&&this.conn.skipHeartbeat||(this.pendingHeartbeatRef=null,this.clearHeartbeats(),this.heartbeatTimer=setTimeout(()=>this.sendHeartbeat(),this.heartbeatIntervalMs))}teardown(e,t,s){if(!this.conn)return e&&e();let i=this.connectClock;this.waitForBufferDone(()=>{i===this.connectClock&&(this.conn&&(t?this.conn.close(t,s||""):this.conn.close()),this.waitForSocketClosed(()=>{i===this.connectClock&&(this.conn&&(this.conn.onopen=function(){},this.conn.onerror=function(){},this.conn.onmessage=function(){},this.conn.onclose=function(){},this.conn=null),e&&e())}))})}waitForBufferDone(e,t=1){if(t===5||!this.conn||!this.conn.bufferedAmount){e();return}setTimeout(()=>{this.waitForBufferDone(e,t+1)},150*t)}waitForSocketClosed(e,t=1){if(t===5||!this.conn||this.conn.readyState===p.closed){e();return}setTimeout(()=>{this.waitForSocketClosed(e,t+1)},150*t)}onConnClose(e){let t=e&&e.code;this.hasLogger()&&this.log("transport","close",e),this.triggerChanError(),this.clearHeartbeats(),!this.closeWasClean&&t!==1e3&&this.reconnectTimer.scheduleTimeout(),this.stateChangeCallbacks.close.forEach(([,s])=>s(e))}onConnError(e){this.hasLogger()&&this.log("transport","error",e);let t=this.transport,s=this.establishedConnections;this.stateChangeCallbacks.error.forEach(([,i])=>{i(e,t,s)}),(t===this.transport||s>0)&&this.triggerChanError()}triggerChanError(){this.channels.forEach(e=>{e.isErrored()||e.isLeaving()||e.isClosed()||e.trigger(m.error,{})})}connectionState(){switch(this.conn&&this.conn.readyState){case p.connecting:return"connecting";case p.open:return"open";case p.closing:return"closing";default:return"closed"}}isConnected(){return this.connectionState()==="open"}remove(e){this.off(e.stateChangeRefs),this.channels=this.channels.filter(t=>t!==e)}off(e){let t=s=>s.filter(([i])=>e.indexOf(i)===-1);this.stateChangeCallbacks={open:t(this.stateChangeCallbacks.open),close:t(this.stateChangeCallbacks.close),error:t(this.stateChangeCallbacks.error),message:t(this.stateChangeCallbacks.message)}}channel(e,t={}){let s=new v(e,t,this);return this.channels.push(s),s}push(e){if(this.hasLogger()){let{topic:t,event:s,payload:i,ref:n,join_ref:r}=e;this.log("push",`${t} ${s} (${r}, ${n})`,i)}this.isConnected()?this.encode(e,t=>this.conn.send(t)):this.sendBuffer.push(()=>this.encode(e,t=>this.conn.send(t)))}makeRef(){let e=this.ref+1;return e===this.ref?this.ref=0:this.ref=e,this.ref.toString()}sendHeartbeat(){this.pendingHeartbeatRef&&!this.isConnected()||(this.pendingHeartbeatRef=this.makeRef(),this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:this.pendingHeartbeatRef}),this.heartbeatTimeoutTimer=setTimeout(()=>this.heartbeatTimeout(),this.heartbeatIntervalMs))}flushSendBuffer(){this.isConnected()&&this.sendBuffer.length>0&&(this.sendBuffer.forEach(e=>e()),this.sendBuffer=[])}onConnMessage(e){this.decode(e.data,t=>{let{topic:s,event:i,payload:n,ref:r,join_ref:o}=t;r&&r===this.pendingHeartbeatRef&&(this.clearHeartbeats(),this.pendingHeartbeatRef=null,this.heartbeatTimer=setTimeout(()=>this.sendHeartbeat(),this.heartbeatIntervalMs)),this.hasLogger()&&this.log("receive",`${n.status||""} ${s} ${i} ${r&&"("+r+")"||""}`,n);for(let h=0;hs.topic===e&&(s.isJoined()||s.isJoining()));t&&(this.hasLogger()&&this.log("transport",`leaving duplicate topic "${e}"`),t.leave())}};return F(K);})(); diff --git a/priv/static/phoenix.mjs b/priv/static/phoenix.mjs index 54940e5ec2..5076dc1fb8 100644 --- a/priv/static/phoenix.mjs +++ b/priv/static/phoenix.mjs @@ -1,21 +1,23 @@ -// js/phoenix/utils.js -var closure = (value) => { +// dist/phoenix/utils.js +function closure(value) { if (typeof value === "function") { return value; } else { - let closure2 = function() { - return value; - }; - return closure2; + return () => value; } -}; +} -// js/phoenix/constants.js +// dist/phoenix/constants.js var globalSelf = typeof self !== "undefined" ? self : null; var phxWindow = typeof window !== "undefined" ? window : null; var global = globalSelf || phxWindow || globalThis; var DEFAULT_VSN = "2.0.0"; -var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; +var SOCKET_STATES = { + connecting: 0, + open: 1, + closing: 2, + closed: 3 +}; var DEFAULT_TIMEOUT = 1e4; var WS_CLOSE_NORMAL = 1e3; var CHANNEL_STATES = { @@ -41,23 +43,22 @@ var XHR_STATES = { }; var AUTH_TOKEN_PREFIX = "base64url.bearer.phx."; -// js/phoenix/push.js +// dist/phoenix/push.js var Push = class { constructor(channel, event, payload, timeout) { this.channel = channel; this.event = event; - this.payload = payload || function() { - return {}; - }; + this.payload = typeof payload === "function" ? payload : () => payload || {}; this.receivedResp = null; this.timeout = timeout; this.timeoutTimer = null; this.recHooks = []; this.sent = false; + this.ref = null; + this.refEvent = null; } /** - * - * @param {number} timeout + * Resend the push with a new timeout */ resend(timeout) { this.timeout = timeout; @@ -65,7 +66,7 @@ var Push = class { this.send(); } /** - * + * Send the push */ send() { if (this.hasReceived("timeout")) { @@ -82,9 +83,7 @@ var Push = class { }); } /** - * - * @param {*} status - * @param {*} callback + * Register a callback for a specific response status */ receive(status, callback) { if (this.hasReceived(status)) { @@ -122,8 +121,10 @@ var Push = class { * @private */ cancelTimeout() { - clearTimeout(this.timeoutTimer); - this.timeoutTimer = null; + if (this.timeoutTimer !== null) { + clearTimeout(this.timeoutTimer); + this.timeoutTimer = null; + } } /** * @private @@ -158,7 +159,7 @@ var Push = class { } }; -// js/phoenix/timer.js +// dist/phoenix/timer.js var Timer = class { constructor(callback, timerCalc) { this.callback = callback; @@ -168,13 +169,17 @@ var Timer = class { } reset() { this.tries = 0; - clearTimeout(this.timer); + if (this.timer !== null) { + clearTimeout(this.timer); + } } /** * Cancels any previous scheduleTimeout and schedules callback */ scheduleTimeout() { - clearTimeout(this.timer); + if (this.timer !== null) { + clearTimeout(this.timer); + } this.timer = setTimeout(() => { this.tries = this.tries + 1; this.callback(); @@ -182,7 +187,7 @@ var Timer = class { } }; -// js/phoenix/channel.js +// dist/phoenix/channel.js var Channel = class { constructor(topic, params, socket) { this.state = CHANNEL_STATES.closed; @@ -202,14 +207,12 @@ var Channel = class { } }, this.socket.rejoinAfterMs); this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset())); - this.stateChangeRefs.push( - this.socket.onOpen(() => { - this.rejoinTimer.reset(); - if (this.isErrored()) { - this.rejoin(); - } - }) - ); + this.stateChangeRefs.push(this.socket.onOpen(() => { + this.rejoinTimer.reset(); + if (this.isErrored()) { + this.rejoin(); + } + })); this.joinPush.receive("ok", () => { this.state = CHANNEL_STATES.joined; this.rejoinTimer.reset(); @@ -243,7 +246,7 @@ var Channel = class { this.joinPush.receive("timeout", () => { if (this.socket.hasLogger()) this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout); - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout); + const leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout); leavePush.send(); this.state = CHANNEL_STATES.errored; this.joinPush.reset(); @@ -257,8 +260,6 @@ var Channel = class { } /** * Join the channel - * @param {integer} timeout - * @returns {Push} */ join(timeout = this.timeout) { if (this.joinedOnce) { @@ -272,14 +273,12 @@ var Channel = class { } /** * Hook into channel close - * @param {Function} callback */ onClose(callback) { - this.on(CHANNEL_EVENTS.close, callback); + return this.on(CHANNEL_EVENTS.close, callback); } /** * Hook into channel errors - * @param {Function} callback */ onError(callback) { return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason)); @@ -296,13 +295,9 @@ var Channel = class { * channel.off("event", ref1) * // Since unsubscription, do_stuff won't fire, * // while do_other_stuff will keep firing on the "event" - * - * @param {string} event - * @param {Function} callback - * @returns {integer} ref */ on(event, callback) { - let ref = this.bindingRef++; + const ref = this.bindingRef++; this.bindings.push({ event, ref, callback }); return ref; } @@ -320,9 +315,6 @@ var Channel = class { * * // Unsubscribe all handlers from event * channel.off("event") - * - * @param {string} event - * @param {integer} ref */ off(event, ref) { this.bindings = this.bindings.filter((bind) => { @@ -330,6 +322,7 @@ var Channel = class { }); } /** + * @internal * @private */ canPush() { @@ -346,19 +339,12 @@ var Channel = class { * .receive("ok", payload => console.log("phoenix replied:", payload)) * .receive("error", err => console.log("phoenix errored", err)) * .receive("timeout", () => console.log("timed out pushing")) - * @param {string} event - * @param {Object} payload - * @param {number} [timeout] - * @returns {Push} */ - push(event, payload, timeout = this.timeout) { - payload = payload || {}; + push(event, payload = {}, timeout = this.timeout) { if (!this.joinedOnce) { throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`); } - let pushEvent = new Push(this, event, function() { - return payload; - }, timeout); + const pushEvent = new Push(this, event, () => payload, timeout); if (this.canPush()) { pushEvent.send(); } else { @@ -367,7 +353,8 @@ var Channel = class { } return pushEvent; } - /** Leaves the channel + /** + * Leaves the channel * * Unsubscribes from server events, and * instructs channel to terminate on server @@ -379,20 +366,17 @@ var Channel = class { * * @example * channel.leave().receive("ok", () => alert("left!") ) - * - * @param {integer} timeout - * @returns {Push} */ leave(timeout = this.timeout) { this.rejoinTimer.reset(); this.joinPush.cancelTimeout(); this.state = CHANNEL_STATES.leaving; - let onClose = () => { + const onClose = () => { if (this.socket.hasLogger()) this.socket.log("channel", `leave ${this.topic}`); this.trigger(CHANNEL_EVENTS.close, "leave"); }; - let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout); + const leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout); leavePush.receive("ok", () => onClose()).receive("timeout", () => onClose()); leavePush.send(); if (!this.canPush()) { @@ -407,15 +391,12 @@ var Channel = class { * before dispatching to the channel callbacks. * * Must return the payload, modified or unmodified - * @param {string} event - * @param {Object} payload - * @param {integer} ref - * @returns {Object} */ - onMessage(_event, payload, _ref) { + onMessage(_event, payload, _ref, _joinRef) { return payload; } /** + * @internal * @private */ isMember(topic, event, payload, joinRef) { @@ -424,19 +405,26 @@ var Channel = class { } if (joinRef && joinRef !== this.joinRef()) { if (this.socket.hasLogger()) - this.socket.log("channel", "dropping outdated message", { topic, event, payload, joinRef }); + this.socket.log("channel", "dropping outdated message", { + topic, + event, + payload, + joinRef + }); return false; } else { return true; } } /** + * @internal * @private */ joinRef() { return this.joinPush.ref; } /** + * @internal * @private */ rejoin(timeout = this.timeout) { @@ -448,50 +436,57 @@ var Channel = class { this.joinPush.resend(timeout); } /** + * @internal * @private */ trigger(event, payload, ref, joinRef) { - let handledPayload = this.onMessage(event, payload, ref, joinRef); + const handledPayload = this.onMessage(event, payload, ref, joinRef); if (payload && !handledPayload) { throw new Error("channel onMessage callbacks must return the payload, modified or unmodified"); } - let eventBindings = this.bindings.filter((bind) => bind.event === event); + const eventBindings = this.bindings.filter((bind) => bind.event === event); for (let i = 0; i < eventBindings.length; i++) { - let bind = eventBindings[i]; + const bind = eventBindings[i]; bind.callback(handledPayload, ref, joinRef || this.joinRef()); } } /** + * @internal * @private */ replyEventName(ref) { return `chan_reply_${ref}`; } /** + * @internal * @private */ isClosed() { return this.state === CHANNEL_STATES.closed; } /** + * @internal * @private */ isErrored() { return this.state === CHANNEL_STATES.errored; } /** + * @internal * @private */ isJoined() { return this.state === CHANNEL_STATES.joined; } /** + * @internal * @private */ isJoining() { return this.state === CHANNEL_STATES.joining; } /** + * @internal * @private */ isLeaving() { @@ -499,31 +494,30 @@ var Channel = class { } }; -// js/phoenix/ajax.js +// dist/phoenix/ajax.js var Ajax = class { static request(method, endPoint, headers, body, timeout, ontimeout, callback) { if (global.XDomainRequest) { - let req = new global.XDomainRequest(); + const req = new global.XDomainRequest(); return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback); } else if (global.XMLHttpRequest) { - let req = new global.XMLHttpRequest(); + const req = new global.XMLHttpRequest(); return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback); - } else if (global.fetch && global.AbortController) { + } else if (typeof global.fetch === "function" && typeof global.AbortController === "function") { return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback); } else { throw new Error("No suitable XMLHttpRequest implementation found"); } } static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) { - let options = { + const options = { method, headers, body }; - let controller = null; + const controller = new AbortController(); if (timeout) { - controller = new AbortController(); - const _timeoutId = setTimeout(() => controller.abort(), timeout); + setTimeout(() => controller.abort(), timeout); options.signal = controller.signal; } global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => { @@ -539,7 +533,7 @@ var Ajax = class { req.timeout = timeout; req.open(method, endPoint); req.onload = () => { - let response = this.parseJSON(req.responseText); + const response = this.parseJSON(req.responseText); callback && callback(response); }; if (ontimeout) { @@ -553,13 +547,13 @@ var Ajax = class { static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) { req.open(method, endPoint, true); req.timeout = timeout; - for (let [key, value] of Object.entries(headers)) { + for (const [key, value] of Object.entries(headers)) { req.setRequestHeader(key, value); } req.onerror = () => callback && callback(null); req.onreadystatechange = () => { if (req.readyState === XHR_STATES.complete && callback) { - let response = this.parseJSON(req.responseText); + const response = this.parseJSON(req.responseText); callback(response); } }; @@ -575,19 +569,19 @@ var Ajax = class { } try { return JSON.parse(resp); - } catch { + } catch (_a) { console && console.log("failed to parse JSON response", resp); return null; } } static serialize(obj, parentKey) { - let queryStr = []; - for (var key in obj) { + const queryStr = []; + for (const key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) { continue; } - let paramKey = parentKey ? `${parentKey}[${key}]` : key; - let paramVal = obj[key]; + const paramKey = parentKey ? `${parentKey}[${key}]` : key; + const paramVal = obj[key]; if (typeof paramVal === "object") { queryStr.push(this.serialize(paramVal, paramKey)); } else { @@ -600,21 +594,21 @@ var Ajax = class { if (Object.keys(params).length === 0) { return url; } - let prefix = url.match(/\?/) ? "&" : "?"; + const prefix = url.match(/\?/) ? "&" : "?"; return `${url}${prefix}${this.serialize(params)}`; } }; -// js/phoenix/longpoll.js -var arrayBufferToBase64 = (buffer) => { +// dist/phoenix/longpoll.js +function arrayBufferToBase64(buffer) { let binary = ""; - let bytes = new Uint8Array(buffer); - let len = bytes.byteLength; + const bytes = new Uint8Array(buffer); + const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); -}; +} var LongPoll = class { constructor(endPoint, protocols) { if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) { @@ -638,6 +632,7 @@ var LongPoll = class { }; this.pollEndpoint = this.normalizeEndpoint(endPoint); this.readyState = SOCKET_STATES.connecting; + this.timeout = 2e4; setTimeout(() => this.poll(), 0); } normalizeEndpoint(endPoint) { @@ -658,20 +653,22 @@ var LongPoll = class { return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting; } poll() { - const headers = { "Accept": "application/json" }; + const headers = { Accept: "application/json" }; if (this.authToken) { headers["X-Phoenix-AuthToken"] = this.authToken; } this.ajax("GET", headers, null, () => this.ontimeout(), (resp) => { + let status; if (resp) { - var { status, token, messages } = resp; - this.token = token; + const { status: respStatus, token } = resp; + status = respStatus; + this.token = token || null; } else { status = 0; } switch (status) { case 200: - messages.forEach((msg) => { + resp.messages.forEach((msg) => { setTimeout(() => this.onmessage({ data: msg }), 0); }); this.poll(); @@ -691,7 +688,7 @@ var LongPoll = class { case 0: case 500: this.onerror(500); - this.closeAndRetry(1011, "internal server error", 500); + this.closeAndRetry(1011, "internal server error", false); break; default: throw new Error(`unhandled poll status ${status}`); @@ -702,15 +699,18 @@ var LongPoll = class { // setTimeout 0, which optimizes back-to-back procedural // pushes against an empty buffer send(body) { + let bodyStr; if (typeof body !== "string") { - body = arrayBufferToBase64(body); + bodyStr = arrayBufferToBase64(body); + } else { + bodyStr = body; } if (this.currentBatch) { - this.currentBatch.push(body); + this.currentBatch.push(bodyStr); } else if (this.awaitingBatchAck) { - this.batchBuffer.push(body); + this.batchBuffer.push(bodyStr); } else { - this.currentBatch = [body]; + this.currentBatch = [bodyStr]; this.currentBatchTimer = setTimeout(() => { this.batchSend(this.currentBatch); this.currentBatch = null; @@ -731,14 +731,16 @@ var LongPoll = class { }); } close(code, reason, wasClean) { - for (let req of this.reqs) { + for (const req of this.reqs) { req.abort(); } this.readyState = SOCKET_STATES.closed; - let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean }); + const opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean }); this.batchBuffer = []; - clearTimeout(this.currentBatchTimer); - this.currentBatchTimer = null; + if (this.currentBatchTimer !== null) { + clearTimeout(this.currentBatchTimer); + this.currentBatchTimer = null; + } if (typeof CloseEvent !== "undefined") { this.onclose(new CloseEvent("close", opts)); } else { @@ -746,12 +748,11 @@ var LongPoll = class { } } ajax(method, headers, body, onCallerTimeout, callback) { - let req; - let ontimeout = () => { + const ontimeout = () => { this.reqs.delete(req); onCallerTimeout(); }; - req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => { + const req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => { this.reqs.delete(req); if (this.isActive()) { callback(resp); @@ -761,10 +762,13 @@ var LongPoll = class { } }; -// js/phoenix/presence.js -var Presence = class { +// dist/phoenix/presence.js +var Presence = class _Presence { constructor(channel, opts = {}) { - let events = opts.events || { state: "presence_state", diff: "presence_diff" }; + const events = opts.events || { + state: "presence_state", + diff: "presence_diff" + }; this.state = {}; this.pendingDiffs = []; this.channel = channel; @@ -778,37 +782,57 @@ var Presence = class { } }; this.channel.on(events.state, (newState) => { - let { onJoin, onLeave, onSync } = this.caller; + const { onJoin, onLeave, onSync } = this.caller; this.joinRef = this.channel.joinRef(); - this.state = Presence.syncState(this.state, newState, onJoin, onLeave); + this.state = _Presence.syncState(this.state, newState, onJoin, onLeave); this.pendingDiffs.forEach((diff) => { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave); }); this.pendingDiffs = []; onSync(); }); this.channel.on(events.diff, (diff) => { - let { onJoin, onLeave, onSync } = this.caller; + const { onJoin, onLeave, onSync } = this.caller; if (this.inPendingSyncState()) { this.pendingDiffs.push(diff); } else { - this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); + this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave); onSync(); } }); } + /** + * @internal + * @private + */ onJoin(callback) { this.caller.onJoin = callback; } + /** + * @internal + * @private + */ onLeave(callback) { this.caller.onLeave = callback; } + /** + * @internal + * @private + */ onSync(callback) { this.caller.onSync = callback; } + /** + * @internal + * @private + */ list(by) { - return Presence.list(this.state, by); + return _Presence.list(this.state, by); } + /** + * @internal + * @private + */ inPendingSyncState() { return !this.joinRef || this.joinRef !== this.channel.joinRef(); } @@ -818,25 +842,23 @@ var Presence = class { * with the client's state. An optional `onJoin` and `onLeave` callback can * be provided to react to changes in the client's local presences across * disconnects and reconnects with the server. - * - * @returns {Presence} */ static syncState(currentState, newState, onJoin, onLeave) { - let state = this.clone(currentState); - let joins = {}; - let leaves = {}; + const state = this.clone(currentState); + const joins = {}; + const leaves = {}; this.map(state, (key, presence) => { if (!newState[key]) { leaves[key] = presence; } }); this.map(newState, (key, newPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; if (currentPresence) { - let newRefs = newPresence.metas.map((m) => m.phx_ref); - let curRefs = currentPresence.metas.map((m) => m.phx_ref); - let joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0); - let leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0); + const newRefs = newPresence.metas.map((m) => m.phx_ref); + const curRefs = currentPresence.metas.map((m) => m.phx_ref); + const joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0); + const leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0); if (joinedMetas.length > 0) { joins[key] = newPresence; joins[key].metas = joinedMetas; @@ -852,16 +874,13 @@ var Presence = class { return this.syncDiff(state, { joins, leaves }, onJoin, onLeave); } /** - * * Used to sync a diff of presence join and leave * events from the server, as they happen. Like `syncState`, `syncDiff` * accepts optional `onJoin` and `onLeave` callbacks to react to a user * joining or leaving from a device. - * - * @returns {Presence} */ static syncDiff(state, diff, onJoin, onLeave) { - let { joins, leaves } = this.clone(diff); + const { joins, leaves } = this.clone(diff); if (!onJoin) { onJoin = function() { }; @@ -871,21 +890,21 @@ var Presence = class { }; } this.map(joins, (key, newPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; state[key] = this.clone(newPresence); if (currentPresence) { - let joinedRefs = state[key].metas.map((m) => m.phx_ref); - let curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0); + const joinedRefs = state[key].metas.map((m) => m.phx_ref); + const curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0); state[key].metas.unshift(...curMetas); } onJoin(key, currentPresence, newPresence); }); this.map(leaves, (key, leftPresence) => { - let currentPresence = state[key]; + const currentPresence = state[key]; if (!currentPresence) { return; } - let refsToRemove = leftPresence.metas.map((m) => m.phx_ref); + const refsToRemove = leftPresence.metas.map((m) => m.phx_ref); currentPresence.metas = currentPresence.metas.filter((p) => { return refsToRemove.indexOf(p.phx_ref) < 0; }); @@ -898,11 +917,6 @@ var Presence = class { } /** * Returns the array of presences, with selected metadata. - * - * @param {Object} presences - * @param {Function} chooser - * - * @returns {Presence} */ static list(presences, chooser) { if (!chooser) { @@ -923,8 +937,8 @@ var Presence = class { } }; -// js/phoenix/serializer.js -var serializer_default = { +// dist/phoenix/serializer.js +var Serializer = { HEADER_LENGTH: 1, META_LENGTH: 4, KINDS: { push: 0, reply: 1, broadcast: 2 }, @@ -932,7 +946,7 @@ var serializer_default = { if (msg.payload.constructor === ArrayBuffer) { return callback(this.binaryEncode(msg)); } else { - let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; + const payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; return callback(JSON.stringify(payload)); } }, @@ -940,16 +954,16 @@ var serializer_default = { if (rawPayload.constructor === ArrayBuffer) { return callback(this.binaryDecode(rawPayload)); } else { - let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); + const [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); return callback({ join_ref, ref, topic, event, payload }); } }, // private binaryEncode(message) { - let { join_ref, ref, event, topic, payload } = message; - let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length; - let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); - let view = new DataView(header); + const { join_ref, ref, event, topic, payload } = message; + const metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length; + const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); + const view = new DataView(header); let offset = 0; view.setUint8(offset++, this.KINDS.push); view.setUint8(offset++, join_ref.length); @@ -960,15 +974,15 @@ var serializer_default = { Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0))); - var combined = new Uint8Array(header.byteLength + payload.byteLength); + const combined = new Uint8Array(header.byteLength + payload.byteLength); combined.set(new Uint8Array(header), 0); combined.set(new Uint8Array(payload), header.byteLength); return combined.buffer; }, binaryDecode(buffer) { - let view = new DataView(buffer); - let kind = view.getUint8(0); - let decoder = new TextDecoder(); + const view = new DataView(buffer); + const kind = view.getUint8(0); + const decoder = new TextDecoder(); switch (kind) { case this.KINDS.push: return this.decodePush(buffer, view, decoder); @@ -976,56 +990,78 @@ var serializer_default = { return this.decodeReply(buffer, view, decoder); case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder); + default: + throw new Error(`Unknown message kind: ${kind}`); } }, decodePush(buffer, view, decoder) { - let joinRefSize = view.getUint8(1); - let topicSize = view.getUint8(2); - let eventSize = view.getUint8(3); + const joinRefSize = view.getUint8(1); + const topicSize = view.getUint8(2); + const eventSize = view.getUint8(3); let offset = this.HEADER_LENGTH + this.META_LENGTH - 1; - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - return { join_ref: joinRef, ref: null, topic, event, payload: data }; + const data = buffer.slice(offset, buffer.byteLength); + return { + join_ref: joinRef, + ref: null, + topic, + event, + payload: data + }; }, decodeReply(buffer, view, decoder) { - let joinRefSize = view.getUint8(1); - let refSize = view.getUint8(2); - let topicSize = view.getUint8(3); - let eventSize = view.getUint8(4); + const joinRefSize = view.getUint8(1); + const refSize = view.getUint8(2); + const topicSize = view.getUint8(3); + const eventSize = view.getUint8(4); let offset = this.HEADER_LENGTH + this.META_LENGTH; - let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); + const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; - let ref = decoder.decode(buffer.slice(offset, offset + refSize)); + const ref = decoder.decode(buffer.slice(offset, offset + refSize)); offset = offset + refSize; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - let payload = { status: event, response: data }; - return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload }; + const data = buffer.slice(offset, buffer.byteLength); + const payload = { status: event, response: data }; + return { + join_ref: joinRef, + ref, + topic, + event: CHANNEL_EVENTS.reply, + payload + }; }, decodeBroadcast(buffer, view, decoder) { - let topicSize = view.getUint8(1); - let eventSize = view.getUint8(2); + const topicSize = view.getUint8(1); + const eventSize = view.getUint8(2); let offset = this.HEADER_LENGTH + 2; - let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); + const topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; - let event = decoder.decode(buffer.slice(offset, offset + eventSize)); + const event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; - let data = buffer.slice(offset, buffer.byteLength); - return { join_ref: null, ref: null, topic, event, payload: data }; + const data = buffer.slice(offset, buffer.byteLength); + return { + join_ref: null, + ref: null, + topic, + event, + payload: data + }; } }; +var serializer_default = Serializer; -// js/phoenix/socket.js +// dist/phoenix/socket.js var Socket = class { constructor(endPoint, opts = {}) { + var _a; this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] }; this.channels = []; this.sendBuffer = []; @@ -1033,7 +1069,7 @@ var Socket = class { this.timeout = opts.timeout || DEFAULT_TIMEOUT; this.transport = opts.transport || global.WebSocket || LongPoll; this.primaryPassedHealthCheck = false; - this.longPollFallbackMs = opts.longPollFallbackMs; + this.longPollFallbackMs = (_a = opts.longPollFallbackMs) !== null && _a !== void 0 ? _a : null; this.fallbackTimer = null; this.sessionStore = opts.sessionStorage || global && global.sessionStorage; this.establishedConnections = 0; @@ -1043,6 +1079,7 @@ var Socket = class { this.disconnecting = false; this.binaryType = opts.binaryType || "arraybuffer"; this.connectClock = 1; + this.conn = null; if (this.transport !== LongPoll) { this.encode = opts.encode || this.defaultEncoder; this.decode = opts.decode || this.defaultDecoder; @@ -1135,10 +1172,7 @@ var Socket = class { * @returns {string} */ endPointURL() { - let uri = Ajax.appendParams( - Ajax.appendParams(this.endPoint, this.params()), - { vsn: this.vsn } - ); + const uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params()), { vsn: this.vsn }); if (uri.charAt(0) !== "/") { return uri; } @@ -1211,7 +1245,7 @@ var Socket = class { * @param {Function} callback */ onOpen(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.open.push([ref, callback]); return ref; } @@ -1220,7 +1254,7 @@ var Socket = class { * @param {Function} callback */ onClose(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.close.push([ref, callback]); return ref; } @@ -1232,7 +1266,7 @@ var Socket = class { * @param {Function} callback */ onError(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.error.push([ref, callback]); return ref; } @@ -1241,7 +1275,7 @@ var Socket = class { * @param {Function} callback */ onMessage(callback) { - let ref = this.makeRef(); + const ref = this.makeRef(); this.stateChangeCallbacks.message.push([ref, callback]); return ref; } @@ -1255,10 +1289,10 @@ var Socket = class { if (!this.isConnected()) { return false; } - let ref = this.makeRef(); - let startTime = Date.now(); + const ref = this.makeRef(); + const startTime = Date.now(); this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref }); - let onMsgRef = this.onMessage((msg) => { + const onMsgRef = this.onMessage((msg) => { if (msg.ref === ref) { this.off([onMsgRef]); callback(Date.now() - startTime); @@ -1267,6 +1301,7 @@ var Socket = class { return true; } /** + * @internal * @private */ transportConnect() { @@ -1274,7 +1309,10 @@ var Socket = class { this.closeWasClean = false; let protocols = void 0; if (this.authToken) { - protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`]; + protocols = [ + "phoenix", + `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}` + ]; } this.conn = new this.transport(this.endPointURL(), protocols); this.conn.binaryType = this.binaryType; @@ -1294,8 +1332,8 @@ var Socket = class { clearTimeout(this.fallbackTimer); let established = false; let primaryTransport = true; - let openRef, errorRef; - let fallback = (reason) => { + let openRef; + const fallback = (reason) => { this.log("transport", `falling back to ${fallbackTransport.name}...`, reason); this.off([openRef, errorRef]); primaryTransport = false; @@ -1306,7 +1344,7 @@ var Socket = class { return fallback("memorized"); } this.fallbackTimer = setTimeout(fallback, fallbackThreshold); - errorRef = this.onError((reason) => { + const errorRef = this.onError((reason) => { this.log("transport", "error", reason); if (primaryTransport && !established) { clearTimeout(this.fallbackTimer); @@ -1335,6 +1373,10 @@ var Socket = class { clearTimeout(this.heartbeatTimer); clearTimeout(this.heartbeatTimeoutTimer); } + /** + * @internal + * @private + */ onConnOpen() { if (this.hasLogger()) this.log("transport", `${this.transport.name} connected to ${this.endPointURL()}`); @@ -1346,9 +1388,6 @@ var Socket = class { this.resetHeartbeat(); this.stateChangeCallbacks.open.forEach(([, callback]) => callback()); } - /** - * @private - */ heartbeatTimeout() { if (this.pendingHeartbeatRef) { this.pendingHeartbeatRef = null; @@ -1372,7 +1411,7 @@ var Socket = class { if (!this.conn) { return callback && callback(); } - let connectClock = this.connectClock; + const connectClock = this.connectClock; this.waitForBufferDone(() => { if (connectClock !== this.connectClock) { return; @@ -1421,8 +1460,12 @@ var Socket = class { this.waitForSocketClosed(callback, tries + 1); }, 150 * tries); } + /** + * @internal + * @private + */ onConnClose(event) { - let closeCode = event && event.code; + const closeCode = event && event.code; if (this.hasLogger()) this.log("transport", "close", event); this.triggerChanError(); @@ -1433,13 +1476,14 @@ var Socket = class { this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event)); } /** + * @internal * @private */ onConnError(error) { if (this.hasLogger()) - this.log("transport", error); - let transportBefore = this.transport; - let establishedBefore = this.establishedConnections; + this.log("transport", "error", error); + const transportBefore = this.transport; + const establishedBefore = this.establishedConnections; this.stateChangeCallbacks.error.forEach(([, callback]) => { callback(error, transportBefore, establishedBefore); }); @@ -1447,13 +1491,10 @@ var Socket = class { this.triggerChanError(); } } - /** - * @private - */ triggerChanError() { this.channels.forEach((channel) => { if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) { - channel.trigger(CHANNEL_EVENTS.error); + channel.trigger(CHANNEL_EVENTS.error, {}); } }); } @@ -1479,6 +1520,7 @@ var Socket = class { return this.connectionState() === "open"; } /** + * @internal * @private * * @param {Channel} @@ -1494,11 +1536,13 @@ var Socket = class { * `onOpen`, `onClose`, `onError,` and `onMessage` */ off(refs) { - for (let key in this.stateChangeCallbacks) { - this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => { - return refs.indexOf(ref) === -1; - }); - } + const filter = (callbacks) => callbacks.filter(([ref]) => refs.indexOf(ref) === -1); + this.stateChangeCallbacks = { + open: filter(this.stateChangeCallbacks.open), + close: filter(this.stateChangeCallbacks.close), + error: filter(this.stateChangeCallbacks.error), + message: filter(this.stateChangeCallbacks.message) + }; } /** * Initiates a new channel for the given topic @@ -1508,7 +1552,7 @@ var Socket = class { * @returns {Channel} */ channel(topic, chanParams = {}) { - let chan = new Channel(topic, chanParams, this); + const chan = new Channel(topic, chanParams, this); this.channels.push(chan); return chan; } @@ -1517,7 +1561,7 @@ var Socket = class { */ push(data) { if (this.hasLogger()) { - let { topic, event, payload, ref, join_ref } = data; + const { topic, event, payload, ref, join_ref } = data; this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload); } if (this.isConnected()) { @@ -1531,7 +1575,7 @@ var Socket = class { * @returns {string} */ makeRef() { - let newRef = this.ref + 1; + const newRef = this.ref + 1; if (newRef === this.ref) { this.ref = 0; } else { @@ -1539,23 +1583,40 @@ var Socket = class { } return this.ref.toString(); } + /** + * @internal + * @private + */ sendHeartbeat() { if (this.pendingHeartbeatRef && !this.isConnected()) { return; } this.pendingHeartbeatRef = this.makeRef(); - this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef }); + this.push({ + topic: "phoenix", + event: "heartbeat", + payload: {}, + ref: this.pendingHeartbeatRef + }); this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs); } + /** + * @internal + * @private + */ flushSendBuffer() { if (this.isConnected() && this.sendBuffer.length > 0) { this.sendBuffer.forEach((callback) => callback()); this.sendBuffer = []; } } + /** + * @internal + * @private + */ onConnMessage(rawMessage) { this.decode(rawMessage.data, (msg) => { - let { topic, event, payload, ref, join_ref } = msg; + const { topic, event, payload, ref, join_ref } = msg; if (ref && ref === this.pendingHeartbeatRef) { this.clearHeartbeats(); this.pendingHeartbeatRef = null; @@ -1571,13 +1632,17 @@ var Socket = class { channel.trigger(event, payload, ref, join_ref); } for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) { - let [, callback] = this.stateChangeCallbacks.message[i]; + const [, callback] = this.stateChangeCallbacks.message[i]; callback(msg); } }); } + /** + * @internal + * @private + */ leaveOpenTopic(topic) { - let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining())); + const dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining())); if (dupChannel) { if (this.hasLogger()) this.log("transport", `leaving duplicate topic "${topic}"`); diff --git a/priv/static/phoenix.mjs.map b/priv/static/phoenix.mjs.map index 286be04140..65c3ef25a7 100644 --- a/priv/static/phoenix.mjs.map +++ b/priv/static/phoenix.mjs.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../../assets/js/phoenix/utils.js", "../../assets/js/phoenix/constants.js", "../../assets/js/phoenix/push.js", "../../assets/js/phoenix/timer.js", "../../assets/js/phoenix/channel.js", "../../assets/js/phoenix/ajax.js", "../../assets/js/phoenix/longpoll.js", "../../assets/js/phoenix/presence.js", "../../assets/js/phoenix/serializer.js", "../../assets/js/phoenix/socket.js"], - "sourcesContent": ["// wraps value in closure or returns closure\nexport let closure = (value) => {\n if(typeof value === \"function\"){\n return value\n } else {\n let closure = function (){ return value }\n return closure\n }\n}\n", "export const globalSelf = typeof self !== \"undefined\" ? self : null\nexport const phxWindow = typeof window !== \"undefined\" ? window : null\nexport const global = globalSelf || phxWindow || globalThis\nexport const DEFAULT_VSN = \"2.0.0\"\nexport const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}\nexport const DEFAULT_TIMEOUT = 10000\nexport const WS_CLOSE_NORMAL = 1000\nexport const CHANNEL_STATES = {\n closed: \"closed\",\n errored: \"errored\",\n joined: \"joined\",\n joining: \"joining\",\n leaving: \"leaving\",\n}\nexport const CHANNEL_EVENTS = {\n close: \"phx_close\",\n error: \"phx_error\",\n join: \"phx_join\",\n reply: \"phx_reply\",\n leave: \"phx_leave\"\n}\n\nexport const TRANSPORTS = {\n longpoll: \"longpoll\",\n websocket: \"websocket\"\n}\nexport const XHR_STATES = {\n complete: 4\n}\nexport const AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\"\n", "/**\n * Initializes the Push\n * @param {Channel} channel - The Channel\n * @param {string} event - The event, for example `\"phx_join\"`\n * @param {Object} payload - The payload, for example `{user_id: 123}`\n * @param {number} timeout - The push timeout in milliseconds\n */\nexport default class Push {\n constructor(channel, event, payload, timeout){\n this.channel = channel\n this.event = event\n this.payload = payload || function (){ return {} }\n this.receivedResp = null\n this.timeout = timeout\n this.timeoutTimer = null\n this.recHooks = []\n this.sent = false\n }\n\n /**\n *\n * @param {number} timeout\n */\n resend(timeout){\n this.timeout = timeout\n this.reset()\n this.send()\n }\n\n /**\n *\n */\n send(){\n if(this.hasReceived(\"timeout\")){ return }\n this.startTimeout()\n this.sent = true\n this.channel.socket.push({\n topic: this.channel.topic,\n event: this.event,\n payload: this.payload(),\n ref: this.ref,\n join_ref: this.channel.joinRef()\n })\n }\n\n /**\n *\n * @param {*} status\n * @param {*} callback\n */\n receive(status, callback){\n if(this.hasReceived(status)){\n callback(this.receivedResp.response)\n }\n\n this.recHooks.push({status, callback})\n return this\n }\n\n /**\n * @private\n */\n reset(){\n this.cancelRefEvent()\n this.ref = null\n this.refEvent = null\n this.receivedResp = null\n this.sent = false\n }\n\n /**\n * @private\n */\n matchReceive({status, response, _ref}){\n this.recHooks.filter(h => h.status === status)\n .forEach(h => h.callback(response))\n }\n\n /**\n * @private\n */\n cancelRefEvent(){\n if(!this.refEvent){ return }\n this.channel.off(this.refEvent)\n }\n\n /**\n * @private\n */\n cancelTimeout(){\n clearTimeout(this.timeoutTimer)\n this.timeoutTimer = null\n }\n\n /**\n * @private\n */\n startTimeout(){\n if(this.timeoutTimer){ this.cancelTimeout() }\n this.ref = this.channel.socket.makeRef()\n this.refEvent = this.channel.replyEventName(this.ref)\n\n this.channel.on(this.refEvent, payload => {\n this.cancelRefEvent()\n this.cancelTimeout()\n this.receivedResp = payload\n this.matchReceive(payload)\n })\n\n this.timeoutTimer = setTimeout(() => {\n this.trigger(\"timeout\", {})\n }, this.timeout)\n }\n\n /**\n * @private\n */\n hasReceived(status){\n return this.receivedResp && this.receivedResp.status === status\n }\n\n /**\n * @private\n */\n trigger(status, response){\n this.channel.trigger(this.refEvent, {status, response})\n }\n}\n", "/**\n *\n * Creates a timer that accepts a `timerCalc` function to perform\n * calculated timeout retries, such as exponential backoff.\n *\n * @example\n * let reconnectTimer = new Timer(() => this.connect(), function(tries){\n * return [1000, 5000, 10000][tries - 1] || 10000\n * })\n * reconnectTimer.scheduleTimeout() // fires after 1000\n * reconnectTimer.scheduleTimeout() // fires after 5000\n * reconnectTimer.reset()\n * reconnectTimer.scheduleTimeout() // fires after 1000\n *\n * @param {Function} callback\n * @param {Function} timerCalc\n */\nexport default class Timer {\n constructor(callback, timerCalc){\n this.callback = callback\n this.timerCalc = timerCalc\n this.timer = null\n this.tries = 0\n }\n\n reset(){\n this.tries = 0\n clearTimeout(this.timer)\n }\n\n /**\n * Cancels any previous scheduleTimeout and schedules callback\n */\n scheduleTimeout(){\n clearTimeout(this.timer)\n\n this.timer = setTimeout(() => {\n this.tries = this.tries + 1\n this.callback()\n }, this.timerCalc(this.tries + 1))\n }\n}\n", "import {closure} from \"./utils\"\nimport {\n CHANNEL_EVENTS,\n CHANNEL_STATES,\n} from \"./constants\"\n\nimport Push from \"./push\"\nimport Timer from \"./timer\"\n\n/**\n *\n * @param {string} topic\n * @param {(Object|function)} params\n * @param {Socket} socket\n */\nexport default class Channel {\n constructor(topic, params, socket){\n this.state = CHANNEL_STATES.closed\n this.topic = topic\n this.params = closure(params || {})\n this.socket = socket\n this.bindings = []\n this.bindingRef = 0\n this.timeout = this.socket.timeout\n this.joinedOnce = false\n this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout)\n this.pushBuffer = []\n this.stateChangeRefs = []\n\n this.rejoinTimer = new Timer(() => {\n if(this.socket.isConnected()){ this.rejoin() }\n }, this.socket.rejoinAfterMs)\n this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()))\n this.stateChangeRefs.push(this.socket.onOpen(() => {\n this.rejoinTimer.reset()\n if(this.isErrored()){ this.rejoin() }\n })\n )\n this.joinPush.receive(\"ok\", () => {\n this.state = CHANNEL_STATES.joined\n this.rejoinTimer.reset()\n this.pushBuffer.forEach(pushEvent => pushEvent.send())\n this.pushBuffer = []\n })\n this.joinPush.receive(\"error\", () => {\n this.state = CHANNEL_STATES.errored\n if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n })\n this.onClose(() => {\n this.rejoinTimer.reset()\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`)\n this.state = CHANNEL_STATES.closed\n this.socket.remove(this)\n })\n this.onError(reason => {\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `error ${this.topic}`, reason)\n if(this.isJoining()){ this.joinPush.reset() }\n this.state = CHANNEL_STATES.errored\n if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n })\n this.joinPush.receive(\"timeout\", () => {\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout)\n let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout)\n leavePush.send()\n this.state = CHANNEL_STATES.errored\n this.joinPush.reset()\n if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n })\n this.on(CHANNEL_EVENTS.reply, (payload, ref) => {\n this.trigger(this.replyEventName(ref), payload)\n })\n }\n\n /**\n * Join the channel\n * @param {integer} timeout\n * @returns {Push}\n */\n join(timeout = this.timeout){\n if(this.joinedOnce){\n throw new Error(\"tried to join multiple times. 'join' can only be called a single time per channel instance\")\n } else {\n this.timeout = timeout\n this.joinedOnce = true\n this.rejoin()\n return this.joinPush\n }\n }\n\n /**\n * Hook into channel close\n * @param {Function} callback\n */\n onClose(callback){\n this.on(CHANNEL_EVENTS.close, callback)\n }\n\n /**\n * Hook into channel errors\n * @param {Function} callback\n */\n onError(callback){\n return this.on(CHANNEL_EVENTS.error, reason => callback(reason))\n }\n\n /**\n * Subscribes on channel events\n *\n * Subscription returns a ref counter, which can be used later to\n * unsubscribe the exact event listener\n *\n * @example\n * const ref1 = channel.on(\"event\", do_stuff)\n * const ref2 = channel.on(\"event\", do_other_stuff)\n * channel.off(\"event\", ref1)\n * // Since unsubscription, do_stuff won't fire,\n * // while do_other_stuff will keep firing on the \"event\"\n *\n * @param {string} event\n * @param {Function} callback\n * @returns {integer} ref\n */\n on(event, callback){\n let ref = this.bindingRef++\n this.bindings.push({event, ref, callback})\n return ref\n }\n\n /**\n * Unsubscribes off of channel events\n *\n * Use the ref returned from a channel.on() to unsubscribe one\n * handler, or pass nothing for the ref to unsubscribe all\n * handlers for the given event.\n *\n * @example\n * // Unsubscribe the do_stuff handler\n * const ref1 = channel.on(\"event\", do_stuff)\n * channel.off(\"event\", ref1)\n *\n * // Unsubscribe all handlers from event\n * channel.off(\"event\")\n *\n * @param {string} event\n * @param {integer} ref\n */\n off(event, ref){\n this.bindings = this.bindings.filter((bind) => {\n return !(bind.event === event && (typeof ref === \"undefined\" || ref === bind.ref))\n })\n }\n\n /**\n * @private\n */\n canPush(){ return this.socket.isConnected() && this.isJoined() }\n\n /**\n * Sends a message `event` to phoenix with the payload `payload`.\n * Phoenix receives this in the `handle_in(event, payload, socket)`\n * function. if phoenix replies or it times out (default 10000ms),\n * then optionally the reply can be received.\n *\n * @example\n * channel.push(\"event\")\n * .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n * .receive(\"error\", err => console.log(\"phoenix errored\", err))\n * .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n * @param {string} event\n * @param {Object} payload\n * @param {number} [timeout]\n * @returns {Push}\n */\n push(event, payload, timeout = this.timeout){\n payload = payload || {}\n if(!this.joinedOnce){\n throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`)\n }\n let pushEvent = new Push(this, event, function (){ return payload }, timeout)\n if(this.canPush()){\n pushEvent.send()\n } else {\n pushEvent.startTimeout()\n this.pushBuffer.push(pushEvent)\n }\n\n return pushEvent\n }\n\n /** Leaves the channel\n *\n * Unsubscribes from server events, and\n * instructs channel to terminate on server\n *\n * Triggers onClose() hooks\n *\n * To receive leave acknowledgements, use the `receive`\n * hook to bind to the server ack, ie:\n *\n * @example\n * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n *\n * @param {integer} timeout\n * @returns {Push}\n */\n leave(timeout = this.timeout){\n this.rejoinTimer.reset()\n this.joinPush.cancelTimeout()\n\n this.state = CHANNEL_STATES.leaving\n let onClose = () => {\n if(this.socket.hasLogger()) this.socket.log(\"channel\", `leave ${this.topic}`)\n this.trigger(CHANNEL_EVENTS.close, \"leave\")\n }\n let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout)\n leavePush.receive(\"ok\", () => onClose())\n .receive(\"timeout\", () => onClose())\n leavePush.send()\n if(!this.canPush()){ leavePush.trigger(\"ok\", {}) }\n\n return leavePush\n }\n\n /**\n * Overridable message hook\n *\n * Receives all events for specialized message handling\n * before dispatching to the channel callbacks.\n *\n * Must return the payload, modified or unmodified\n * @param {string} event\n * @param {Object} payload\n * @param {integer} ref\n * @returns {Object}\n */\n onMessage(_event, payload, _ref){ return payload }\n\n /**\n * @private\n */\n isMember(topic, event, payload, joinRef){\n if(this.topic !== topic){ return false }\n\n if(joinRef && joinRef !== this.joinRef()){\n if(this.socket.hasLogger()) this.socket.log(\"channel\", \"dropping outdated message\", {topic, event, payload, joinRef})\n return false\n } else {\n return true\n }\n }\n\n /**\n * @private\n */\n joinRef(){ return this.joinPush.ref }\n\n /**\n * @private\n */\n rejoin(timeout = this.timeout){\n if(this.isLeaving()){ return }\n this.socket.leaveOpenTopic(this.topic)\n this.state = CHANNEL_STATES.joining\n this.joinPush.resend(timeout)\n }\n\n /**\n * @private\n */\n trigger(event, payload, ref, joinRef){\n let handledPayload = this.onMessage(event, payload, ref, joinRef)\n if(payload && !handledPayload){ throw new Error(\"channel onMessage callbacks must return the payload, modified or unmodified\") }\n\n let eventBindings = this.bindings.filter(bind => bind.event === event)\n\n for(let i = 0; i < eventBindings.length; i++){\n let bind = eventBindings[i]\n bind.callback(handledPayload, ref, joinRef || this.joinRef())\n }\n }\n\n /**\n * @private\n */\n replyEventName(ref){ return `chan_reply_${ref}` }\n\n /**\n * @private\n */\n isClosed(){ return this.state === CHANNEL_STATES.closed }\n\n /**\n * @private\n */\n isErrored(){ return this.state === CHANNEL_STATES.errored }\n\n /**\n * @private\n */\n isJoined(){ return this.state === CHANNEL_STATES.joined }\n\n /**\n * @private\n */\n isJoining(){ return this.state === CHANNEL_STATES.joining }\n\n /**\n * @private\n */\n isLeaving(){ return this.state === CHANNEL_STATES.leaving }\n}\n", "import {\n global,\n XHR_STATES\n} from \"./constants\"\n\nexport default class Ajax {\n\n static request(method, endPoint, headers, body, timeout, ontimeout, callback){\n if(global.XDomainRequest){\n let req = new global.XDomainRequest() // IE8, IE9\n return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback)\n } else if(global.XMLHttpRequest){\n let req = new global.XMLHttpRequest() // IE7+, Firefox, Chrome, Opera, Safari\n return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback)\n } else if(global.fetch && global.AbortController){\n // Fetch with AbortController for modern browsers\n return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback)\n } else {\n throw new Error(\"No suitable XMLHttpRequest implementation found\")\n }\n }\n\n static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback){\n let options = {\n method,\n headers,\n body,\n }\n let controller = null\n if(timeout){\n controller = new AbortController()\n const _timeoutId = setTimeout(() => controller.abort(), timeout)\n options.signal = controller.signal\n }\n global.fetch(endPoint, options)\n .then(response => response.text())\n .then(data => this.parseJSON(data))\n .then(data => callback && callback(data))\n .catch(err => {\n if(err.name === \"AbortError\" && ontimeout){\n ontimeout()\n } else {\n callback && callback(null)\n }\n })\n return controller\n }\n\n static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback){\n req.timeout = timeout\n req.open(method, endPoint)\n req.onload = () => {\n let response = this.parseJSON(req.responseText)\n callback && callback(response)\n }\n if(ontimeout){ req.ontimeout = ontimeout }\n\n // Work around bug in IE9 that requires an attached onprogress handler\n req.onprogress = () => { }\n\n req.send(body)\n return req\n }\n\n static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback){\n req.open(method, endPoint, true)\n req.timeout = timeout\n for(let [key, value] of Object.entries(headers)){\n req.setRequestHeader(key, value)\n }\n req.onerror = () => callback && callback(null)\n req.onreadystatechange = () => {\n if(req.readyState === XHR_STATES.complete && callback){\n let response = this.parseJSON(req.responseText)\n callback(response)\n }\n }\n if(ontimeout){ req.ontimeout = ontimeout }\n\n req.send(body)\n return req\n }\n\n static parseJSON(resp){\n if(!resp || resp === \"\"){ return null }\n\n try {\n return JSON.parse(resp)\n } catch {\n console && console.log(\"failed to parse JSON response\", resp)\n return null\n }\n }\n\n static serialize(obj, parentKey){\n let queryStr = []\n for(var key in obj){\n if(!Object.prototype.hasOwnProperty.call(obj, key)){ continue }\n let paramKey = parentKey ? `${parentKey}[${key}]` : key\n let paramVal = obj[key]\n if(typeof paramVal === \"object\"){\n queryStr.push(this.serialize(paramVal, paramKey))\n } else {\n queryStr.push(encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal))\n }\n }\n return queryStr.join(\"&\")\n }\n\n static appendParams(url, params){\n if(Object.keys(params).length === 0){ return url }\n\n let prefix = url.match(/\\?/) ? \"&\" : \"?\"\n return `${url}${prefix}${this.serialize(params)}`\n }\n}\n", "import {\n SOCKET_STATES,\n TRANSPORTS,\n AUTH_TOKEN_PREFIX\n} from \"./constants\"\n\nimport Ajax from \"./ajax\"\n\nlet arrayBufferToBase64 = (buffer) => {\n let binary = \"\"\n let bytes = new Uint8Array(buffer)\n let len = bytes.byteLength\n for(let i = 0; i < len; i++){ binary += String.fromCharCode(bytes[i]) }\n return btoa(binary)\n}\n\nexport default class LongPoll {\n\n constructor(endPoint, protocols){\n // we only support subprotocols for authToken\n // [\"phoenix\", \"base64url.bearer.phx.BASE64_ENCODED_TOKEN\"]\n if(protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)){\n this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length))\n }\n this.endPoint = null\n this.token = null\n this.skipHeartbeat = true\n this.reqs = new Set()\n this.awaitingBatchAck = false\n this.currentBatch = null\n this.currentBatchTimer = null\n this.batchBuffer = []\n this.onopen = function (){ } // noop\n this.onerror = function (){ } // noop\n this.onmessage = function (){ } // noop\n this.onclose = function (){ } // noop\n this.pollEndpoint = this.normalizeEndpoint(endPoint)\n this.readyState = SOCKET_STATES.connecting\n // we must wait for the caller to finish setting up our callbacks and timeout properties\n setTimeout(() => this.poll(), 0)\n }\n\n normalizeEndpoint(endPoint){\n return (endPoint\n .replace(\"ws://\", \"http://\")\n .replace(\"wss://\", \"https://\")\n .replace(new RegExp(\"(.*)\\/\" + TRANSPORTS.websocket), \"$1/\" + TRANSPORTS.longpoll))\n }\n\n endpointURL(){\n return Ajax.appendParams(this.pollEndpoint, {token: this.token})\n }\n\n closeAndRetry(code, reason, wasClean){\n this.close(code, reason, wasClean)\n this.readyState = SOCKET_STATES.connecting\n }\n\n ontimeout(){\n this.onerror(\"timeout\")\n this.closeAndRetry(1005, \"timeout\", false)\n }\n\n isActive(){ return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting }\n\n poll(){\n const headers = {\"Accept\": \"application/json\"}\n if(this.authToken){\n headers[\"X-Phoenix-AuthToken\"] = this.authToken\n }\n this.ajax(\"GET\", headers, null, () => this.ontimeout(), resp => {\n if(resp){\n var {status, token, messages} = resp\n this.token = token\n } else {\n status = 0\n }\n\n switch(status){\n case 200:\n messages.forEach(msg => {\n // Tasks are what things like event handlers, setTimeout callbacks,\n // promise resolves and more are run within.\n // In modern browsers, there are two different kinds of tasks,\n // microtasks and macrotasks.\n // Microtasks are mainly used for Promises, while macrotasks are\n // used for everything else.\n // Microtasks always have priority over macrotasks. If the JS engine\n // is looking for a task to run, it will always try to empty the\n // microtask queue before attempting to run anything from the\n // macrotask queue.\n //\n // For the WebSocket transport, messages always arrive in their own\n // event. This means that if any promises are resolved from within,\n // their callbacks will always finish execution by the time the\n // next message event handler is run.\n //\n // In order to emulate this behaviour, we need to make sure each\n // onmessage handler is run within its own macrotask.\n setTimeout(() => this.onmessage({data: msg}), 0)\n })\n this.poll()\n break\n case 204:\n this.poll()\n break\n case 410:\n this.readyState = SOCKET_STATES.open\n this.onopen({})\n this.poll()\n break\n case 403:\n this.onerror(403)\n this.close(1008, \"forbidden\", false)\n break\n case 0:\n case 500:\n this.onerror(500)\n this.closeAndRetry(1011, \"internal server error\", 500)\n break\n default: throw new Error(`unhandled poll status ${status}`)\n }\n })\n }\n\n // we collect all pushes within the current event loop by\n // setTimeout 0, which optimizes back-to-back procedural\n // pushes against an empty buffer\n\n send(body){\n if(typeof(body) !== \"string\"){ body = arrayBufferToBase64(body) }\n if(this.currentBatch){\n this.currentBatch.push(body)\n } else if(this.awaitingBatchAck){\n this.batchBuffer.push(body)\n } else {\n this.currentBatch = [body]\n this.currentBatchTimer = setTimeout(() => {\n this.batchSend(this.currentBatch)\n this.currentBatch = null\n }, 0)\n }\n }\n\n batchSend(messages){\n this.awaitingBatchAck = true\n this.ajax(\"POST\", {\"Content-Type\": \"application/x-ndjson\"}, messages.join(\"\\n\"), () => this.onerror(\"timeout\"), resp => {\n this.awaitingBatchAck = false\n if(!resp || resp.status !== 200){\n this.onerror(resp && resp.status)\n this.closeAndRetry(1011, \"internal server error\", false)\n } else if(this.batchBuffer.length > 0){\n this.batchSend(this.batchBuffer)\n this.batchBuffer = []\n }\n })\n }\n\n close(code, reason, wasClean){\n for(let req of this.reqs){ req.abort() }\n this.readyState = SOCKET_STATES.closed\n let opts = Object.assign({code: 1000, reason: undefined, wasClean: true}, {code, reason, wasClean})\n this.batchBuffer = []\n clearTimeout(this.currentBatchTimer)\n this.currentBatchTimer = null\n if(typeof(CloseEvent) !== \"undefined\"){\n this.onclose(new CloseEvent(\"close\", opts))\n } else {\n this.onclose(opts)\n }\n }\n\n ajax(method, headers, body, onCallerTimeout, callback){\n let req\n let ontimeout = () => {\n this.reqs.delete(req)\n onCallerTimeout()\n }\n req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, resp => {\n this.reqs.delete(req)\n if(this.isActive()){ callback(resp) }\n })\n this.reqs.add(req)\n }\n}\n", "/**\n * Initializes the Presence\n * @param {Channel} channel - The Channel\n * @param {Object} opts - The options,\n * for example `{events: {state: \"state\", diff: \"diff\"}}`\n */\nexport default class Presence {\n\n constructor(channel, opts = {}){\n let events = opts.events || {state: \"presence_state\", diff: \"presence_diff\"}\n this.state = {}\n this.pendingDiffs = []\n this.channel = channel\n this.joinRef = null\n this.caller = {\n onJoin: function (){ },\n onLeave: function (){ },\n onSync: function (){ }\n }\n\n this.channel.on(events.state, newState => {\n let {onJoin, onLeave, onSync} = this.caller\n\n this.joinRef = this.channel.joinRef()\n this.state = Presence.syncState(this.state, newState, onJoin, onLeave)\n\n this.pendingDiffs.forEach(diff => {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)\n })\n this.pendingDiffs = []\n onSync()\n })\n\n this.channel.on(events.diff, diff => {\n let {onJoin, onLeave, onSync} = this.caller\n\n if(this.inPendingSyncState()){\n this.pendingDiffs.push(diff)\n } else {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)\n onSync()\n }\n })\n }\n\n onJoin(callback){ this.caller.onJoin = callback }\n\n onLeave(callback){ this.caller.onLeave = callback }\n\n onSync(callback){ this.caller.onSync = callback }\n\n list(by){ return Presence.list(this.state, by) }\n\n inPendingSyncState(){\n return !this.joinRef || (this.joinRef !== this.channel.joinRef())\n }\n\n // lower-level public static API\n\n /**\n * Used to sync the list of presences on the server\n * with the client's state. An optional `onJoin` and `onLeave` callback can\n * be provided to react to changes in the client's local presences across\n * disconnects and reconnects with the server.\n *\n * @returns {Presence}\n */\n static syncState(currentState, newState, onJoin, onLeave){\n let state = this.clone(currentState)\n let joins = {}\n let leaves = {}\n\n this.map(state, (key, presence) => {\n if(!newState[key]){\n leaves[key] = presence\n }\n })\n this.map(newState, (key, newPresence) => {\n let currentPresence = state[key]\n if(currentPresence){\n let newRefs = newPresence.metas.map(m => m.phx_ref)\n let curRefs = currentPresence.metas.map(m => m.phx_ref)\n let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0)\n let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0)\n if(joinedMetas.length > 0){\n joins[key] = newPresence\n joins[key].metas = joinedMetas\n }\n if(leftMetas.length > 0){\n leaves[key] = this.clone(currentPresence)\n leaves[key].metas = leftMetas\n }\n } else {\n joins[key] = newPresence\n }\n })\n return this.syncDiff(state, {joins: joins, leaves: leaves}, onJoin, onLeave)\n }\n\n /**\n *\n * Used to sync a diff of presence join and leave\n * events from the server, as they happen. Like `syncState`, `syncDiff`\n * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n * joining or leaving from a device.\n *\n * @returns {Presence}\n */\n static syncDiff(state, diff, onJoin, onLeave){\n let {joins, leaves} = this.clone(diff)\n if(!onJoin){ onJoin = function (){ } }\n if(!onLeave){ onLeave = function (){ } }\n\n this.map(joins, (key, newPresence) => {\n let currentPresence = state[key]\n state[key] = this.clone(newPresence)\n if(currentPresence){\n let joinedRefs = state[key].metas.map(m => m.phx_ref)\n let curMetas = currentPresence.metas.filter(m => joinedRefs.indexOf(m.phx_ref) < 0)\n state[key].metas.unshift(...curMetas)\n }\n onJoin(key, currentPresence, newPresence)\n })\n this.map(leaves, (key, leftPresence) => {\n let currentPresence = state[key]\n if(!currentPresence){ return }\n let refsToRemove = leftPresence.metas.map(m => m.phx_ref)\n currentPresence.metas = currentPresence.metas.filter(p => {\n return refsToRemove.indexOf(p.phx_ref) < 0\n })\n onLeave(key, currentPresence, leftPresence)\n if(currentPresence.metas.length === 0){\n delete state[key]\n }\n })\n return state\n }\n\n /**\n * Returns the array of presences, with selected metadata.\n *\n * @param {Object} presences\n * @param {Function} chooser\n *\n * @returns {Presence}\n */\n static list(presences, chooser){\n if(!chooser){ chooser = function (key, pres){ return pres } }\n\n return this.map(presences, (key, presence) => {\n return chooser(key, presence)\n })\n }\n\n // private\n\n static map(obj, func){\n return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key]))\n }\n\n static clone(obj){ return JSON.parse(JSON.stringify(obj)) }\n}\n", "/* The default serializer for encoding and decoding messages */\nimport {\n CHANNEL_EVENTS\n} from \"./constants\"\n\nexport default {\n HEADER_LENGTH: 1,\n META_LENGTH: 4,\n KINDS: {push: 0, reply: 1, broadcast: 2},\n\n encode(msg, callback){\n if(msg.payload.constructor === ArrayBuffer){\n return callback(this.binaryEncode(msg))\n } else {\n let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]\n return callback(JSON.stringify(payload))\n }\n },\n\n decode(rawPayload, callback){\n if(rawPayload.constructor === ArrayBuffer){\n return callback(this.binaryDecode(rawPayload))\n } else {\n let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload)\n return callback({join_ref, ref, topic, event, payload})\n }\n },\n\n // private\n\n binaryEncode(message){\n let {join_ref, ref, event, topic, payload} = message\n let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length\n let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength)\n let view = new DataView(header)\n let offset = 0\n\n view.setUint8(offset++, this.KINDS.push) // kind\n view.setUint8(offset++, join_ref.length)\n view.setUint8(offset++, ref.length)\n view.setUint8(offset++, topic.length)\n view.setUint8(offset++, event.length)\n Array.from(join_ref, char => view.setUint8(offset++, char.charCodeAt(0)))\n Array.from(ref, char => view.setUint8(offset++, char.charCodeAt(0)))\n Array.from(topic, char => view.setUint8(offset++, char.charCodeAt(0)))\n Array.from(event, char => view.setUint8(offset++, char.charCodeAt(0)))\n\n var combined = new Uint8Array(header.byteLength + payload.byteLength)\n combined.set(new Uint8Array(header), 0)\n combined.set(new Uint8Array(payload), header.byteLength)\n\n return combined.buffer\n },\n\n binaryDecode(buffer){\n let view = new DataView(buffer)\n let kind = view.getUint8(0)\n let decoder = new TextDecoder()\n switch(kind){\n case this.KINDS.push: return this.decodePush(buffer, view, decoder)\n case this.KINDS.reply: return this.decodeReply(buffer, view, decoder)\n case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder)\n }\n },\n\n decodePush(buffer, view, decoder){\n let joinRefSize = view.getUint8(1)\n let topicSize = view.getUint8(2)\n let eventSize = view.getUint8(3)\n let offset = this.HEADER_LENGTH + this.META_LENGTH - 1 // pushes have no ref\n let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize))\n offset = offset + joinRefSize\n let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n offset = offset + topicSize\n let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n offset = offset + eventSize\n let data = buffer.slice(offset, buffer.byteLength)\n return {join_ref: joinRef, ref: null, topic: topic, event: event, payload: data}\n },\n\n decodeReply(buffer, view, decoder){\n let joinRefSize = view.getUint8(1)\n let refSize = view.getUint8(2)\n let topicSize = view.getUint8(3)\n let eventSize = view.getUint8(4)\n let offset = this.HEADER_LENGTH + this.META_LENGTH\n let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize))\n offset = offset + joinRefSize\n let ref = decoder.decode(buffer.slice(offset, offset + refSize))\n offset = offset + refSize\n let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n offset = offset + topicSize\n let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n offset = offset + eventSize\n let data = buffer.slice(offset, buffer.byteLength)\n let payload = {status: event, response: data}\n return {join_ref: joinRef, ref: ref, topic: topic, event: CHANNEL_EVENTS.reply, payload: payload}\n },\n\n decodeBroadcast(buffer, view, decoder){\n let topicSize = view.getUint8(1)\n let eventSize = view.getUint8(2)\n let offset = this.HEADER_LENGTH + 2\n let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n offset = offset + topicSize\n let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n offset = offset + eventSize\n let data = buffer.slice(offset, buffer.byteLength)\n\n return {join_ref: null, ref: null, topic: topic, event: event, payload: data}\n }\n}\n", "import {\n global,\n phxWindow,\n CHANNEL_EVENTS,\n DEFAULT_TIMEOUT,\n DEFAULT_VSN,\n SOCKET_STATES,\n TRANSPORTS,\n WS_CLOSE_NORMAL,\n AUTH_TOKEN_PREFIX\n} from \"./constants\"\n\nimport {\n closure\n} from \"./utils\"\n\nimport Ajax from \"./ajax\"\nimport Channel from \"./channel\"\nimport LongPoll from \"./longpoll\"\nimport Serializer from \"./serializer\"\nimport Timer from \"./timer\"\n\n/** Initializes the Socket *\n *\n * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"ws://example.com/socket\"`,\n * `\"wss://example.com\"`\n * `\"/socket\"` (inherited host & protocol)\n * @param {Object} [opts] - Optional configuration\n * @param {Function} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.\n *\n * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined.\n * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`.\n *\n * @param {number} [opts.longPollFallbackMs] - The millisecond time to attempt the primary transport\n * before falling back to the LongPoll transport. Disabled by default.\n *\n * @param {boolean} [opts.debug] - When true, enables debug logging. Default false.\n *\n * @param {Function} [opts.encode] - The function to encode outgoing messages.\n *\n * Defaults to JSON encoder.\n *\n * @param {Function} [opts.decode] - The function to decode incoming messages.\n *\n * Defaults to JSON:\n *\n * ```javascript\n * (payload, callback) => callback(JSON.parse(payload))\n * ```\n *\n * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.\n *\n * Defaults `DEFAULT_TIMEOUT`\n * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message\n * @param {Function} [opts.reconnectAfterMs] - The optional function that returns the\n * socket reconnect interval, in milliseconds.\n *\n * Defaults to stepped backoff of:\n *\n * ```javascript\n * function(tries){\n * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n * }\n * ````\n *\n * @param {Function} [opts.rejoinAfterMs] - The optional function that returns the millisec\n * rejoin interval for individual channels.\n *\n * ```javascript\n * function(tries){\n * return [1000, 2000, 5000][tries - 1] || 10000\n * }\n * ````\n *\n * @param {Function} [opts.logger] - The optional function for specialized logging, ie:\n *\n * ```javascript\n * function(kind, msg, data) {\n * console.log(`${kind}: ${msg}`, data)\n * }\n * ```\n *\n * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.\n *\n * Defaults to 20s (double the server long poll timer).\n *\n * @param {(Object|function)} [opts.params] - The optional params to pass when connecting\n * @param {string} [opts.authToken] - the optional authentication token to be exposed on the server\n * under the `:auth_token` connect_info key.\n * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.\n *\n * Defaults to \"arraybuffer\"\n *\n * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect.\n *\n * Defaults to DEFAULT_VSN.\n *\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is\n * useful when Phoenix won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain channel in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] || null }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n*/\nexport default class Socket {\n constructor(endPoint, opts = {}){\n this.stateChangeCallbacks = {open: [], close: [], error: [], message: []}\n this.channels = []\n this.sendBuffer = []\n this.ref = 0\n this.timeout = opts.timeout || DEFAULT_TIMEOUT\n this.transport = opts.transport || global.WebSocket || LongPoll\n this.primaryPassedHealthCheck = false\n this.longPollFallbackMs = opts.longPollFallbackMs\n this.fallbackTimer = null\n this.sessionStore = opts.sessionStorage || (global && global.sessionStorage)\n this.establishedConnections = 0\n this.defaultEncoder = Serializer.encode.bind(Serializer)\n this.defaultDecoder = Serializer.decode.bind(Serializer)\n this.closeWasClean = false\n this.disconnecting = false\n this.binaryType = opts.binaryType || \"arraybuffer\"\n this.connectClock = 1\n if(this.transport !== LongPoll){\n this.encode = opts.encode || this.defaultEncoder\n this.decode = opts.decode || this.defaultDecoder\n } else {\n this.encode = this.defaultEncoder\n this.decode = this.defaultDecoder\n }\n let awaitingConnectionOnPageShow = null\n if(phxWindow && phxWindow.addEventListener){\n phxWindow.addEventListener(\"pagehide\", _e => {\n if(this.conn){\n this.disconnect()\n awaitingConnectionOnPageShow = this.connectClock\n }\n })\n phxWindow.addEventListener(\"pageshow\", _e => {\n if(awaitingConnectionOnPageShow === this.connectClock){\n awaitingConnectionOnPageShow = null\n this.connect()\n }\n })\n }\n this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000\n this.rejoinAfterMs = (tries) => {\n if(opts.rejoinAfterMs){\n return opts.rejoinAfterMs(tries)\n } else {\n return [1000, 2000, 5000][tries - 1] || 10000\n }\n }\n this.reconnectAfterMs = (tries) => {\n if(opts.reconnectAfterMs){\n return opts.reconnectAfterMs(tries)\n } else {\n return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n }\n }\n this.logger = opts.logger || null\n if(!this.logger && opts.debug){\n this.logger = (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }\n }\n this.longpollerTimeout = opts.longpollerTimeout || 20000\n this.params = closure(opts.params || {})\n this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`\n this.vsn = opts.vsn || DEFAULT_VSN\n this.heartbeatTimeoutTimer = null\n this.heartbeatTimer = null\n this.pendingHeartbeatRef = null\n this.reconnectTimer = new Timer(() => {\n this.teardown(() => this.connect())\n }, this.reconnectAfterMs)\n this.authToken = opts.authToken\n }\n\n /**\n * Returns the LongPoll transport reference\n */\n getLongPollTransport(){ return LongPoll }\n\n /**\n * Disconnects and replaces the active transport\n *\n * @param {Function} newTransport - The new transport class to instantiate\n *\n */\n replaceTransport(newTransport){\n this.connectClock++\n this.closeWasClean = true\n clearTimeout(this.fallbackTimer)\n this.reconnectTimer.reset()\n if(this.conn){\n this.conn.close()\n this.conn = null\n }\n this.transport = newTransport\n }\n\n /**\n * Returns the socket protocol\n *\n * @returns {string}\n */\n protocol(){ return location.protocol.match(/^https/) ? \"wss\" : \"ws\" }\n\n /**\n * The fully qualified socket url\n *\n * @returns {string}\n */\n endPointURL(){\n let uri = Ajax.appendParams(\n Ajax.appendParams(this.endPoint, this.params()), {vsn: this.vsn})\n if(uri.charAt(0) !== \"/\"){ return uri }\n if(uri.charAt(1) === \"/\"){ return `${this.protocol()}:${uri}` }\n\n return `${this.protocol()}://${location.host}${uri}`\n }\n\n /**\n * Disconnects the socket\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n *\n * @param {Function} callback - Optional callback which is called after socket is disconnected.\n * @param {integer} code - A status code for disconnection (Optional).\n * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n */\n disconnect(callback, code, reason){\n this.connectClock++\n this.disconnecting = true\n this.closeWasClean = true\n clearTimeout(this.fallbackTimer)\n this.reconnectTimer.reset()\n this.teardown(() => {\n this.disconnecting = false\n callback && callback()\n }, code, reason)\n }\n\n /**\n *\n * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n *\n * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n */\n connect(params){\n if(params){\n console && console.log(\"passing params to connect is deprecated. Instead pass :params to the Socket constructor\")\n this.params = closure(params)\n }\n if(this.conn && !this.disconnecting){ return }\n if(this.longPollFallbackMs && this.transport !== LongPoll){\n this.connectWithFallback(LongPoll, this.longPollFallbackMs)\n } else {\n this.transportConnect()\n }\n }\n\n /**\n * Logs the message. Override `this.logger` for specialized logging. noops by default\n * @param {string} kind\n * @param {string} msg\n * @param {Object} data\n */\n log(kind, msg, data){ this.logger && this.logger(kind, msg, data) }\n\n /**\n * Returns true if a logger has been set on this socket.\n */\n hasLogger(){ return this.logger !== null }\n\n /**\n * Registers callbacks for connection open events\n *\n * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n *\n * @param {Function} callback\n */\n onOpen(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.open.push([ref, callback])\n return ref\n }\n\n /**\n * Registers callbacks for connection close events\n * @param {Function} callback\n */\n onClose(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.close.push([ref, callback])\n return ref\n }\n\n /**\n * Registers callbacks for connection error events\n *\n * @example socket.onError(function(error){ alert(\"An error occurred\") })\n *\n * @param {Function} callback\n */\n onError(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.error.push([ref, callback])\n return ref\n }\n\n /**\n * Registers callbacks for connection message events\n * @param {Function} callback\n */\n onMessage(callback){\n let ref = this.makeRef()\n this.stateChangeCallbacks.message.push([ref, callback])\n return ref\n }\n\n /**\n * Pings the server and invokes the callback with the RTT in milliseconds\n * @param {Function} callback\n *\n * Returns true if the ping was pushed or false if unable to be pushed.\n */\n ping(callback){\n if(!this.isConnected()){ return false }\n let ref = this.makeRef()\n let startTime = Date.now()\n this.push({topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: ref})\n let onMsgRef = this.onMessage(msg => {\n if(msg.ref === ref){\n this.off([onMsgRef])\n callback(Date.now() - startTime)\n }\n })\n return true\n }\n\n /**\n * @private\n */\n\n transportConnect(){\n this.connectClock++\n this.closeWasClean = false\n let protocols = undefined\n // Sec-WebSocket-Protocol based token\n // (longpoll uses Authorization header instead)\n if(this.authToken){\n protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`]\n }\n this.conn = new this.transport(this.endPointURL(), protocols)\n this.conn.binaryType = this.binaryType\n this.conn.timeout = this.longpollerTimeout\n this.conn.onopen = () => this.onConnOpen()\n this.conn.onerror = error => this.onConnError(error)\n this.conn.onmessage = event => this.onConnMessage(event)\n this.conn.onclose = event => this.onConnClose(event)\n }\n\n getSession(key){ return this.sessionStore && this.sessionStore.getItem(key) }\n\n storeSession(key, val){ this.sessionStore && this.sessionStore.setItem(key, val) }\n\n connectWithFallback(fallbackTransport, fallbackThreshold = 2500){\n clearTimeout(this.fallbackTimer)\n let established = false\n let primaryTransport = true\n let openRef, errorRef\n let fallback = (reason) => {\n this.log(\"transport\", `falling back to ${fallbackTransport.name}...`, reason)\n this.off([openRef, errorRef])\n primaryTransport = false\n this.replaceTransport(fallbackTransport)\n this.transportConnect()\n }\n if(this.getSession(`phx:fallback:${fallbackTransport.name}`)){ return fallback(\"memorized\") }\n\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold)\n\n errorRef = this.onError(reason => {\n this.log(\"transport\", \"error\", reason)\n if(primaryTransport && !established){\n clearTimeout(this.fallbackTimer)\n fallback(reason)\n }\n })\n this.onOpen(() => {\n established = true\n if(!primaryTransport){\n // only memorize LP if we never connected to primary\n if(!this.primaryPassedHealthCheck){ this.storeSession(`phx:fallback:${fallbackTransport.name}`, \"true\") }\n return this.log(\"transport\", `established ${fallbackTransport.name} fallback`)\n }\n // if we've established primary, give the fallback a new period to attempt ping\n clearTimeout(this.fallbackTimer)\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold)\n this.ping(rtt => {\n this.log(\"transport\", \"connected to primary after\", rtt)\n this.primaryPassedHealthCheck = true\n clearTimeout(this.fallbackTimer)\n })\n })\n this.transportConnect()\n }\n\n clearHeartbeats(){\n clearTimeout(this.heartbeatTimer)\n clearTimeout(this.heartbeatTimeoutTimer)\n }\n\n onConnOpen(){\n if(this.hasLogger()) this.log(\"transport\", `${this.transport.name} connected to ${this.endPointURL()}`)\n this.closeWasClean = false\n this.disconnecting = false\n this.establishedConnections++\n this.flushSendBuffer()\n this.reconnectTimer.reset()\n this.resetHeartbeat()\n this.stateChangeCallbacks.open.forEach(([, callback]) => callback())\n }\n\n /**\n * @private\n */\n\n heartbeatTimeout(){\n if(this.pendingHeartbeatRef){\n this.pendingHeartbeatRef = null\n if(this.hasLogger()){ this.log(\"transport\", \"heartbeat timeout. Attempting to re-establish connection\") }\n this.triggerChanError()\n this.closeWasClean = false\n this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, \"heartbeat timeout\")\n }\n }\n\n resetHeartbeat(){\n if(this.conn && this.conn.skipHeartbeat){ return }\n this.pendingHeartbeatRef = null\n this.clearHeartbeats()\n this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs)\n }\n\n teardown(callback, code, reason){\n if(!this.conn){\n return callback && callback()\n }\n let connectClock = this.connectClock\n\n this.waitForBufferDone(() => {\n if(connectClock !== this.connectClock){ return }\n if(this.conn){\n if(code){ this.conn.close(code, reason || \"\") } else { this.conn.close() }\n }\n\n this.waitForSocketClosed(() => {\n if(connectClock !== this.connectClock){ return }\n if(this.conn){\n this.conn.onopen = function (){ } // noop\n this.conn.onerror = function (){ } // noop\n this.conn.onmessage = function (){ } // noop\n this.conn.onclose = function (){ } // noop\n this.conn = null\n }\n\n callback && callback()\n })\n })\n }\n\n waitForBufferDone(callback, tries = 1){\n if(tries === 5 || !this.conn || !this.conn.bufferedAmount){\n callback()\n return\n }\n\n setTimeout(() => {\n this.waitForBufferDone(callback, tries + 1)\n }, 150 * tries)\n }\n\n waitForSocketClosed(callback, tries = 1){\n if(tries === 5 || !this.conn || this.conn.readyState === SOCKET_STATES.closed){\n callback()\n return\n }\n\n setTimeout(() => {\n this.waitForSocketClosed(callback, tries + 1)\n }, 150 * tries)\n }\n\n onConnClose(event){\n let closeCode = event && event.code\n if(this.hasLogger()) this.log(\"transport\", \"close\", event)\n this.triggerChanError()\n this.clearHeartbeats()\n if(!this.closeWasClean && closeCode !== 1000){\n this.reconnectTimer.scheduleTimeout()\n }\n this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event))\n }\n\n /**\n * @private\n */\n onConnError(error){\n if(this.hasLogger()) this.log(\"transport\", error)\n let transportBefore = this.transport\n let establishedBefore = this.establishedConnections\n this.stateChangeCallbacks.error.forEach(([, callback]) => {\n callback(error, transportBefore, establishedBefore)\n })\n if(transportBefore === this.transport || establishedBefore > 0){\n this.triggerChanError()\n }\n }\n\n /**\n * @private\n */\n triggerChanError(){\n this.channels.forEach(channel => {\n if(!(channel.isErrored() || channel.isLeaving() || channel.isClosed())){\n channel.trigger(CHANNEL_EVENTS.error)\n }\n })\n }\n\n /**\n * @returns {string}\n */\n connectionState(){\n switch(this.conn && this.conn.readyState){\n case SOCKET_STATES.connecting: return \"connecting\"\n case SOCKET_STATES.open: return \"open\"\n case SOCKET_STATES.closing: return \"closing\"\n default: return \"closed\"\n }\n }\n\n /**\n * @returns {boolean}\n */\n isConnected(){ return this.connectionState() === \"open\" }\n\n /**\n * @private\n *\n * @param {Channel}\n */\n remove(channel){\n this.off(channel.stateChangeRefs)\n this.channels = this.channels.filter(c => c !== channel)\n }\n\n /**\n * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n *\n * @param {refs} - list of refs returned by calls to\n * `onOpen`, `onClose`, `onError,` and `onMessage`\n */\n off(refs){\n for(let key in this.stateChangeCallbacks){\n this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {\n return refs.indexOf(ref) === -1\n })\n }\n }\n\n /**\n * Initiates a new channel for the given topic\n *\n * @param {string} topic\n * @param {Object} chanParams - Parameters for the channel\n * @returns {Channel}\n */\n channel(topic, chanParams = {}){\n let chan = new Channel(topic, chanParams, this)\n this.channels.push(chan)\n return chan\n }\n\n /**\n * @param {Object} data\n */\n push(data){\n if(this.hasLogger()){\n let {topic, event, payload, ref, join_ref} = data\n this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload)\n }\n\n if(this.isConnected()){\n this.encode(data, result => this.conn.send(result))\n } else {\n this.sendBuffer.push(() => this.encode(data, result => this.conn.send(result)))\n }\n }\n\n /**\n * Return the next message ref, accounting for overflows\n * @returns {string}\n */\n makeRef(){\n let newRef = this.ref + 1\n if(newRef === this.ref){ this.ref = 0 } else { this.ref = newRef }\n\n return this.ref.toString()\n }\n\n sendHeartbeat(){\n if(this.pendingHeartbeatRef && !this.isConnected()){ return }\n this.pendingHeartbeatRef = this.makeRef()\n this.push({topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: this.pendingHeartbeatRef})\n this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs)\n }\n\n flushSendBuffer(){\n if(this.isConnected() && this.sendBuffer.length > 0){\n this.sendBuffer.forEach(callback => callback())\n this.sendBuffer = []\n }\n }\n\n onConnMessage(rawMessage){\n this.decode(rawMessage.data, msg => {\n let {topic, event, payload, ref, join_ref} = msg\n if(ref && ref === this.pendingHeartbeatRef){\n this.clearHeartbeats()\n this.pendingHeartbeatRef = null\n this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs)\n }\n\n if(this.hasLogger()) this.log(\"receive\", `${payload.status || \"\"} ${topic} ${event} ${ref && \"(\" + ref + \")\" || \"\"}`, payload)\n\n for(let i = 0; i < this.channels.length; i++){\n const channel = this.channels[i]\n if(!channel.isMember(topic, event, payload, join_ref)){ continue }\n channel.trigger(event, payload, ref, join_ref)\n }\n\n for(let i = 0; i < this.stateChangeCallbacks.message.length; i++){\n let [, callback] = this.stateChangeCallbacks.message[i]\n callback(msg)\n }\n })\n }\n\n leaveOpenTopic(topic){\n let dupChannel = this.channels.find(c => c.topic === topic && (c.isJoined() || c.isJoining()))\n if(dupChannel){\n if(this.hasLogger()) this.log(\"transport\", `leaving duplicate topic \"${topic}\"`)\n dupChannel.leave()\n }\n }\n}\n"], - "mappings": "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", - "names": ["closure"] + "sources": ["../../assets/js/phoenix/utils.ts", "../../assets/js/phoenix/constants.ts", "../../assets/js/phoenix/push.ts", "../../assets/js/phoenix/timer.ts", "../../assets/js/phoenix/channel.ts", "../../assets/js/phoenix/ajax.ts", "../../assets/js/phoenix/longpoll.ts", "../../assets/js/phoenix/presence.ts", "../../assets/js/phoenix/serializer.ts", "../../assets/js/phoenix/socket.ts"], + "sourcesContent": ["// wraps value in closure or returns closure\nexport function closure(value: T | (() => T)): () => T {\n if (typeof value === \"function\") {\n return value as () => T;\n } else {\n return () => value;\n }\n}\n", "export const globalSelf = typeof self !== \"undefined\" ? self : null;\nexport const phxWindow = typeof window !== \"undefined\" ? window : null;\nexport const global = globalSelf || phxWindow || globalThis;\nexport const DEFAULT_VSN = \"2.0.0\";\n\nexport const SOCKET_STATES = {\n connecting: 0,\n open: 1,\n closing: 2,\n closed: 3,\n} as const;\n\nexport type SocketState = (typeof SOCKET_STATES)[keyof typeof SOCKET_STATES];\n\nexport const DEFAULT_TIMEOUT = 10000;\nexport const WS_CLOSE_NORMAL = 1000;\n\nexport const CHANNEL_STATES = {\n closed: \"closed\",\n errored: \"errored\",\n joined: \"joined\",\n joining: \"joining\",\n leaving: \"leaving\",\n} as const;\n\nexport type ChannelState = (typeof CHANNEL_STATES)[keyof typeof CHANNEL_STATES];\n\nexport const CHANNEL_EVENTS = {\n close: \"phx_close\",\n error: \"phx_error\",\n join: \"phx_join\",\n reply: \"phx_reply\",\n leave: \"phx_leave\",\n} as const;\n\nexport type ChannelEvent = (typeof CHANNEL_EVENTS)[keyof typeof CHANNEL_EVENTS];\n\nexport const TRANSPORTS = {\n longpoll: \"longpoll\",\n websocket: \"websocket\",\n} as const;\n\nexport type Transport = (typeof TRANSPORTS)[keyof typeof TRANSPORTS];\n\nexport const XHR_STATES = {\n complete: 4,\n} as const;\n\nexport const AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\";\n", "import type Channel from \"./channel\";\n\nexport interface PushResponse {\n status: string;\n response: any;\n _ref?: string;\n}\n\nexport interface ReceiveHook {\n status: string;\n callback: (response: any) => void;\n}\n\n/**\n * Initializes the Push\n * @param channel - The Channel\n * @param event - The event, for example `\"phx_join\"`\n * @param payload - The payload, for example `{user_id: 123}`\n * @param timeout - The push timeout in milliseconds\n * @internal\n */\nexport default class Push {\n public channel: Channel;\n public event: string;\n public payload: () => any;\n public receivedResp: PushResponse | null;\n public timeout: number;\n public timeoutTimer: number | null;\n public recHooks: ReceiveHook[];\n public sent: boolean;\n public ref: string | null;\n public refEvent: string | null;\n\n constructor(\n channel: Channel,\n event: string,\n payload: any | (() => any),\n timeout: number,\n ) {\n this.channel = channel;\n this.event = event;\n this.payload =\n typeof payload === \"function\" ? payload : () => payload || {};\n this.receivedResp = null;\n this.timeout = timeout;\n this.timeoutTimer = null;\n this.recHooks = [];\n this.sent = false;\n this.ref = null;\n this.refEvent = null;\n }\n\n /**\n * Resend the push with a new timeout\n */\n resend(timeout: number): void {\n this.timeout = timeout;\n this.reset();\n this.send();\n }\n\n /**\n * Send the push\n */\n send(): void {\n if (this.hasReceived(\"timeout\")) {\n return;\n }\n this.startTimeout();\n this.sent = true;\n this.channel.socket.push({\n topic: this.channel.topic,\n event: this.event,\n payload: this.payload(),\n ref: this.ref,\n join_ref: this.channel.joinRef(),\n });\n }\n\n /**\n * Register a callback for a specific response status\n */\n receive(status: string, callback: (response: any) => void): Push {\n if (this.hasReceived(status)) {\n callback(this.receivedResp!.response);\n }\n\n this.recHooks.push({ status, callback });\n return this;\n }\n\n /**\n * @private\n */\n reset(): void {\n this.cancelRefEvent();\n this.ref = null;\n this.refEvent = null;\n this.receivedResp = null;\n this.sent = false;\n }\n\n /**\n * @private\n */\n matchReceive({ status, response, _ref }: PushResponse): void {\n this.recHooks\n .filter((h) => h.status === status)\n .forEach((h) => h.callback(response));\n }\n\n /**\n * @private\n */\n cancelRefEvent(): void {\n if (!this.refEvent) {\n return;\n }\n this.channel.off(this.refEvent);\n }\n\n /**\n * @private\n */\n cancelTimeout(): void {\n if (this.timeoutTimer !== null) {\n clearTimeout(this.timeoutTimer);\n this.timeoutTimer = null;\n }\n }\n\n /**\n * @private\n */\n startTimeout(): void {\n if (this.timeoutTimer) {\n this.cancelTimeout();\n }\n this.ref = this.channel.socket.makeRef();\n this.refEvent = this.channel.replyEventName(this.ref);\n\n this.channel.on(this.refEvent, (payload: PushResponse) => {\n this.cancelRefEvent();\n this.cancelTimeout();\n this.receivedResp = payload;\n this.matchReceive(payload);\n });\n\n this.timeoutTimer = setTimeout(() => {\n this.trigger(\"timeout\", {});\n }, this.timeout) as any;\n }\n\n /**\n * @private\n */\n hasReceived(status: string): boolean {\n return this.receivedResp && this.receivedResp.status === status;\n }\n\n /**\n * @private\n */\n trigger(status: string, response: any): void {\n this.channel.trigger(this.refEvent!, { status, response });\n }\n}\n", "/**\n *\n * Creates a timer that accepts a `timerCalc` function to perform\n * calculated timeout retries, such as exponential backoff.\n *\n * @example\n * let reconnectTimer = new Timer(() => this.connect(), function(tries){\n * return [1000, 5000, 10000][tries - 1] || 10000\n * })\n * reconnectTimer.scheduleTimeout() // fires after 1000\n * reconnectTimer.scheduleTimeout() // fires after 5000\n * reconnectTimer.reset()\n * reconnectTimer.scheduleTimeout() // fires after 1000\n */\nexport default class Timer {\n private callback: () => void;\n private timerCalc: (tries: number) => number;\n private timer: number | null;\n private tries: number;\n\n constructor(callback: () => void, timerCalc: (tries: number) => number) {\n this.callback = callback;\n this.timerCalc = timerCalc;\n this.timer = null;\n this.tries = 0;\n }\n\n reset(): void {\n this.tries = 0;\n if (this.timer !== null) {\n clearTimeout(this.timer);\n }\n }\n\n /**\n * Cancels any previous scheduleTimeout and schedules callback\n */\n scheduleTimeout(): void {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n }\n\n this.timer = setTimeout(\n () => {\n this.tries = this.tries + 1;\n this.callback();\n },\n this.timerCalc(this.tries + 1),\n ) as any;\n }\n}\n", "import { closure } from \"./utils\";\nimport { CHANNEL_EVENTS, CHANNEL_STATES, type ChannelState } from \"./constants\";\n\nimport Push from \"./push\";\nimport Timer from \"./timer\";\nimport type Socket from \"./socket\";\n\nexport interface ChannelBinding {\n event: string;\n ref: number;\n callback: (payload: any, ref?: string, joinRef?: string) => void;\n}\n\n/**\n * Channel class for Phoenix WebSocket communication\n */\nexport default class Channel {\n public state: ChannelState;\n public topic: string;\n public params: () => any;\n public socket: Socket;\n public bindings: ChannelBinding[];\n public bindingRef: number;\n public timeout: number;\n public joinedOnce: boolean;\n public joinPush: Push;\n public pushBuffer: Push[];\n public stateChangeRefs: string[];\n public rejoinTimer: Timer;\n\n constructor(topic: string, params: any | (() => any), socket: Socket) {\n this.state = CHANNEL_STATES.closed;\n this.topic = topic;\n this.params = closure(params || {});\n this.socket = socket;\n this.bindings = [];\n this.bindingRef = 0;\n this.timeout = this.socket.timeout;\n this.joinedOnce = false;\n this.joinPush = new Push(\n this,\n CHANNEL_EVENTS.join,\n this.params,\n this.timeout,\n );\n this.pushBuffer = [];\n this.stateChangeRefs = [];\n\n this.rejoinTimer = new Timer(() => {\n if (this.socket.isConnected()) {\n this.rejoin();\n }\n }, this.socket.rejoinAfterMs);\n this.stateChangeRefs.push(\n this.socket.onError(() => this.rejoinTimer.reset()),\n );\n this.stateChangeRefs.push(\n this.socket.onOpen(() => {\n this.rejoinTimer.reset();\n if (this.isErrored()) {\n this.rejoin();\n }\n }),\n );\n this.joinPush.receive(\"ok\", () => {\n this.state = CHANNEL_STATES.joined;\n this.rejoinTimer.reset();\n this.pushBuffer.forEach((pushEvent) => pushEvent.send());\n this.pushBuffer = [];\n });\n this.joinPush.receive(\"error\", () => {\n this.state = CHANNEL_STATES.errored;\n if (this.socket.isConnected()) {\n this.rejoinTimer.scheduleTimeout();\n }\n });\n this.onClose(() => {\n this.rejoinTimer.reset();\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`);\n this.state = CHANNEL_STATES.closed;\n this.socket.remove(this);\n });\n this.onError((reason: any) => {\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", `error ${this.topic}`, reason);\n if (this.isJoining()) {\n this.joinPush.reset();\n }\n this.state = CHANNEL_STATES.errored;\n if (this.socket.isConnected()) {\n this.rejoinTimer.scheduleTimeout();\n }\n });\n this.joinPush.receive(\"timeout\", () => {\n if (this.socket.hasLogger())\n this.socket.log(\n \"channel\",\n `timeout ${this.topic} (${this.joinRef()})`,\n this.joinPush.timeout,\n );\n const leavePush = new Push(\n this,\n CHANNEL_EVENTS.leave,\n closure({}),\n this.timeout,\n );\n leavePush.send();\n this.state = CHANNEL_STATES.errored;\n this.joinPush.reset();\n if (this.socket.isConnected()) {\n this.rejoinTimer.scheduleTimeout();\n }\n });\n this.on(CHANNEL_EVENTS.reply, (payload: any, ref?: string) => {\n this.trigger(this.replyEventName(ref!), payload);\n });\n }\n\n /**\n * Join the channel\n */\n join(timeout: number = this.timeout): Push {\n if (this.joinedOnce) {\n throw new Error(\n \"tried to join multiple times. 'join' can only be called a single time per channel instance\",\n );\n } else {\n this.timeout = timeout;\n this.joinedOnce = true;\n this.rejoin();\n return this.joinPush;\n }\n }\n\n /**\n * Hook into channel close\n */\n onClose(\n callback: (payload?: any, ref?: string, joinRef?: string) => void,\n ): number {\n return this.on(CHANNEL_EVENTS.close, callback);\n }\n\n /**\n * Hook into channel errors\n */\n onError(\n callback: (reason: any, ref?: string, joinRef?: string) => void,\n ): number {\n return this.on(CHANNEL_EVENTS.error, (reason: any) => callback(reason));\n }\n\n /**\n * Subscribes on channel events\n *\n * Subscription returns a ref counter, which can be used later to\n * unsubscribe the exact event listener\n *\n * @example\n * const ref1 = channel.on(\"event\", do_stuff)\n * const ref2 = channel.on(\"event\", do_other_stuff)\n * channel.off(\"event\", ref1)\n * // Since unsubscription, do_stuff won't fire,\n * // while do_other_stuff will keep firing on the \"event\"\n */\n on(\n event: string,\n callback: (payload: any, ref?: string, joinRef?: string) => void,\n ): number {\n const ref = this.bindingRef++;\n this.bindings.push({ event, ref, callback });\n return ref;\n }\n\n /**\n * Unsubscribes off of channel events\n *\n * Use the ref returned from a channel.on() to unsubscribe one\n * handler, or pass nothing for the ref to unsubscribe all\n * handlers for the given event.\n *\n * @example\n * // Unsubscribe the do_stuff handler\n * const ref1 = channel.on(\"event\", do_stuff)\n * channel.off(\"event\", ref1)\n *\n * // Unsubscribe all handlers from event\n * channel.off(\"event\")\n */\n off(event: string, ref?: number): void {\n this.bindings = this.bindings.filter((bind) => {\n return !(\n bind.event === event &&\n (typeof ref === \"undefined\" || ref === bind.ref)\n );\n });\n }\n\n /**\n * @internal\n * @private\n */\n canPush(): boolean {\n return this.socket.isConnected() && this.isJoined();\n }\n\n /**\n * Sends a message `event` to phoenix with the payload `payload`.\n * Phoenix receives this in the `handle_in(event, payload, socket)`\n * function. if phoenix replies or it times out (default 10000ms),\n * then optionally the reply can be received.\n *\n * @example\n * channel.push(\"event\")\n * .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n * .receive(\"error\", err => console.log(\"phoenix errored\", err))\n * .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n */\n push(event: string, payload: any = {}, timeout: number = this.timeout): Push {\n if (!this.joinedOnce) {\n throw new Error(\n `tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`,\n );\n }\n const pushEvent = new Push(this, event, () => payload, timeout);\n if (this.canPush()) {\n pushEvent.send();\n } else {\n pushEvent.startTimeout();\n this.pushBuffer.push(pushEvent);\n }\n\n return pushEvent;\n }\n\n /**\n * Leaves the channel\n *\n * Unsubscribes from server events, and\n * instructs channel to terminate on server\n *\n * Triggers onClose() hooks\n *\n * To receive leave acknowledgements, use the `receive`\n * hook to bind to the server ack, ie:\n *\n * @example\n * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n */\n leave(timeout: number = this.timeout): Push {\n this.rejoinTimer.reset();\n this.joinPush.cancelTimeout();\n\n this.state = CHANNEL_STATES.leaving;\n const onClose = () => {\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", `leave ${this.topic}`);\n this.trigger(CHANNEL_EVENTS.close, \"leave\");\n };\n const leavePush = new Push(\n this,\n CHANNEL_EVENTS.leave,\n closure({}),\n timeout,\n );\n leavePush\n .receive(\"ok\", () => onClose())\n .receive(\"timeout\", () => onClose());\n leavePush.send();\n if (!this.canPush()) {\n leavePush.trigger(\"ok\", {});\n }\n\n return leavePush;\n }\n\n /**\n * Overridable message hook\n *\n * Receives all events for specialized message handling\n * before dispatching to the channel callbacks.\n *\n * Must return the payload, modified or unmodified\n */\n onMessage(\n _event: string,\n payload: any,\n _ref?: string,\n _joinRef?: string,\n ): any {\n return payload;\n }\n\n /**\n * @internal\n * @private\n */\n isMember(\n topic: string,\n event: string,\n payload: any,\n joinRef?: string,\n ): boolean {\n if (this.topic !== topic) {\n return false;\n }\n\n if (joinRef && joinRef !== this.joinRef()) {\n if (this.socket.hasLogger())\n this.socket.log(\"channel\", \"dropping outdated message\", {\n topic,\n event,\n payload,\n joinRef,\n });\n return false;\n } else {\n return true;\n }\n }\n\n /**\n * @internal\n * @private\n */\n joinRef(): string | null {\n return this.joinPush.ref;\n }\n\n /**\n * @internal\n * @private\n */\n rejoin(timeout: number = this.timeout): void {\n if (this.isLeaving()) {\n return;\n }\n this.socket.leaveOpenTopic(this.topic);\n this.state = CHANNEL_STATES.joining;\n this.joinPush.resend(timeout);\n }\n\n /**\n * @internal\n * @private\n */\n trigger(event: string, payload: any, ref?: string, joinRef?: string): void {\n const handledPayload = this.onMessage(event, payload, ref, joinRef);\n if (payload && !handledPayload) {\n throw new Error(\n \"channel onMessage callbacks must return the payload, modified or unmodified\",\n );\n }\n\n const eventBindings = this.bindings.filter((bind) => bind.event === event);\n\n for (let i = 0; i < eventBindings.length; i++) {\n const bind = eventBindings[i]!;\n bind.callback(handledPayload, ref, joinRef || this.joinRef());\n }\n }\n\n /**\n * @internal\n * @private\n */\n replyEventName(ref: string): string {\n return `chan_reply_${ref}`;\n }\n\n /**\n * @internal\n * @private\n */\n isClosed(): boolean {\n return this.state === CHANNEL_STATES.closed;\n }\n\n /**\n * @internal\n * @private\n */\n isErrored(): boolean {\n return this.state === CHANNEL_STATES.errored;\n }\n\n /**\n * @internal\n * @private\n */\n isJoined(): boolean {\n return this.state === CHANNEL_STATES.joined;\n }\n\n /**\n * @internal\n * @private\n */\n isJoining(): boolean {\n return this.state === CHANNEL_STATES.joining;\n }\n\n /**\n * @internal\n * @private\n */\n isLeaving(): boolean {\n return this.state === CHANNEL_STATES.leaving;\n }\n}\n", "import { global, XHR_STATES } from \"./constants\";\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\ntype Headers = Record;\ntype RequestBody = string | null;\ntype AjaxCallback = (response: any) => void;\ntype TimeoutCallback = () => void;\n\ninterface XDomainRequest {\n timeout: number;\n open(method: string, url: string): void;\n send(body?: string | null): void;\n onload: (() => void) | null;\n ontimeout: (() => void) | null;\n onprogress: (() => void) | null;\n responseText: string;\n}\n\ninterface XMLHttpRequestLike {\n open(method: string, url: string, async?: boolean): void;\n send(body?: string | null): void;\n setRequestHeader(name: string, value: string): void;\n timeout: number;\n readyState: number;\n responseText: string;\n onreadystatechange: ((ev?: any) => any) | null;\n onerror: (() => void) | null;\n ontimeout: (() => void) | null;\n}\n\nexport default class Ajax {\n static request(\n method: HttpMethod,\n endPoint: string,\n headers: Headers,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): XMLHttpRequestLike | XDomainRequest | AbortController {\n if ((global as any).XDomainRequest) {\n const req = new (global as any).XDomainRequest(); // IE8, IE9\n return this.xdomainRequest(\n req,\n method,\n endPoint,\n body,\n timeout,\n ontimeout,\n callback,\n );\n } else if ((global as any).XMLHttpRequest) {\n const req = new (global as any).XMLHttpRequest(); // IE7+, Firefox, Chrome, Opera, Safari\n return this.xhrRequest(\n req,\n method,\n endPoint,\n headers,\n body,\n timeout,\n ontimeout,\n callback,\n );\n } else if (\n typeof global.fetch === \"function\" &&\n typeof global.AbortController === \"function\"\n ) {\n // Fetch with AbortController for modern browsers\n return this.fetchRequest(\n method,\n endPoint,\n headers,\n body,\n timeout,\n ontimeout,\n callback,\n );\n } else {\n throw new Error(\"No suitable XMLHttpRequest implementation found\");\n }\n }\n\n static fetchRequest(\n method: HttpMethod,\n endPoint: string,\n headers: Headers,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): AbortController {\n const options: RequestInit = {\n method,\n headers,\n body,\n };\n const controller = new AbortController();\n if (timeout) {\n setTimeout(() => controller.abort(), timeout);\n options.signal = controller.signal;\n }\n global\n .fetch(endPoint, options)\n .then((response) => response.text())\n .then((data) => this.parseJSON(data))\n .then((data) => callback && callback(data))\n .catch((err) => {\n if (err.name === \"AbortError\" && ontimeout) {\n ontimeout();\n } else {\n callback && callback(null);\n }\n });\n return controller;\n }\n\n static xdomainRequest(\n req: XDomainRequest,\n method: HttpMethod,\n endPoint: string,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): XDomainRequest {\n req.timeout = timeout;\n req.open(method, endPoint);\n req.onload = () => {\n const response = this.parseJSON(req.responseText);\n callback && callback(response);\n };\n if (ontimeout) {\n req.ontimeout = ontimeout;\n }\n\n // Work around bug in IE9 that requires an attached onprogress handler\n req.onprogress = () => {};\n\n req.send(body);\n return req;\n }\n\n static xhrRequest(\n req: XMLHttpRequestLike,\n method: HttpMethod,\n endPoint: string,\n headers: Headers,\n body: RequestBody,\n timeout: number,\n ontimeout: TimeoutCallback | null,\n callback: AjaxCallback | null,\n ): XMLHttpRequestLike {\n req.open(method, endPoint, true);\n req.timeout = timeout;\n for (const [key, value] of Object.entries(headers)) {\n req.setRequestHeader(key, value);\n }\n req.onerror = () => callback && callback(null);\n req.onreadystatechange = () => {\n if (req.readyState === XHR_STATES.complete && callback) {\n const response = this.parseJSON(req.responseText);\n callback(response);\n }\n };\n if (ontimeout) {\n req.ontimeout = ontimeout;\n }\n\n req.send(body);\n return req;\n }\n\n static parseJSON(resp: string | null | undefined): any {\n if (!resp || resp === \"\") {\n return null;\n }\n\n try {\n return JSON.parse(resp);\n } catch {\n console && console.log(\"failed to parse JSON response\", resp);\n return null;\n }\n }\n\n static serialize(obj: Record, parentKey?: string): string {\n const queryStr: string[] = [];\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n continue;\n }\n const paramKey = parentKey ? `${parentKey}[${key}]` : key;\n const paramVal = obj[key];\n if (typeof paramVal === \"object\") {\n queryStr.push(this.serialize(paramVal, paramKey));\n } else {\n queryStr.push(\n encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal),\n );\n }\n }\n return queryStr.join(\"&\");\n }\n\n static appendParams(url: string, params: Record): string {\n if (Object.keys(params).length === 0) {\n return url;\n }\n\n const prefix = url.match(/\\?/) ? \"&\" : \"?\";\n return `${url}${prefix}${this.serialize(params)}`;\n }\n}\n", "import {\n SOCKET_STATES,\n TRANSPORTS,\n AUTH_TOKEN_PREFIX,\n type SocketState,\n} from \"./constants\";\n\nimport Ajax from \"./ajax\";\n\nfunction arrayBufferToBase64(buffer: ArrayBuffer): string {\n let binary = \"\";\n const bytes = new Uint8Array(buffer);\n const len = bytes.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n\ninterface LongPollResponse {\n status: number;\n token?: string;\n messages?: string[];\n}\n\ninterface MessageEvent {\n data: string;\n}\n\ninterface CloseEventInit {\n code?: number;\n reason?: string;\n wasClean?: boolean;\n}\n\nexport default class LongPoll {\n public endPoint: string | null;\n public token: string | null;\n public skipHeartbeat: boolean;\n public reqs: Set;\n public awaitingBatchAck: boolean;\n public currentBatch: string[] | null;\n public currentBatchTimer: number | null;\n public batchBuffer: string[];\n public onopen: (event: any) => void;\n public onerror: (error: any) => void;\n public onmessage: (event: MessageEvent) => void;\n public onclose: (event: CloseEvent | CloseEventInit) => void;\n public pollEndpoint: string;\n public readyState: SocketState;\n public timeout: number;\n public authToken?: string;\n\n constructor(endPoint: string, protocols?: string[]) {\n // we only support subprotocols for authToken\n // [\"phoenix\", \"base64url.bearer.phx.BASE64_ENCODED_TOKEN\"]\n if (\n protocols &&\n protocols.length === 2 &&\n protocols[1]!.startsWith(AUTH_TOKEN_PREFIX)\n ) {\n this.authToken = atob(protocols[1]!.slice(AUTH_TOKEN_PREFIX.length));\n }\n this.endPoint = null;\n this.token = null;\n this.skipHeartbeat = true;\n this.reqs = new Set();\n this.awaitingBatchAck = false;\n this.currentBatch = null;\n this.currentBatchTimer = null;\n this.batchBuffer = [];\n this.onopen = function () {}; // noop\n this.onerror = function () {}; // noop\n this.onmessage = function () {}; // noop\n this.onclose = function () {}; // noop\n this.pollEndpoint = this.normalizeEndpoint(endPoint);\n this.readyState = SOCKET_STATES.connecting;\n this.timeout = 20000; // will be set by Socket\n // we must wait for the caller to finish setting up our callbacks and timeout properties\n setTimeout(() => this.poll(), 0);\n }\n\n normalizeEndpoint(endPoint: string): string {\n return endPoint\n .replace(\"ws://\", \"http://\")\n .replace(\"wss://\", \"https://\")\n .replace(\n new RegExp(\"(.*)/\" + TRANSPORTS.websocket),\n \"$1/\" + TRANSPORTS.longpoll,\n );\n }\n\n endpointURL(): string {\n return Ajax.appendParams(this.pollEndpoint, { token: this.token });\n }\n\n closeAndRetry(code: number, reason: string, wasClean: boolean): void {\n this.close(code, reason, wasClean);\n this.readyState = SOCKET_STATES.connecting;\n }\n\n ontimeout(): void {\n this.onerror(\"timeout\");\n this.closeAndRetry(1005, \"timeout\", false);\n }\n\n isActive(): boolean {\n return (\n this.readyState === SOCKET_STATES.open ||\n this.readyState === SOCKET_STATES.connecting\n );\n }\n\n poll(): void {\n const headers: Record = { Accept: \"application/json\" };\n if (this.authToken) {\n headers[\"X-Phoenix-AuthToken\"] = this.authToken;\n }\n this.ajax(\n \"GET\",\n headers,\n null,\n () => this.ontimeout(),\n (resp: LongPollResponse | null) => {\n let status: number;\n if (resp) {\n const { status: respStatus, token } = resp;\n status = respStatus;\n this.token = token || null;\n } else {\n status = 0;\n }\n\n switch (status) {\n case 200:\n resp!.messages!.forEach((msg) => {\n // Tasks are what things like event handlers, setTimeout callbacks,\n // promise resolves and more are run within.\n // In modern browsers, there are two different kinds of tasks,\n // microtasks and macrotasks.\n // Microtasks are mainly used for Promises, while macrotasks are\n // used for everything else.\n // Microtasks always have priority over macrotasks. If the JS engine\n // is looking for a task to run, it will always try to empty the\n // microtask queue before attempting to run anything from the\n // macrotask queue.\n //\n // For the WebSocket transport, messages always arrive in their own\n // event. This means that if any promises are resolved from within,\n // their callbacks will always finish execution by the time the\n // next message event handler is run.\n //\n // In order to emulate this behaviour, we need to make sure each\n // onmessage handler is run within its own macrotask.\n setTimeout(() => this.onmessage({ data: msg }), 0);\n });\n this.poll();\n break;\n case 204:\n this.poll();\n break;\n case 410:\n this.readyState = SOCKET_STATES.open;\n this.onopen({});\n this.poll();\n break;\n case 403:\n this.onerror(403);\n this.close(1008, \"forbidden\", false);\n break;\n case 0:\n case 500:\n this.onerror(500);\n this.closeAndRetry(1011, \"internal server error\", false);\n break;\n default:\n throw new Error(`unhandled poll status ${status}`);\n }\n },\n );\n }\n\n // we collect all pushes within the current event loop by\n // setTimeout 0, which optimizes back-to-back procedural\n // pushes against an empty buffer\n\n send(body: string | ArrayBuffer): void {\n let bodyStr: string;\n if (typeof body !== \"string\") {\n bodyStr = arrayBufferToBase64(body);\n } else {\n bodyStr = body;\n }\n if (this.currentBatch) {\n this.currentBatch.push(bodyStr);\n } else if (this.awaitingBatchAck) {\n this.batchBuffer.push(bodyStr);\n } else {\n this.currentBatch = [bodyStr];\n this.currentBatchTimer = setTimeout(() => {\n this.batchSend(this.currentBatch!);\n this.currentBatch = null;\n }, 0) as any;\n }\n }\n\n batchSend(messages: string[]): void {\n this.awaitingBatchAck = true;\n this.ajax(\n \"POST\",\n { \"Content-Type\": \"application/x-ndjson\" },\n messages.join(\"\\n\"),\n () => this.onerror(\"timeout\"),\n (resp: any) => {\n this.awaitingBatchAck = false;\n if (!resp || resp.status !== 200) {\n this.onerror(resp && resp.status);\n this.closeAndRetry(1011, \"internal server error\", false);\n } else if (this.batchBuffer.length > 0) {\n this.batchSend(this.batchBuffer);\n this.batchBuffer = [];\n }\n },\n );\n }\n\n close(code?: number, reason?: string, wasClean?: boolean): void {\n for (const req of this.reqs) {\n req.abort();\n }\n this.readyState = SOCKET_STATES.closed;\n const opts = Object.assign(\n { code: 1000, reason: undefined, wasClean: true },\n { code, reason, wasClean },\n );\n this.batchBuffer = [];\n if (this.currentBatchTimer !== null) {\n clearTimeout(this.currentBatchTimer);\n this.currentBatchTimer = null;\n }\n if (typeof CloseEvent !== \"undefined\") {\n this.onclose(new CloseEvent(\"close\", opts));\n } else {\n this.onclose(opts);\n }\n }\n\n ajax(\n method: \"GET\" | \"POST\",\n headers: Record,\n body: string | null,\n onCallerTimeout: () => void,\n callback: (resp: any) => void,\n ): void {\n const ontimeout = () => {\n this.reqs.delete(req);\n onCallerTimeout();\n };\n const req = Ajax.request(\n method,\n this.endpointURL(),\n headers,\n body,\n this.timeout,\n ontimeout,\n (resp: any) => {\n this.reqs.delete(req);\n if (this.isActive()) {\n callback(resp);\n }\n },\n );\n this.reqs.add(req);\n }\n}\n", "import type Channel from \"./channel\";\n\nexport interface PresenceMeta {\n phx_ref: string;\n [key: string]: any;\n}\n\nexport interface PresenceState {\n metas: PresenceMeta[];\n [key: string]: any;\n}\n\nexport interface PresenceMap {\n [key: string]: PresenceState;\n}\n\nexport interface PresenceDiff {\n joins: PresenceMap;\n leaves: PresenceMap;\n}\n\nexport interface PresenceOptions {\n events?: {\n state: string;\n diff: string;\n };\n}\n\nexport type PresenceCallback = (\n key: string,\n current: PresenceState | undefined,\n newPres: PresenceState,\n) => void;\nexport type PresenceSyncCallback = () => void;\nexport type PresenceChooser = (\n key: string,\n presence: PresenceState,\n) => T;\n\n/**\n * Initializes the Presence\n * @param channel - The Channel\n * @param opts - The options, for example `{events: {state: \"state\", diff: \"diff\"}}`\n */\nexport default class Presence {\n public state: PresenceMap;\n public pendingDiffs: PresenceDiff[];\n public channel: Channel;\n public joinRef: string | null;\n public caller: {\n onJoin: PresenceCallback;\n onLeave: PresenceCallback;\n onSync: PresenceSyncCallback;\n };\n\n constructor(channel: Channel, opts: PresenceOptions = {}) {\n const events = opts.events || {\n state: \"presence_state\",\n diff: \"presence_diff\",\n };\n this.state = {};\n this.pendingDiffs = [];\n this.channel = channel;\n this.joinRef = null;\n this.caller = {\n onJoin: function () {},\n onLeave: function () {},\n onSync: function () {},\n };\n\n this.channel.on(events.state, (newState: PresenceMap) => {\n const { onJoin, onLeave, onSync } = this.caller;\n\n this.joinRef = this.channel.joinRef();\n this.state = Presence.syncState(this.state, newState, onJoin, onLeave);\n\n this.pendingDiffs.forEach((diff) => {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);\n });\n this.pendingDiffs = [];\n onSync();\n });\n\n this.channel.on(events.diff, (diff: PresenceDiff) => {\n const { onJoin, onLeave, onSync } = this.caller;\n\n if (this.inPendingSyncState()) {\n this.pendingDiffs.push(diff);\n } else {\n this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);\n onSync();\n }\n });\n }\n\n /**\n * @internal\n * @private\n */\n onJoin(callback: PresenceCallback): void {\n this.caller.onJoin = callback;\n }\n\n /**\n * @internal\n * @private\n */\n onLeave(callback: PresenceCallback): void {\n this.caller.onLeave = callback;\n }\n\n /**\n * @internal\n * @private\n */\n onSync(callback: PresenceSyncCallback): void {\n this.caller.onSync = callback;\n }\n\n /**\n * @internal\n * @private\n */\n list(by?: PresenceChooser): T[] {\n return Presence.list(this.state, by);\n }\n\n /**\n * @internal\n * @private\n */\n inPendingSyncState(): boolean {\n return !this.joinRef || this.joinRef !== this.channel.joinRef();\n }\n\n // lower-level public static API\n\n /**\n * Used to sync the list of presences on the server\n * with the client's state. An optional `onJoin` and `onLeave` callback can\n * be provided to react to changes in the client's local presences across\n * disconnects and reconnects with the server.\n */\n static syncState(\n currentState: PresenceMap,\n newState: PresenceMap,\n onJoin?: PresenceCallback,\n onLeave?: PresenceCallback,\n ): PresenceMap {\n const state = this.clone(currentState);\n const joins: PresenceMap = {};\n const leaves: PresenceMap = {};\n\n this.map(state, (key, presence) => {\n if (!newState[key]) {\n leaves[key] = presence;\n }\n });\n this.map(newState, (key, newPresence) => {\n const currentPresence = state[key];\n if (currentPresence) {\n const newRefs = newPresence.metas.map((m) => m.phx_ref);\n const curRefs = currentPresence.metas.map((m) => m.phx_ref);\n const joinedMetas = newPresence.metas.filter(\n (m) => curRefs.indexOf(m.phx_ref) < 0,\n );\n const leftMetas = currentPresence.metas.filter(\n (m) => newRefs.indexOf(m.phx_ref) < 0,\n );\n if (joinedMetas.length > 0) {\n joins[key] = newPresence;\n joins[key]!.metas = joinedMetas;\n }\n if (leftMetas.length > 0) {\n leaves[key] = this.clone(currentPresence);\n leaves[key]!.metas = leftMetas;\n }\n } else {\n joins[key] = newPresence;\n }\n });\n return this.syncDiff(\n state,\n { joins: joins, leaves: leaves },\n onJoin,\n onLeave,\n );\n }\n\n /**\n * Used to sync a diff of presence join and leave\n * events from the server, as they happen. Like `syncState`, `syncDiff`\n * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n * joining or leaving from a device.\n */\n static syncDiff(\n state: PresenceMap,\n diff: PresenceDiff,\n onJoin?: PresenceCallback,\n onLeave?: PresenceCallback,\n ): PresenceMap {\n const { joins, leaves } = this.clone(diff);\n if (!onJoin) {\n onJoin = function () {};\n }\n if (!onLeave) {\n onLeave = function () {};\n }\n\n this.map(joins, (key, newPresence) => {\n const currentPresence = state[key];\n state[key] = this.clone(newPresence);\n if (currentPresence) {\n const joinedRefs = state[key]!.metas.map((m) => m.phx_ref);\n const curMetas = currentPresence.metas.filter(\n (m) => joinedRefs.indexOf(m.phx_ref) < 0,\n );\n state[key]!.metas.unshift(...curMetas);\n }\n onJoin!(key, currentPresence, newPresence);\n });\n this.map(leaves, (key, leftPresence) => {\n const currentPresence = state[key];\n if (!currentPresence) {\n return;\n }\n const refsToRemove = leftPresence.metas.map((m) => m.phx_ref);\n currentPresence.metas = currentPresence.metas.filter((p) => {\n return refsToRemove.indexOf(p.phx_ref) < 0;\n });\n onLeave!(key, currentPresence, leftPresence);\n if (currentPresence.metas.length === 0) {\n delete state[key];\n }\n });\n return state;\n }\n\n /**\n * Returns the array of presences, with selected metadata.\n */\n static list(\n presences: PresenceMap,\n chooser?: PresenceChooser,\n ): T[] {\n if (!chooser) {\n chooser = function (key, pres) {\n return pres as any;\n };\n }\n\n return this.map(presences, (key, presence) => {\n return chooser!(key, presence);\n });\n }\n\n // private\n\n private static map(\n obj: PresenceMap,\n func: (key: string, presence: PresenceState) => T,\n ): T[] {\n return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]!));\n }\n\n private static clone(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n }\n}\n", "/* The default serializer for encoding and decoding messages */\nimport { CHANNEL_EVENTS } from \"./constants\";\n\nexport interface Message {\n join_ref: string | null;\n ref: string | null;\n topic: string;\n event: string;\n payload: any;\n}\n\nexport interface BinaryMessage {\n join_ref: string;\n ref: string;\n event: string;\n topic: string;\n payload: ArrayBuffer;\n}\n\nexport interface ReplyPayload {\n status: string;\n response: ArrayBuffer;\n}\n\nconst Serializer = {\n HEADER_LENGTH: 1,\n META_LENGTH: 4,\n KINDS: { push: 0, reply: 1, broadcast: 2 } as const,\n\n encode(\n msg: Message | BinaryMessage,\n callback: (encoded: string | ArrayBuffer) => void,\n ): void {\n if (msg.payload.constructor === ArrayBuffer) {\n return callback(this.binaryEncode(msg as BinaryMessage));\n } else {\n const payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];\n return callback(JSON.stringify(payload));\n }\n },\n\n decode(\n rawPayload: string | ArrayBuffer,\n callback: (decoded: Message) => void,\n ): void {\n if (rawPayload.constructor === ArrayBuffer) {\n return callback(this.binaryDecode(rawPayload as ArrayBuffer));\n } else {\n const [join_ref, ref, topic, event, payload] = JSON.parse(\n rawPayload as string,\n );\n return callback({ join_ref, ref, topic, event, payload });\n }\n },\n\n // private\n\n binaryEncode(message: BinaryMessage): ArrayBuffer {\n const { join_ref, ref, event, topic, payload } = message;\n const metaLength =\n this.META_LENGTH +\n join_ref.length +\n ref.length +\n topic.length +\n event.length;\n const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);\n const view = new DataView(header);\n let offset = 0;\n\n view.setUint8(offset++, this.KINDS.push); // kind\n view.setUint8(offset++, join_ref.length);\n view.setUint8(offset++, ref.length);\n view.setUint8(offset++, topic.length);\n view.setUint8(offset++, event.length);\n Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n\n const combined = new Uint8Array(header.byteLength + payload.byteLength);\n combined.set(new Uint8Array(header), 0);\n combined.set(new Uint8Array(payload), header.byteLength);\n\n return combined.buffer;\n },\n\n binaryDecode(buffer: ArrayBuffer): Message {\n const view = new DataView(buffer);\n const kind = view.getUint8(0);\n const decoder = new TextDecoder();\n switch (kind) {\n case this.KINDS.push:\n return this.decodePush(buffer, view, decoder);\n case this.KINDS.reply:\n return this.decodeReply(buffer, view, decoder);\n case this.KINDS.broadcast:\n return this.decodeBroadcast(buffer, view, decoder);\n default:\n throw new Error(`Unknown message kind: ${kind}`);\n }\n },\n\n decodePush(\n buffer: ArrayBuffer,\n view: DataView,\n decoder: TextDecoder,\n ): Message {\n const joinRefSize = view.getUint8(1);\n const topicSize = view.getUint8(2);\n const eventSize = view.getUint8(3);\n let offset = this.HEADER_LENGTH + this.META_LENGTH - 1; // pushes have no ref\n const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n offset = offset + joinRefSize;\n const topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n offset = offset + topicSize;\n const event = decoder.decode(buffer.slice(offset, offset + eventSize));\n offset = offset + eventSize;\n const data = buffer.slice(offset, buffer.byteLength);\n return {\n join_ref: joinRef,\n ref: null,\n topic: topic,\n event: event,\n payload: data,\n };\n },\n\n decodeReply(\n buffer: ArrayBuffer,\n view: DataView,\n decoder: TextDecoder,\n ): Message {\n const joinRefSize = view.getUint8(1);\n const refSize = view.getUint8(2);\n const topicSize = view.getUint8(3);\n const eventSize = view.getUint8(4);\n let offset = this.HEADER_LENGTH + this.META_LENGTH;\n const joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n offset = offset + joinRefSize;\n const ref = decoder.decode(buffer.slice(offset, offset + refSize));\n offset = offset + refSize;\n const topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n offset = offset + topicSize;\n const event = decoder.decode(buffer.slice(offset, offset + eventSize));\n offset = offset + eventSize;\n const data = buffer.slice(offset, buffer.byteLength);\n const payload: ReplyPayload = { status: event, response: data };\n return {\n join_ref: joinRef,\n ref: ref,\n topic: topic,\n event: CHANNEL_EVENTS.reply,\n payload: payload,\n };\n },\n\n decodeBroadcast(\n buffer: ArrayBuffer,\n view: DataView,\n decoder: TextDecoder,\n ): Message {\n const topicSize = view.getUint8(1);\n const eventSize = view.getUint8(2);\n let offset = this.HEADER_LENGTH + 2;\n const topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n offset = offset + topicSize;\n const event = decoder.decode(buffer.slice(offset, offset + eventSize));\n offset = offset + eventSize;\n const data = buffer.slice(offset, buffer.byteLength);\n\n return {\n join_ref: null,\n ref: null,\n topic: topic,\n event: event,\n payload: data,\n };\n },\n};\n\nexport default Serializer;\n", "import {\n global,\n phxWindow,\n CHANNEL_EVENTS,\n DEFAULT_TIMEOUT,\n DEFAULT_VSN,\n SOCKET_STATES,\n TRANSPORTS,\n WS_CLOSE_NORMAL,\n AUTH_TOKEN_PREFIX,\n} from \"./constants\";\n\nimport { closure } from \"./utils\";\n\nimport Ajax from \"./ajax\";\nimport Channel from \"./channel\";\nimport LongPoll from \"./longpoll\";\nimport Serializer from \"./serializer\";\nimport Timer from \"./timer\";\n\n// Type definitions for WebSocket-like transport\ninterface Transport {\n new (url: string, protocols?: string | string[]): TransportInstance;\n name?: string;\n}\n\ntype TransportClass = Transport | typeof LongPoll;\n\n// Generic timer type that works in both Node.js and browser environments\ntype TimerHandle = ReturnType;\n\ninterface TransportInstance {\n binaryType?: string;\n timeout?: number;\n readyState: number;\n bufferedAmount?: number;\n skipHeartbeat?: boolean;\n onopen: ((event: Event) => void) | null;\n onerror: ((event: Event) => void) | null;\n onmessage: ((event: MessageEvent) => void) | null;\n onclose: ((event: CloseEvent) => void) | null;\n send(data: string | ArrayBuffer): void;\n close(code?: number, reason?: string): void;\n}\n\n// Message structure interfaces\ninterface Message {\n topic: string;\n event: string;\n payload: any;\n ref: string | null;\n join_ref?: string | null;\n}\n\ninterface PushData {\n topic: string;\n event: string;\n payload: any;\n ref: string;\n join_ref?: string;\n}\n\n// Callback types\ntype StateChangeCallback = () => void;\ntype ErrorCallback = (\n error: any,\n transport?: Transport,\n establishedConnections?: number,\n) => void;\ntype MessageCallback = (message: Message) => void;\ntype CloseCallback = (event: CloseEvent) => void;\ntype LoggerFunction = (kind: string, msg: string, data?: any) => void;\ntype ReconnectFunction = (tries: number) => number;\ntype EncodeFunction = (\n payload: any,\n callback: (encoded: string | ArrayBuffer) => void,\n) => void;\ntype DecodeFunction = (\n payload: string | ArrayBuffer,\n callback: (decoded: Message) => void,\n) => void;\ntype ParamsFunction = () => Record;\ntype PingCallback = (rtt: number) => void;\n\n// Storage interface\ninterface Storage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n// Socket options interface\ninterface SocketOptions {\n transport?: Transport;\n longPollFallbackMs?: number;\n debug?: boolean;\n encode?: EncodeFunction;\n decode?: DecodeFunction;\n timeout?: number;\n heartbeatIntervalMs?: number;\n reconnectAfterMs?: ReconnectFunction;\n rejoinAfterMs?: ReconnectFunction;\n logger?: LoggerFunction;\n longpollerTimeout?: number;\n params?: Record | ParamsFunction;\n authToken?: string;\n binaryType?: string;\n vsn?: string;\n sessionStorage?: Storage;\n}\n\n// State change callbacks structure\ninterface StateChangeCallbacks {\n open: Array<[string, StateChangeCallback]>;\n close: Array<[string, CloseCallback]>;\n error: Array<[string, ErrorCallback]>;\n message: Array<[string, MessageCallback]>;\n}\n\n/** Initializes the Socket *\n *\n * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"ws://example.com/socket\"`,\n * `\"wss://example.com\"`\n * `\"/socket\"` (inherited host & protocol)\n * @param {Object} [opts] - Optional configuration\n * @param {Function} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.\n *\n * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined.\n * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`.\n *\n * @param {number} [opts.longPollFallbackMs] - The millisecond time to attempt the primary transport\n * before falling back to the LongPoll transport. Disabled by default.\n *\n * @param {boolean} [opts.debug] - When true, enables debug logging. Default false.\n *\n * @param {Function} [opts.encode] - The function to encode outgoing messages.\n *\n * Defaults to JSON encoder.\n *\n * @param {Function} [opts.decode] - The function to decode incoming messages.\n *\n * Defaults to JSON:\n *\n * ```javascript\n * (payload, callback) => callback(JSON.parse(payload))\n * ```\n *\n * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.\n *\n * Defaults `DEFAULT_TIMEOUT`\n * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message\n * @param {Function} [opts.reconnectAfterMs] - The optional function that returns the\n * socket reconnect interval, in milliseconds.\n *\n * Defaults to stepped backoff of:\n *\n * ```javascript\n * function(tries){\n * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n * }\n * ````\n *\n * @param {Function} [opts.rejoinAfterMs] - The optional function that returns the millisec\n * rejoin interval for individual channels.\n *\n * ```javascript\n * function(tries){\n * return [1000, 2000, 5000][tries - 1] || 10000\n * }\n * ````\n *\n * @param {Function} [opts.logger] - The optional function for specialized logging, ie:\n *\n * ```javascript\n * function(kind, msg, data) {\n * console.log(`${kind}: ${msg}`, data)\n * }\n * ```\n *\n * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.\n *\n * Defaults to 20s (double the server long poll timer).\n *\n * @param {(Object|function)} [opts.params] - The optional params to pass when connecting\n * @param {string} [opts.authToken] - the optional authentication token to be exposed on the server\n * under the `:auth_token` connect_info key.\n * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.\n *\n * Defaults to \"arraybuffer\"\n *\n * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect.\n *\n * Defaults to DEFAULT_VSN.\n *\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is\n * useful when Phoenix won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain channel in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] || null }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n */\nexport default class Socket {\n private stateChangeCallbacks: StateChangeCallbacks;\n private channels: Channel[];\n private sendBuffer: Array<() => void>;\n private ref: number;\n private transport: TransportClass;\n private primaryPassedHealthCheck: boolean;\n private longPollFallbackMs: number | null;\n private fallbackTimer: TimerHandle | null;\n private sessionStore: Storage | null;\n private establishedConnections: number;\n private defaultEncoder: EncodeFunction;\n private defaultDecoder: DecodeFunction;\n private closeWasClean: boolean;\n private disconnecting: boolean;\n private binaryType: string;\n private connectClock: number;\n private encode: EncodeFunction;\n private decode: DecodeFunction;\n private heartbeatIntervalMs: number;\n private reconnectAfterMs: ReconnectFunction;\n private logger: LoggerFunction | null;\n private longpollerTimeout: number;\n private params: ParamsFunction;\n private endPoint: string;\n private vsn: string;\n private heartbeatTimeoutTimer: TimerHandle | null;\n private heartbeatTimer: TimerHandle | null;\n private pendingHeartbeatRef: string | null;\n private reconnectTimer: Timer;\n private authToken?: string;\n private conn: TransportInstance | null;\n\n /**\n * @internal\n * @private\n */\n timeout: number;\n\n /**\n * @internal\n * @private\n */\n rejoinAfterMs: ReconnectFunction;\n\n constructor(endPoint: string, opts: SocketOptions = {}) {\n this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };\n this.channels = [];\n this.sendBuffer = [];\n this.ref = 0;\n this.timeout = opts.timeout || DEFAULT_TIMEOUT;\n this.transport = opts.transport || global.WebSocket || LongPoll;\n this.primaryPassedHealthCheck = false;\n this.longPollFallbackMs = opts.longPollFallbackMs ?? null;\n this.fallbackTimer = null;\n this.sessionStore =\n opts.sessionStorage || (global && global.sessionStorage);\n this.establishedConnections = 0;\n this.defaultEncoder = Serializer.encode.bind(Serializer);\n this.defaultDecoder = Serializer.decode.bind(Serializer);\n this.closeWasClean = false;\n this.disconnecting = false;\n this.binaryType = opts.binaryType || \"arraybuffer\";\n this.connectClock = 1;\n this.conn = null;\n\n if (this.transport !== LongPoll) {\n this.encode = opts.encode || this.defaultEncoder;\n this.decode = opts.decode || this.defaultDecoder;\n } else {\n this.encode = this.defaultEncoder;\n this.decode = this.defaultDecoder;\n }\n\n let awaitingConnectionOnPageShow: number | null = null;\n if (phxWindow && phxWindow.addEventListener) {\n phxWindow.addEventListener(\"pagehide\", (_e: Event) => {\n if (this.conn) {\n this.disconnect();\n awaitingConnectionOnPageShow = this.connectClock;\n }\n });\n phxWindow.addEventListener(\"pageshow\", (_e: Event) => {\n if (awaitingConnectionOnPageShow === this.connectClock) {\n awaitingConnectionOnPageShow = null;\n this.connect();\n }\n });\n }\n\n this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;\n this.rejoinAfterMs = (tries: number) => {\n if (opts.rejoinAfterMs) {\n return opts.rejoinAfterMs(tries);\n } else {\n return [1000, 2000, 5000][tries - 1] || 10000;\n }\n };\n this.reconnectAfterMs = (tries: number) => {\n if (opts.reconnectAfterMs) {\n return opts.reconnectAfterMs(tries);\n } else {\n return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000;\n }\n };\n this.logger = opts.logger || null;\n if (!this.logger && opts.debug) {\n this.logger = (kind: string, msg: string, data?: any) => {\n console.log(`${kind}: ${msg}`, data);\n };\n }\n this.longpollerTimeout = opts.longpollerTimeout || 20000;\n this.params = closure(opts.params || {}) as ParamsFunction;\n this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;\n this.vsn = opts.vsn || DEFAULT_VSN;\n this.heartbeatTimeoutTimer = null;\n this.heartbeatTimer = null;\n this.pendingHeartbeatRef = null;\n this.reconnectTimer = new Timer(() => {\n this.teardown(() => this.connect());\n }, this.reconnectAfterMs);\n this.authToken = opts.authToken;\n }\n\n /**\n * Returns the LongPoll transport reference\n */\n getLongPollTransport(): typeof LongPoll {\n return LongPoll;\n }\n\n /**\n * Disconnects and replaces the active transport\n *\n * @param {Function} newTransport - The new transport class to instantiate\n *\n */\n replaceTransport(newTransport: Transport): void {\n this.connectClock++;\n this.closeWasClean = true;\n clearTimeout(this.fallbackTimer!);\n this.reconnectTimer.reset();\n if (this.conn) {\n this.conn.close();\n this.conn = null;\n }\n this.transport = newTransport;\n }\n\n /**\n * Returns the socket protocol\n *\n * @returns {string}\n */\n protocol(): string {\n return location.protocol.match(/^https/) ? \"wss\" : \"ws\";\n }\n\n /**\n * The fully qualified socket url\n *\n * @returns {string}\n */\n endPointURL(): string {\n const uri = Ajax.appendParams(\n Ajax.appendParams(this.endPoint, this.params()),\n { vsn: this.vsn },\n );\n if (uri.charAt(0) !== \"/\") {\n return uri;\n }\n if (uri.charAt(1) === \"/\") {\n return `${this.protocol()}:${uri}`;\n }\n\n return `${this.protocol()}://${location.host}${uri}`;\n }\n\n /**\n * Disconnects the socket\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n *\n * @param {Function} callback - Optional callback which is called after socket is disconnected.\n * @param {integer} code - A status code for disconnection (Optional).\n * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n */\n disconnect(callback?: () => void, code?: number, reason?: string): void {\n this.connectClock++;\n this.disconnecting = true;\n this.closeWasClean = true;\n clearTimeout(this.fallbackTimer!);\n this.reconnectTimer.reset();\n this.teardown(\n () => {\n this.disconnecting = false;\n callback && callback();\n },\n code,\n reason,\n );\n }\n\n /**\n *\n * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n *\n * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n */\n connect(params?: Record): void {\n if (params) {\n console &&\n console.log(\n \"passing params to connect is deprecated. Instead pass :params to the Socket constructor\",\n );\n this.params = closure(params) as ParamsFunction;\n }\n if (this.conn && !this.disconnecting) {\n return;\n }\n if (this.longPollFallbackMs && this.transport !== LongPoll) {\n this.connectWithFallback(LongPoll, this.longPollFallbackMs);\n } else {\n this.transportConnect();\n }\n }\n\n /**\n * Logs the message. Override `this.logger` for specialized logging. noops by default\n * @param {string} kind\n * @param {string} msg\n * @param {Object} data\n */\n log(kind: string, msg: string, data?: any): void {\n this.logger && this.logger(kind, msg, data);\n }\n\n /**\n * Returns true if a logger has been set on this socket.\n */\n hasLogger(): boolean {\n return this.logger !== null;\n }\n\n /**\n * Registers callbacks for connection open events\n *\n * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n *\n * @param {Function} callback\n */\n onOpen(callback: StateChangeCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.open.push([ref, callback]);\n return ref;\n }\n\n /**\n * Registers callbacks for connection close events\n * @param {Function} callback\n */\n onClose(callback: CloseCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.close.push([ref, callback]);\n return ref;\n }\n\n /**\n * Registers callbacks for connection error events\n *\n * @example socket.onError(function(error){ alert(\"An error occurred\") })\n *\n * @param {Function} callback\n */\n onError(callback: ErrorCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.error.push([ref, callback]);\n return ref;\n }\n\n /**\n * Registers callbacks for connection message events\n * @param {Function} callback\n */\n onMessage(callback: MessageCallback): string {\n const ref = this.makeRef();\n this.stateChangeCallbacks.message.push([ref, callback]);\n return ref;\n }\n\n /**\n * Pings the server and invokes the callback with the RTT in milliseconds\n * @param {Function} callback\n *\n * Returns true if the ping was pushed or false if unable to be pushed.\n */\n ping(callback: PingCallback): boolean {\n if (!this.isConnected()) {\n return false;\n }\n const ref = this.makeRef();\n const startTime = Date.now();\n this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: ref });\n const onMsgRef = this.onMessage((msg) => {\n if (msg.ref === ref) {\n this.off([onMsgRef]);\n callback(Date.now() - startTime);\n }\n });\n return true;\n }\n\n /**\n * @internal\n * @private\n */\n transportConnect(): void {\n this.connectClock++;\n this.closeWasClean = false;\n let protocols: string[] | undefined = undefined;\n // Sec-WebSocket-Protocol based token\n // (longpoll uses Authorization header instead)\n if (this.authToken) {\n protocols = [\n \"phoenix\",\n `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`,\n ];\n }\n this.conn = new this.transport(this.endPointURL(), protocols);\n this.conn.binaryType = this.binaryType;\n this.conn.timeout = this.longpollerTimeout;\n this.conn.onopen = () => this.onConnOpen();\n this.conn.onerror = (error) => this.onConnError(error);\n this.conn.onmessage = (event) => this.onConnMessage(event);\n this.conn.onclose = (event) => this.onConnClose(event);\n }\n\n private getSession(key: string): string | null {\n return this.sessionStore && this.sessionStore.getItem(key);\n }\n\n private storeSession(key: string, val: string): void {\n this.sessionStore && this.sessionStore.setItem(key, val);\n }\n\n private connectWithFallback(\n fallbackTransport: Transport,\n fallbackThreshold: number = 2500,\n ): void {\n clearTimeout(this.fallbackTimer!);\n let established = false;\n let primaryTransport = true;\n let openRef: string;\n const fallback = (reason: any) => {\n this.log(\n \"transport\",\n `falling back to ${fallbackTransport.name}...`,\n reason,\n );\n this.off([openRef, errorRef]);\n primaryTransport = false;\n this.replaceTransport(fallbackTransport);\n this.transportConnect();\n };\n if (this.getSession(`phx:fallback:${fallbackTransport.name}`)) {\n return fallback(\"memorized\");\n }\n\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n\n const errorRef = this.onError((reason) => {\n this.log(\"transport\", \"error\", reason);\n if (primaryTransport && !established) {\n clearTimeout(this.fallbackTimer!);\n fallback(reason);\n }\n });\n this.onOpen(() => {\n established = true;\n if (!primaryTransport) {\n // only memorize LP if we never connected to primary\n if (!this.primaryPassedHealthCheck) {\n this.storeSession(`phx:fallback:${fallbackTransport.name}`, \"true\");\n }\n return this.log(\n \"transport\",\n `established ${fallbackTransport.name} fallback`,\n );\n }\n // if we've established primary, give the fallback a new period to attempt ping\n clearTimeout(this.fallbackTimer!);\n this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n this.ping((rtt) => {\n this.log(\"transport\", \"connected to primary after\", rtt);\n this.primaryPassedHealthCheck = true;\n clearTimeout(this.fallbackTimer!);\n });\n });\n this.transportConnect();\n }\n\n private clearHeartbeats(): void {\n clearTimeout(this.heartbeatTimer!);\n clearTimeout(this.heartbeatTimeoutTimer!);\n }\n\n /**\n * @internal\n * @private\n */\n onConnOpen(): void {\n if (this.hasLogger())\n this.log(\n \"transport\",\n `${this.transport.name} connected to ${this.endPointURL()}`,\n );\n this.closeWasClean = false;\n this.disconnecting = false;\n this.establishedConnections++;\n this.flushSendBuffer();\n this.reconnectTimer.reset();\n this.resetHeartbeat();\n this.stateChangeCallbacks.open.forEach(([, callback]) => callback());\n }\n\n private heartbeatTimeout(): void {\n if (this.pendingHeartbeatRef) {\n this.pendingHeartbeatRef = null;\n if (this.hasLogger()) {\n this.log(\n \"transport\",\n \"heartbeat timeout. Attempting to re-establish connection\",\n );\n }\n this.triggerChanError();\n this.closeWasClean = false;\n this.teardown(\n () => this.reconnectTimer.scheduleTimeout(),\n WS_CLOSE_NORMAL,\n \"heartbeat timeout\",\n );\n }\n }\n\n private resetHeartbeat(): void {\n if (this.conn && this.conn.skipHeartbeat) {\n return;\n }\n this.pendingHeartbeatRef = null;\n this.clearHeartbeats();\n this.heartbeatTimer = setTimeout(\n () => this.sendHeartbeat(),\n this.heartbeatIntervalMs,\n );\n }\n\n private teardown(\n callback?: () => void,\n code?: number,\n reason?: string,\n ): void {\n if (!this.conn) {\n return callback && callback();\n }\n const connectClock = this.connectClock;\n\n this.waitForBufferDone(() => {\n if (connectClock !== this.connectClock) {\n return;\n }\n if (this.conn) {\n if (code) {\n this.conn.close(code, reason || \"\");\n } else {\n this.conn.close();\n }\n }\n\n this.waitForSocketClosed(() => {\n if (connectClock !== this.connectClock) {\n return;\n }\n if (this.conn) {\n this.conn.onopen = function () {}; // noop\n this.conn.onerror = function () {}; // noop\n this.conn.onmessage = function () {}; // noop\n this.conn.onclose = function () {}; // noop\n this.conn = null;\n }\n\n callback && callback();\n });\n });\n }\n\n private waitForBufferDone(callback: () => void, tries: number = 1): void {\n if (tries === 5 || !this.conn || !this.conn.bufferedAmount) {\n callback();\n return;\n }\n\n setTimeout(() => {\n this.waitForBufferDone(callback, tries + 1);\n }, 150 * tries);\n }\n\n private waitForSocketClosed(callback: () => void, tries: number = 1): void {\n if (\n tries === 5 ||\n !this.conn ||\n this.conn.readyState === SOCKET_STATES.closed\n ) {\n callback();\n return;\n }\n\n setTimeout(() => {\n this.waitForSocketClosed(callback, tries + 1);\n }, 150 * tries);\n }\n\n /**\n * @internal\n * @private\n */\n onConnClose(event: CloseEvent): void {\n const closeCode = event && event.code;\n if (this.hasLogger()) this.log(\"transport\", \"close\", event);\n this.triggerChanError();\n this.clearHeartbeats();\n if (!this.closeWasClean && closeCode !== 1000) {\n this.reconnectTimer.scheduleTimeout();\n }\n this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));\n }\n\n /**\n * @internal\n * @private\n */\n onConnError(error: Event): void {\n if (this.hasLogger()) this.log(\"transport\", \"error\", error);\n const transportBefore = this.transport;\n const establishedBefore = this.establishedConnections;\n this.stateChangeCallbacks.error.forEach(([, callback]) => {\n callback(error, transportBefore, establishedBefore);\n });\n if (transportBefore === this.transport || establishedBefore > 0) {\n this.triggerChanError();\n }\n }\n\n private triggerChanError(): void {\n this.channels.forEach((channel) => {\n if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {\n channel.trigger(CHANNEL_EVENTS.error, {});\n }\n });\n }\n\n /**\n * @returns {string}\n */\n connectionState(): string {\n switch (this.conn && this.conn.readyState) {\n case SOCKET_STATES.connecting:\n return \"connecting\";\n case SOCKET_STATES.open:\n return \"open\";\n case SOCKET_STATES.closing:\n return \"closing\";\n default:\n return \"closed\";\n }\n }\n\n /**\n * @returns {boolean}\n */\n isConnected(): boolean {\n return this.connectionState() === \"open\";\n }\n\n /**\n * @internal\n * @private\n *\n * @param {Channel}\n */\n remove(channel: Channel): void {\n this.off(channel.stateChangeRefs);\n this.channels = this.channels.filter((c) => c !== channel);\n }\n\n /**\n * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n *\n * @param {refs} - list of refs returned by calls to\n * `onOpen`, `onClose`, `onError,` and `onMessage`\n */\n off(refs: string[]): void {\n const filter = (callbacks: Array<[string, any]>) =>\n callbacks.filter(([ref]) => refs.indexOf(ref) === -1);\n\n this.stateChangeCallbacks = {\n open: filter(this.stateChangeCallbacks.open),\n close: filter(this.stateChangeCallbacks.close),\n error: filter(this.stateChangeCallbacks.error),\n message: filter(this.stateChangeCallbacks.message),\n };\n }\n\n /**\n * Initiates a new channel for the given topic\n *\n * @param {string} topic\n * @param {Object} chanParams - Parameters for the channel\n * @returns {Channel}\n */\n channel(topic: string, chanParams: Record = {}): Channel {\n const chan = new Channel(topic, chanParams, this);\n this.channels.push(chan);\n return chan;\n }\n\n /**\n * @param {Object} data\n */\n push(data: PushData): void {\n if (this.hasLogger()) {\n const { topic, event, payload, ref, join_ref } = data;\n this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload);\n }\n\n if (this.isConnected()) {\n this.encode(data, (result) => this.conn!.send(result));\n } else {\n this.sendBuffer.push(() =>\n this.encode(data, (result) => this.conn!.send(result)),\n );\n }\n }\n\n /**\n * Return the next message ref, accounting for overflows\n * @returns {string}\n */\n makeRef(): string {\n const newRef = this.ref + 1;\n if (newRef === this.ref) {\n this.ref = 0;\n } else {\n this.ref = newRef;\n }\n\n return this.ref.toString();\n }\n\n /**\n * @internal\n * @private\n */\n sendHeartbeat(): void {\n if (this.pendingHeartbeatRef && !this.isConnected()) {\n return;\n }\n this.pendingHeartbeatRef = this.makeRef();\n this.push({\n topic: \"phoenix\",\n event: \"heartbeat\",\n payload: {},\n ref: this.pendingHeartbeatRef,\n });\n this.heartbeatTimeoutTimer = setTimeout(\n () => this.heartbeatTimeout(),\n this.heartbeatIntervalMs,\n );\n }\n\n /**\n * @internal\n * @private\n */\n flushSendBuffer(): void {\n if (this.isConnected() && this.sendBuffer.length > 0) {\n this.sendBuffer.forEach((callback) => callback());\n this.sendBuffer = [];\n }\n }\n\n /**\n * @internal\n * @private\n */\n onConnMessage(rawMessage: MessageEvent): void {\n this.decode(rawMessage.data, (msg) => {\n const { topic, event, payload, ref, join_ref } = msg;\n if (ref && ref === this.pendingHeartbeatRef) {\n this.clearHeartbeats();\n this.pendingHeartbeatRef = null;\n this.heartbeatTimer = setTimeout(\n () => this.sendHeartbeat(),\n this.heartbeatIntervalMs,\n );\n }\n\n if (this.hasLogger())\n this.log(\n \"receive\",\n `${payload.status || \"\"} ${topic} ${event} ${(ref && \"(\" + ref + \")\") || \"\"}`,\n payload,\n );\n\n for (let i = 0; i < this.channels.length; i++) {\n const channel = this.channels[i];\n if (!channel.isMember(topic, event, payload, join_ref)) {\n continue;\n }\n channel.trigger(event, payload, ref, join_ref);\n }\n\n for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {\n const [, callback] = this.stateChangeCallbacks.message[i];\n callback(msg);\n }\n });\n }\n\n /**\n * @internal\n * @private\n */\n leaveOpenTopic(topic: string): void {\n const dupChannel = this.channels.find(\n (c) => c.topic === topic && (c.isJoined() || c.isJoining()),\n );\n if (dupChannel) {\n if (this.hasLogger())\n this.log(\"transport\", `leaving duplicate topic \"${topic}\"`);\n dupChannel.leave();\n }\n }\n}\n"], + "mappings": "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", + "names": [] } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..8762bf86de --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "ESNext", + "moduleResolution": "node", + "lib": ["ES2018", "DOM"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./assets/dist", + "rootDir": "./assets/js", + "strict": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": [ + "assets/js/**/*" + ], + "exclude": [ + "node_modules", + "assets/dist" + ] +} \ No newline at end of file diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000000..0589f27e6c --- /dev/null +++ b/typedoc.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./assets/js/phoenix/index.ts"], + "out": "doc/js", + "highlightLanguages": [ + "bash", + "console", + "css", + "html", + "javascript", + "json", + "typescript", + "elixir" + ] +}