diff --git a/.changeset/soft-cars-return.md b/.changeset/soft-cars-return.md new file mode 100644 index 0000000..a9365e0 --- /dev/null +++ b/.changeset/soft-cars-return.md @@ -0,0 +1,7 @@ +--- +"@tapsioss/react-client-socket-manager": patch +"@tapsioss/client-socket-manager": patch +--- + +Sync devtool with socket instance. + \ No newline at end of file diff --git a/packages/core/src/ClientSocketManager.test.ts b/packages/core/src/ClientSocketManager.test.ts index f6a38e5..97bacc7 100644 --- a/packages/core/src/ClientSocketManager.test.ts +++ b/packages/core/src/ClientSocketManager.test.ts @@ -306,7 +306,7 @@ describe("ClientSocketManager: unit tests", () => { expect(devtool.getDevtoolElement()).not.toBeNull(); }); - it("should update devtool ui when socket was updated", async () => { + it("should be synchronized with the devtool", async () => { const connectResolver = createPromiseResolvers(); const initResolver = createPromiseResolvers(); const initMessageResolver = createPromiseResolvers(); @@ -332,9 +332,8 @@ describe("ClientSocketManager: unit tests", () => { }, }); - // at first the devtool should be in the dom but because no we have no logs or channels, these sections shouldn't exist. + // at first the devtool should be in the dom but because we have no logs or channels, these sections shouldn't exist. expect(devtool.getDevtoolElement()).not.toBeNull(); - expect(devtool.getDevtoolChannelsElement()).toBeNull(); expect(devtool.getDevtoolInfoElement()).not.toBeNull(); expect(devtool.getDevtoolStatusElement()?.innerHTML).not.toEqual( devtool.Status.CONNECTED, @@ -350,11 +349,6 @@ describe("ClientSocketManager: unit tests", () => { await initResolver.promise; // subscribing to a channel... - expect(devtool.getDevtoolLogSectionElement()!.innerHTML).not.toContain( - devtool.LogType.SUBSCRIBED, - ); - expect(devtool.getDevtoolChannelsElement()).toBeNull(); - socketManager.subscribe("test/init", () => {}); expect(devtool.getDevtoolChannelsElement()).not.toBeNull(); diff --git a/packages/core/src/ClientSocketManager.ts b/packages/core/src/ClientSocketManager.ts index 171ef45..0beef5c 100644 --- a/packages/core/src/ClientSocketManager.ts +++ b/packages/core/src/ClientSocketManager.ts @@ -52,8 +52,8 @@ class ClientSocketManager< this._inputListeners.onInit?.call(this); + devtool.setZIndex(devtoolZIndex); if (devtoolEnabled) { - devtool.setZIndex(devtoolZIndex); this.showDevtool(); } } catch (err) { @@ -78,10 +78,8 @@ class ClientSocketManager< this._socket.on(SocketReservedEvents.CONNECTION, () => { this._inputListeners.onSocketConnection?.call(this); - devtool.render({ - action: s => { - s.status = devtool.Status.CONNECTED; - }, + devtool.update(s => { + s.status = devtool.Status.CONNECTED; }); }); @@ -95,10 +93,8 @@ class ClientSocketManager< this._socket.on(SocketReservedEvents.DISCONNECTION, (reason, details) => { this._inputListeners.onSocketDisconnection?.call(this, reason, details); - devtool.render({ - action: s => { - s.status = devtool.Status.DISCONNECTED; - }, + devtool.update(s => { + s.status = devtool.Status.DISCONNECTED; }); if (!this.autoReconnectable) { @@ -125,14 +121,12 @@ class ClientSocketManager< manager.on(ManagerReservedEvents.CONNECTION_ERROR, error => { onConnectionError?.call(this, error); - devtool.render({ - action: s => { - s.logs.enqueue({ - type: devtool.LogType.CONNECTION_ERROR, - date: new Date(), - detail: error.message, - }); - }, + devtool.update(s => { + s.logs.enqueue({ + type: devtool.LogType.CONNECTION_ERROR, + date: new Date(), + detail: error.message, + }); }); }); @@ -142,54 +136,46 @@ class ClientSocketManager< manager.on(ManagerReservedEvents.RECONNECTING, attempt => { onReconnecting?.call(this, attempt); - devtool.render({ - action: s => { - s.status = devtool.Status.RECONNECTING; - s.logs.enqueue({ - type: devtool.LogType.RECONNECTING, - date: new Date(), - detail: `Reconnecting... (${attempt} attempt(s))`, - }); - }, + devtool.update(s => { + s.status = devtool.Status.RECONNECTING; + s.logs.enqueue({ + type: devtool.LogType.RECONNECTING, + date: new Date(), + detail: `Reconnecting... (${attempt} attempt(s))`, + }); }); }); manager.on(ManagerReservedEvents.RECONNECTING_ERROR, error => { onReconnectingError?.call(this, error); - devtool.render({ - action: s => { - s.logs.enqueue({ - type: devtool.LogType.RECONNECTING_ERROR, - date: new Date(), - detail: error.message, - }); - }, + devtool.update(s => { + s.logs.enqueue({ + type: devtool.LogType.RECONNECTING_ERROR, + date: new Date(), + detail: error.message, + }); }); }); manager.on(ManagerReservedEvents.RECONNECTION_FAILURE, () => { onReconnectionFailure?.call(this); - devtool.render({ - action: s => { - s.logs.enqueue({ - type: devtool.LogType.RECONNECTION_FAILURE, - date: new Date(), - detail: `Failed to reconnect.`, - }); - }, + devtool.update(s => { + s.logs.enqueue({ + type: devtool.LogType.RECONNECTION_FAILURE, + date: new Date(), + detail: `Failed to reconnect.`, + }); }); }); manager.on(ManagerReservedEvents.SUCCESSFUL_RECONNECTION, attempt => { onSuccessfulReconnection?.call(this, attempt); - devtool.render({ - action: s => { - s.logs.enqueue({ - type: devtool.LogType.SUCCESSFUL_RECONNECTION, - date: new Date(), - detail: `Successfully connected after ${attempt} attempt(s)`, - }); - }, + devtool.update(s => { + s.logs.enqueue({ + type: devtool.LogType.SUCCESSFUL_RECONNECTION, + date: new Date(), + detail: `Successfully connected after ${attempt} attempt(s)`, + }); }); }); } @@ -354,15 +340,13 @@ class ClientSocketManager< onSubscriptionComplete?.call(this, channel); - devtool.render({ - action: s => { - s.channels.add(channel); - s.logs.enqueue({ - type: devtool.LogType.SUBSCRIBED, - date: new Date(), - detail: `subscribed to \`${channel}\` channel`, - }); - }, + devtool.update(s => { + s.channels.add(channel); + s.logs.enqueue({ + type: devtool.LogType.SUBSCRIBED, + date: new Date(), + detail: `subscribed to \`${channel}\` channel`, + }); }); } @@ -387,15 +371,13 @@ class ClientSocketManager< if (cb) this._socket.off(channel, cb); else this._socket.off(channel); - devtool.render({ - action: s => { - s.channels.delete(channel); - s.logs.enqueue({ - type: devtool.LogType.UNSUBSCRIBED, - date: new Date(), - detail: `unsubscribed from \`${channel}\` channel`, - }); - }, + devtool.update(s => { + s.channels.delete(channel); + s.logs.enqueue({ + type: devtool.LogType.UNSUBSCRIBED, + date: new Date(), + detail: `unsubscribed from \`${channel}\` channel`, + }); }); } @@ -407,14 +389,12 @@ class ClientSocketManager< this._socket?.connect(); - devtool.render({ - action: s => { - s.logs.enqueue({ - type: devtool.LogType.CONNECTED, - date: new Date(), - detail: `socket was conneced manually`, - }); - }, + devtool.update(s => { + s.logs.enqueue({ + type: devtool.LogType.CONNECTED, + date: new Date(), + detail: `socket was conneced manually`, + }); }); } @@ -430,14 +410,12 @@ class ClientSocketManager< this._socket?.disconnect(); - devtool.render({ - action: s => { - s.logs.enqueue({ - type: devtool.LogType.DISCONNECTED, - date: new Date(), - detail: `socket was disconneced manually`, - }); - }, + devtool.update(s => { + s.logs.enqueue({ + type: devtool.LogType.DISCONNECTED, + date: new Date(), + detail: `socket was disconneced manually`, + }); }); } @@ -468,14 +446,14 @@ class ClientSocketManager< * Show devtool in the browser programmatically. */ public showDevtool(): void { - devtool.render({ force: true }); + devtool.show(); } /** * Hide devtool in the browser programmatically. */ public hideDevtool(): void { - devtool.dispose(); + devtool.hide(); } } diff --git a/packages/core/src/devtool/devtool.test.ts b/packages/core/src/devtool/devtool.test.ts index 9182be2..003ee7f 100644 --- a/packages/core/src/devtool/devtool.test.ts +++ b/packages/core/src/devtool/devtool.test.ts @@ -11,14 +11,7 @@ import * as devtool from "./devtool.ts"; describe("Devtool", () => { beforeEach(() => { devtool.setZIndex(99999); - devtool.render({ - force: true, - action: s => { - s.channels.clear(); - s.status = devtool.Status.UNKNOWN; - s.logs.clear(); - }, - }); + devtool.show(); }); afterEach(() => { @@ -36,22 +29,19 @@ describe("Devtool", () => { it("should not double-init", () => { const originalWrapper = devtool.getDevtoolWrapperElement(); - devtool.render(); // re-init should do nothing expect(document.querySelectorAll(`#${DEVTOOL_WRAPPER_ID}`)).toHaveLength(1); expect(devtool.getDevtoolWrapperElement()).toBe(originalWrapper); }); it("should dispose properly", () => { - devtool.dispose(); + devtool.hide(); expect(devtool.getDevtoolWrapperElement()).toBeNull(); }); it("should be able to update status of the socket using render", () => { Object.values(Status).forEach(status => { - devtool.render({ - action: state => { - state.status = status; - }, + devtool.update(state => { + state.status = status; }); expect(devtool.getDevtoolInfoElement()!.innerHTML).toContain(status); @@ -65,12 +55,10 @@ describe("Devtool", () => { date: new Date(), }; - devtool.render({ - action: state => { - state.logs.enqueue(mockLog); - state.channels.add("my-channel"); - state.status = Status.DISCONNECTED; - }, + devtool.update(state => { + state.logs.enqueue(mockLog); + state.channels.add("my-channel"); + state.status = Status.DISCONNECTED; }); const info = devtool.getDevtoolInfoElement(); @@ -97,14 +85,12 @@ describe("Devtool", () => { // Fill the logs for (let i = 0; i < LOG_CAPACITY; i++) { - devtool.render({ - action: state => { - state.logs.enqueue({ - type: LogType.CONNECTION_ERROR, - detail: `log-${i}`, - date: new Date(), - }); - }, + devtool.update(state => { + state.logs.enqueue({ + type: LogType.CONNECTION_ERROR, + detail: `log-${i}`, + date: new Date(), + }); }); } @@ -118,14 +104,12 @@ describe("Devtool", () => { expect(devtool.getDevtoolLogSectionElement()!.innerHTML).toContain(`log-0`); // after adding new log, the first element will not be available and the new log will append to the queue. - devtool.render({ - action: state => { - state.logs.enqueue({ - type: LogType.CONNECTION_ERROR, - detail: `log-${LOG_CAPACITY}`, - date: new Date(), - }); - }, + devtool.update(state => { + state.logs.enqueue({ + type: LogType.CONNECTION_ERROR, + detail: `log-${LOG_CAPACITY}`, + date: new Date(), + }); }); expect(devtool.getDevtoolLogSectionElement()!.innerHTML).not.toContain( diff --git a/packages/core/src/devtool/devtool.ts b/packages/core/src/devtool/devtool.ts index 84ac623..a320b68 100644 --- a/packages/core/src/devtool/devtool.ts +++ b/packages/core/src/devtool/devtool.ts @@ -19,6 +19,7 @@ import { import { FixedQueue } from "./FixedQueue.ts"; import { type DevtoolState, type Log } from "./types.ts"; import { + formatDate, generateAttributes, generateInlineStyle, makeElementDraggable, @@ -152,7 +153,7 @@ export const renderLog = (log: Log) => { return `
-

${log.date.toISOString()}

+

${formatDate(log.date)}

${log.type}

${log.detail}

@@ -276,7 +277,51 @@ export const updateInfoSection = () => { return infoSection; }; -const init = () => { +export const setZIndex = (z: number) => { + zIndex = z; +}; + +export const hide = () => { + getDevtoolWrapperElement()?.remove(); + + active = false; + expanded = false; +}; + +export const dispose = () => { + update(s => { + s.channels.clear(); + s.logs.clear(); + s.status = Status.UNKNOWN; + }); + hide(); +}; + +const toggle = () => { + const socketIcon = getDevtoolSocketIconElement()!; + const closeIcon = getDevtoolCloseIconElement()!; + const info = getDevtoolInfoElement()!; + + expanded = !expanded; + socketIcon.style.opacity = !expanded ? "1" : "0"; + closeIcon.style.opacity = expanded ? "1" : "0"; + info.style.opacity = expanded ? "1" : "0"; + info.style.transform = `scale(${expanded ? "1" : "0"})`; + getDevtoolInfoElement()?.setAttribute( + "data-open", + expanded ? "true" : "false", + ); +}; + +export const update = (cb: (s: typeof devtool) => void) => { + cb?.(devtool); + + if (active) { + updateInfoSection(); + } +}; + +export const show = () => { if (active) return; active = true; @@ -289,6 +334,12 @@ const init = () => { devtoolWrapper.style.zIndex = `${zIndex}`; } + if (Number.isNaN(zIndex)) { + throw new Error("No z-index was set for the devtool."); + } else { + devtoolWrapper.style.zIndex = `${zIndex}`; + } + devtoolWrapper.style.position = "fixed"; devtoolWrapper.style.top = "8px"; devtoolWrapper.style.left = "8px"; @@ -305,6 +356,11 @@ const init = () => { makeElementDraggable(iconButton, devtoolWrapper); } + if (iconButton) { + iconButton.addEventListener("click", toggle); + makeElementDraggable(iconButton, devtoolWrapper); + } + [DEVTOOL_CLOSE_ICON_ID, DEVTOOL_SOCKET_ICON_ID].forEach(icon => { const buttonIcon = document.getElementById(icon); @@ -320,54 +376,7 @@ const init = () => { } } }); -}; - -export const setZIndex = (z: number) => { - zIndex = z; -}; - -export const dispose = () => { - getDevtoolWrapperElement()?.remove(); - - active = false; - expanded = false; -}; - -const toggle = () => { - const socketIcon = getDevtoolSocketIconElement()!; - const closeIcon = getDevtoolCloseIconElement()!; - const info = getDevtoolInfoElement()!; - - expanded = !expanded; - socketIcon.style.opacity = !expanded ? "1" : "0"; - closeIcon.style.opacity = expanded ? "1" : "0"; - info.style.opacity = expanded ? "1" : "0"; - info.style.transform = `scale(${expanded ? "1" : "0"})`; - getDevtoolInfoElement()?.setAttribute( - "data-open", - expanded ? "true" : "false", - ); -}; - -type RenderOptions = { - action?: (s: typeof devtool) => void; - force?: boolean; -}; - -export const render = (options?: RenderOptions) => { - const { action, force = false } = options ?? {}; - - if (force) { - init(); - } else { - if (!active) return; - - const devtoolElement = getDevtoolElement(); - - if (!devtoolElement) init(); - } - action?.(devtool); updateInfoSection(); }; diff --git a/packages/core/src/devtool/utils.ts b/packages/core/src/devtool/utils.ts index 4485969..4f88c3d 100644 --- a/packages/core/src/devtool/utils.ts +++ b/packages/core/src/devtool/utils.ts @@ -140,3 +140,11 @@ export const makeElementDraggable = ( document.addEventListener("mouseup", handleDragEnd, false); document.addEventListener("touchend", handleDragEnd, false); }; + +export const formatDate = (date: Date) => { + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + + return `${hours}:${minutes}:${seconds}`; +}; diff --git a/playground/client/index.ts b/playground/client/index.ts index 25ec030..543c727 100644 --- a/playground/client/index.ts +++ b/playground/client/index.ts @@ -5,7 +5,8 @@ import { ClientSocketManager } from "@tapsioss/client-socket-manager"; const socketManager = new ClientSocketManager("http://localhost:3000", { devtool: { - enabled: true, + enabled: false, + zIndex: 2000, }, eventHandlers: { onReconnectingError(err) {