UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

1,503 lines (1,500 loc) • 810 kB
"use strict"; (() => { var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // vendor/topbar.js var require_topbar = __commonJS({ "vendor/topbar.js"(exports, module) { "use strict"; (function(window2, document2) { "use strict"; (function() { var lastTime = 0; var vendors = ["ms", "moz", "webkit", "o"]; for (var x = 0; x < vendors.length && !window2.requestAnimationFrame; ++x) { window2.requestAnimationFrame = window2[vendors[x] + "RequestAnimationFrame"]; window2.cancelAnimationFrame = window2[vendors[x] + "CancelAnimationFrame"] || window2[vendors[x] + "CancelRequestAnimationFrame"]; } if (!window2.requestAnimationFrame) window2.requestAnimationFrame = function(callback, element) { var currTime = (/* @__PURE__ */ new Date()).getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window2.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window2.cancelAnimationFrame) window2.cancelAnimationFrame = function(id) { clearTimeout(id); }; })(); var canvas, currentProgress, showing, progressTimerId = null, fadeTimerId = null, delayTimerId = null, addEvent = function(elem, type, handler) { if (elem.addEventListener) elem.addEventListener(type, handler, false); else if (elem.attachEvent) elem.attachEvent("on" + type, handler); else elem["on" + type] = handler; }, options = { autoRun: true, barThickness: 3, barColors: { 0: "rgba(26, 188, 156, .9)", ".25": "rgba(52, 152, 219, .9)", ".50": "rgba(241, 196, 15, .9)", ".75": "rgba(230, 126, 34, .9)", "1.0": "rgba(211, 84, 0, .9)" }, shadowBlur: 10, shadowColor: "rgba(0, 0, 0, .6)", className: null }, repaint = function() { canvas.width = window2.innerWidth; canvas.height = options.barThickness * 5; var ctx = canvas.getContext("2d"); ctx.shadowBlur = options.shadowBlur; ctx.shadowColor = options.shadowColor; var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); for (var stop in options.barColors) lineGradient.addColorStop(stop, options.barColors[stop]); ctx.lineWidth = options.barThickness; ctx.beginPath(); ctx.moveTo(0, options.barThickness / 2); ctx.lineTo( Math.ceil(currentProgress * canvas.width), options.barThickness / 2 ); ctx.strokeStyle = lineGradient; ctx.stroke(); }, createCanvas = function() { canvas = document2.createElement("canvas"); var style = canvas.style; style.position = "fixed"; style.top = style.left = style.right = style.margin = style.padding = 0; style.zIndex = 100001; style.display = "none"; if (options.className) canvas.classList.add(options.className); document2.body.appendChild(canvas); addEvent(window2, "resize", repaint); }, topbar2 = { config: function(opts) { for (var key in opts) if (options.hasOwnProperty(key)) options[key] = opts[key]; }, show: function(delay) { if (showing) return; if (delay) { if (delayTimerId) return; delayTimerId = setTimeout(() => topbar2.show(), delay); } else { showing = true; if (fadeTimerId !== null) window2.cancelAnimationFrame(fadeTimerId); if (!canvas) createCanvas(); canvas.style.opacity = 1; canvas.style.display = "block"; topbar2.progress(0); if (options.autoRun) { (function loop() { progressTimerId = window2.requestAnimationFrame(loop); topbar2.progress( "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) ); })(); } } }, progress: function(to) { if (typeof to === "undefined") return currentProgress; if (typeof to === "string") { to = (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 ? currentProgress : 0) + parseFloat(to); } currentProgress = to > 1 ? 1 : to; repaint(); return currentProgress; }, hide: function() { clearTimeout(delayTimerId); delayTimerId = null; if (!showing) return; showing = false; if (progressTimerId != null) { window2.cancelAnimationFrame(progressTimerId); progressTimerId = null; } (function loop() { if (topbar2.progress("+.1") >= 1) { canvas.style.opacity -= 0.05; if (canvas.style.opacity <= 0.05) { canvas.style.display = "none"; fadeTimerId = null; return; } } fadeTimerId = window2.requestAnimationFrame(loop); })(); } }; if (typeof module === "object" && typeof module.exports === "object") { module.exports = topbar2; } else if (typeof define === "function" && define.amd) { define(function() { return topbar2; }); } else { this.topbar = topbar2; } }).call(exports, window, document); } }); // ../deps/phoenix_html/priv/static/phoenix_html.js (function() { var PolyfillEvent = eventConstructor(); function eventConstructor() { if (typeof window.CustomEvent === "function") return window.CustomEvent; function CustomEvent2(event, params) { params = params || { bubbles: false, cancelable: false, detail: void 0 }; var evt = document.createEvent("CustomEvent"); evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); return evt; } CustomEvent2.prototype = window.Event.prototype; return CustomEvent2; } function buildHiddenInput(name, value) { var input = document.createElement("input"); input.type = "hidden"; input.name = name; input.value = value; return input; } function handleClick(element, targetModifierKey) { var to = element.getAttribute("data-to"), method = buildHiddenInput("_method", element.getAttribute("data-method")), csrf = buildHiddenInput("_csrf_token", element.getAttribute("data-csrf")), form = document.createElement("form"), submit = document.createElement("input"), target = element.getAttribute("target"); form.method = element.getAttribute("data-method") === "get" ? "get" : "post"; form.action = to; form.style.display = "none"; if (target) form.target = target; else if (targetModifierKey) form.target = "_blank"; form.appendChild(csrf); form.appendChild(method); document.body.appendChild(form); submit.type = "submit"; form.appendChild(submit); submit.click(); } window.addEventListener("click", function(e) { var element = e.target; if (e.defaultPrevented) return; while (element && element.getAttribute) { var phoenixLinkEvent = new PolyfillEvent("phoenix.link.click", { "bubbles": true, "cancelable": true }); if (!element.dispatchEvent(phoenixLinkEvent)) { e.preventDefault(); e.stopImmediatePropagation(); return false; } if (element.getAttribute("data-method") && element.getAttribute("data-to")) { handleClick(element, e.metaKey || e.shiftKey); e.preventDefault(); return false; } else { element = element.parentNode; } } }, false); window.addEventListener("phoenix.link.click", function(e) { var message = e.target.getAttribute("data-confirm"); if (message && !window.confirm(message)) { e.preventDefault(); } }, false); })(); // ../deps/phoenix/priv/static/phoenix.mjs var closure = (value) => { if (typeof value === "function") { return value; } else { let closure22 = function() { return value; }; return closure22; } }; var globalSelf = typeof self !== "undefined" ? self : null; var phxWindow = typeof window !== "undefined" ? window : null; var global = globalSelf || phxWindow || global; var DEFAULT_VSN = "2.0.0"; var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; var DEFAULT_TIMEOUT = 1e4; var WS_CLOSE_NORMAL = 1e3; var CHANNEL_STATES = { closed: "closed", errored: "errored", joined: "joined", joining: "joining", leaving: "leaving" }; var CHANNEL_EVENTS = { close: "phx_close", error: "phx_error", join: "phx_join", reply: "phx_reply", leave: "phx_leave" }; var TRANSPORTS = { longpoll: "longpoll", websocket: "websocket" }; var XHR_STATES = { complete: 4 }; var Push = class { 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 }); } }; var Timer = class { 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)); } }; var Channel = class { 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; } }; var Ajax = class { static request(method, endPoint, accept, body, timeout, ontimeout, callback) { if (global.XDomainRequest) { let req = new global.XDomainRequest(); return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback); } else { let req = new global.XMLHttpRequest(); return this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback); } } 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; } req.onprogress = () => { }; req.send(body); return req; } static xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) { req.open(method, endPoint, true); req.timeout = timeout; req.setRequestHeader("Content-Type", accept); 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 (e) { 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)}`; } }; var 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); }; var LongPoll = class { constructor(endPoint) { this.endPoint = null; this.token = null; this.skipHeartbeat = true; this.reqs = /* @__PURE__ */ new Set(); this.awaitingBatchAck = false; this.currentBatch = null; this.currentBatchTimer = null; this.batchBuffer = []; this.onopen = function() { }; this.onerror = function() { }; this.onmessage = function() { }; this.onclose = function() { }; this.pollEndpoint = this.normalizeEndpoint(endPoint); this.readyState = SOCKET_STATES.connecting; 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() { this.ajax("GET", "application/json", null, () => this.ontimeout(), (resp) => { if (resp) { var { status, token, messages } = resp; this.token = token; } else { status = 0; } switch (status) { case 200: messages.forEach((msg) => { 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", "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: 1e3, reason: void 0, 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, contentType, body, onCallerTimeout, callback) { let req; let ontimeout = () => { this.reqs.delete(req); onCallerTimeout(); }; req = Ajax.request(method, this.endpointURL(), contentType, body, this.timeout, ontimeout, (resp) => { this.reqs.delete(req); if (this.isActive()) { callback(resp); } }); this.reqs.add(req); } }; var serializer_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); 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; 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, 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, topic, event: CHANNEL_EVENTS.reply, 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, event, payload: data }; } }; var Socket = class { 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_default.encode.bind(serializer_default); this.defaultDecoder = serializer_default.decode.bind(serializer_default); 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 || 3e4; this.rejoinAfterMs = (tries) => { if (opts.rejoinAfterMs) { return opts.rejoinAfterMs(tries); } else { return [1e3, 2e3, 5e3][tries - 1] || 1e4; } }; this.reconnectAfterMs = (tries) => { if (opts.reconnectAfterMs) { return opts.reconnectAfterMs(tries); } else { return [10, 50, 100, 150, 200, 250, 500, 1e3, 2e3][tries - 1] || 5e3; } }; this.logger = opts.logger || null; if (!this.logger && opts.debug) { this.logger = (kind, msg, data) => { console.log(`${kind}: ${msg}`, data); }; } this.longpollerTimeout = opts.longpollerTimeout || 2e4; 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); } /** * 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 }); 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; this.conn = new this.transport(this.endPointURL()); 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) { if (!this.primaryPassedHealthCheck) { this.storeSession(`phx:fallback:${fallbackTransport.name}`, "true"); } return this.log("transport", `established ${fallbackTransport.name} fallback`); } 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() { }; this.conn.onerror = function() { }; this.conn.onmessage = function() { }; this.conn.onclose = function() { }; 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 !== 1e3) { this.reconnectTimer.scheduleTimeout(); } this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event)); } /** * @private */ onConnError(error) { if (this.hasLogger()) this.l