diff --git a/.changeset/ninety-friends-hug.md b/.changeset/ninety-friends-hug.md
new file mode 100644
index 0000000..fe41417
--- /dev/null
+++ b/.changeset/ninety-friends-hug.md
@@ -0,0 +1,6 @@
+---
+"@tapsioss/client-socket-manager": minor
+---
+
+Add `hideDevtool` and `showDevtool` methods to the `ClientSocketManager`.
+
\ No newline at end of file
diff --git a/packages/core/README.md b/packages/core/README.md
index ff9d0a1..fde0365 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -11,7 +11,7 @@ more abstracted and opinionated manner.
-
+---
`ClientSocketManager` is a flexible and robust manager for handling socket
connections using `socket.io-client`. It provides easy setup and management of
@@ -67,12 +67,12 @@ socketManager.subscribe("message", msg => {
constructor(uri: string, options?: ClientSocketManagerOptions)
```
-#### Parameters:
+#### Parameters
- `uri`: The URI of the socket server.
- `options`: (optional): Configuration options for the socket connection.
-##### Options:
+##### Options
We have extended
[socket-io's options](https://socket.io/docs/v4/client-options/) to include
@@ -99,7 +99,7 @@ additional options:
useful for development and debugging purposes. In production environments,
it's recommended to leave this disabled.
-### Properties:
+### Properties
#### `id: string | null`
@@ -122,9 +122,9 @@ Whether the connection state was recovered after a temporary disconnection.
Whether the Socket will try to reconnect when its Manager connects or
reconnects.
-### Methods:
+### Methods
-#### `emit`:
+#### `emit`
```ts
emit>(
@@ -135,12 +135,12 @@ emit>(
Emits an event to the socket identified by the channel name.
-##### Parameters:
+##### Parameters
- `channel`: The name of the channel to emit the event to.
- `args`: The arguments to pass with the event.
-#### `subscribe`:
+#### `subscribe`
```ts
subscribe>(
@@ -156,7 +156,7 @@ subscribe>(
Subscribes to a specified channel with a callback function. Ensures that only
one listener exists per channel.
-##### Parameters:
+##### Parameters
- `channel`: The name of the channel to subscribe to.
- `cb`: The callback function to invoke when a message is received on the
@@ -166,7 +166,7 @@ one listener exists per channel.
subscription is complete.
- `signal`: The `AbortSignal` to unsubscribe the listener upon abortion.
-#### `unsubscribe`:
+#### `unsubscribe`
```ts
unsubscribe>(
@@ -178,12 +178,12 @@ unsubscribe>(
Removes the listener for the specified channel. If no callback is provided, it
removes all listeners for that channel.
-##### Parameters:
+##### Parameters
- `channel`: The name of the channel whose listener should be deleted.
- `cb` (optional): The subscriber callback function to remove.
-#### `connect`:
+#### `connect`
```ts
connect(): void;
@@ -191,7 +191,7 @@ connect(): void;
Manually connects/reconnects the socket.
-#### `disconnect`:
+#### `disconnect`
```ts
disconnect(): void;
@@ -201,7 +201,7 @@ Manually disconnects the socket. In that case, the socket will not try to
reconnect. If this is the last active Socket instance of the Manager, the
low-level connection will be closed.
-#### `dispose`:
+#### `dispose`
```ts
dispose(): void;
@@ -210,6 +210,22 @@ dispose(): void;
Disposes of the socket, manager, and engine, ensuring all connections are closed
and cleaned up.
+#### `showDevtool`
+
+```ts
+showDevtool(): void;
+```
+
+Show devtool in the browser programmatically.
+
+#### `hideDevtool`
+
+```ts
+hideDevtool(): void;
+```
+
+Hide devtool in the browser programmatically.
+
## `ClientSocketManagerStub`
The package also exports a stubbed version of the socket manager for use in
diff --git a/packages/core/src/ClientSocketManager.test.ts b/packages/core/src/ClientSocketManager.test.ts
index c44ae7f..ed4582b 100644
--- a/packages/core/src/ClientSocketManager.test.ts
+++ b/packages/core/src/ClientSocketManager.test.ts
@@ -328,9 +328,8 @@ describe("ClientSocketManager: unit tests", () => {
},
});
- // at first the devtool should be in the dom but because no we have lo logs or channels, these sections shouldn't exist.
+ // at first the devtool should be in the dom but because no we have no logs or channels, these sections shouldn't exist.
expect(devtool.getDevtoolElement()).not.toBeNull();
- expect(devtool.getDevtoolLogSectionElement()).toBeNull();
expect(devtool.getDevtoolChannelsElement()).toBeNull();
expect(devtool.getDevtoolInfoElement()).not.toBeNull();
expect(devtool.getDevtoolStatusElement()?.innerHTML).not.toEqual(
@@ -372,6 +371,21 @@ describe("ClientSocketManager: unit tests", () => {
);
expect(devtool.getDevtoolChannelsElement()).toBeNull();
+ // hiding devtool hide the devtool from the browser...
+ socketManager.hideDevtool();
+ expect(devtool.getDevtoolElement()).toBeNull();
+ // ... but the devtool state should remain the same
+ expect(socketManager.connected).toBe(true);
+ expect(socketManager.disposed).toBe(false);
+
+ // the devtool can be visible again
+ socketManager.showDevtool();
+ expect(devtool.getDevtoolElement()).not.toBeNull();
+ expect(devtool.getDevtoolStatusElement()?.innerHTML).toEqual(
+ devtool.Status.CONNECTED,
+ );
+ expect(devtool.getDevtoolLogSectionElement()!.children).toHaveLength(3);
+
socketManager.dispose();
await diposeResolver.promise;
diff --git a/packages/core/src/ClientSocketManager.ts b/packages/core/src/ClientSocketManager.ts
index d81b6b8..1df2b45 100644
--- a/packages/core/src/ClientSocketManager.ts
+++ b/packages/core/src/ClientSocketManager.ts
@@ -50,7 +50,7 @@ class ClientSocketManager<
this._inputListeners.onInit?.call(this);
if (devtoolOpt) {
- devtool.init();
+ this.showDevtool();
}
} catch (err) {
// eslint-disable-next-line no-console
@@ -74,8 +74,10 @@ class ClientSocketManager<
this._socket.on(SocketReservedEvents.CONNECTION, () => {
this._inputListeners.onSocketConnection?.call(this);
- devtool.render(s => {
- s.status = devtool.Status.CONNECTED;
+ devtool.render({
+ action: s => {
+ s.status = devtool.Status.CONNECTED;
+ },
});
});
@@ -89,8 +91,10 @@ class ClientSocketManager<
this._socket.on(SocketReservedEvents.DISCONNECTION, (reason, details) => {
this._inputListeners.onSocketDisconnection?.call(this, reason, details);
- devtool.render(s => {
- s.status = devtool.Status.DISCONNECTED;
+ devtool.render({
+ action: s => {
+ s.status = devtool.Status.DISCONNECTED;
+ },
});
if (!this.autoReconnectable) {
@@ -117,12 +121,14 @@ class ClientSocketManager<
manager.on(ManagerReservedEvents.CONNECTION_ERROR, error => {
onConnectionError?.call(this, error);
- devtool.render(s => {
- s.logs.enqueue({
- type: devtool.LogType.CONNECTION_ERROR,
- date: new Date(),
- detail: error.message,
- });
+ devtool.render({
+ action: s => {
+ s.logs.enqueue({
+ type: devtool.LogType.CONNECTION_ERROR,
+ date: new Date(),
+ detail: error.message,
+ });
+ },
});
});
@@ -132,46 +138,54 @@ class ClientSocketManager<
manager.on(ManagerReservedEvents.RECONNECTING, attempt => {
onReconnecting?.call(this, attempt);
- devtool.render(s => {
- s.status = devtool.Status.RECONNECTING;
- s.logs.enqueue({
- type: devtool.LogType.RECONNECTING,
- date: new Date(),
- detail: `Reconnecting... (${attempt} attempt(s))`,
- });
+ devtool.render({
+ action: 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(s => {
- s.logs.enqueue({
- type: devtool.LogType.RECONNECTING_ERROR,
- date: new Date(),
- detail: error.message,
- });
+ devtool.render({
+ action: 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(s => {
- s.logs.enqueue({
- type: devtool.LogType.RECONNECTION_FAILURE,
- date: new Date(),
- detail: `Failed to reconnect.`,
- });
+ devtool.render({
+ action: 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(s => {
- s.logs.enqueue({
- type: devtool.LogType.SUCCESSFUL_RECONNECTION,
- date: new Date(),
- detail: `Successfully connected after ${attempt} attempt(s)`,
- });
+ devtool.render({
+ action: s => {
+ s.logs.enqueue({
+ type: devtool.LogType.SUCCESSFUL_RECONNECTION,
+ date: new Date(),
+ detail: `Successfully connected after ${attempt} attempt(s)`,
+ });
+ },
});
});
}
@@ -336,13 +350,15 @@ class ClientSocketManager<
onSubscriptionComplete?.call(this, channel);
- devtool.render(s => {
- s.channels.add(channel);
- s.logs.enqueue({
- type: devtool.LogType.SUBSCRIBED,
- date: new Date(),
- detail: `subscribed to \`${channel}\` channel`,
- });
+ devtool.render({
+ action: s => {
+ s.channels.add(channel);
+ s.logs.enqueue({
+ type: devtool.LogType.SUBSCRIBED,
+ date: new Date(),
+ detail: `subscribed to \`${channel}\` channel`,
+ });
+ },
});
}
@@ -367,13 +383,15 @@ class ClientSocketManager<
if (cb) this._socket.off(channel, cb);
else this._socket.off(channel);
- devtool.render(s => {
- s.channels.delete(channel);
- s.logs.enqueue({
- type: devtool.LogType.UNSUBSCRIBED,
- date: new Date(),
- detail: `unsubscribed from \`${channel}\` channel`,
- });
+ devtool.render({
+ action: s => {
+ s.channels.delete(channel);
+ s.logs.enqueue({
+ type: devtool.LogType.UNSUBSCRIBED,
+ date: new Date(),
+ detail: `unsubscribed from \`${channel}\` channel`,
+ });
+ },
});
}
@@ -385,12 +403,14 @@ class ClientSocketManager<
this._socket?.connect();
- devtool.render(s => {
- s.logs.enqueue({
- type: devtool.LogType.CONNECTED,
- date: new Date(),
- detail: `socket was conneced manually`,
- });
+ devtool.render({
+ action: s => {
+ s.logs.enqueue({
+ type: devtool.LogType.CONNECTED,
+ date: new Date(),
+ detail: `socket was conneced manually`,
+ });
+ },
});
}
@@ -406,12 +426,14 @@ class ClientSocketManager<
this._socket?.disconnect();
- devtool.render(s => {
- s.logs.enqueue({
- type: devtool.LogType.DISCONNECTED,
- date: new Date(),
- detail: `socket was disconneced manually`,
- });
+ devtool.render({
+ action: s => {
+ s.logs.enqueue({
+ type: devtool.LogType.DISCONNECTED,
+ date: new Date(),
+ detail: `socket was disconneced manually`,
+ });
+ },
});
}
@@ -437,6 +459,20 @@ class ClientSocketManager<
devtool.dispose();
}
+
+ /**
+ * Show devtool in the browser programmatically.
+ */
+ public showDevtool(): void {
+ devtool.render({ force: true });
+ }
+
+ /**
+ * Hide devtool in the browser programmatically.
+ */
+ public hideDevtool(): void {
+ devtool.dispose();
+ }
}
export default ClientSocketManager;
diff --git a/packages/core/src/ClientSocketManagerStub.ts b/packages/core/src/ClientSocketManagerStub.ts
index ff2de33..e96fbf6 100644
--- a/packages/core/src/ClientSocketManagerStub.ts
+++ b/packages/core/src/ClientSocketManagerStub.ts
@@ -137,6 +137,16 @@ class ClientSocketManagerStub {
this._disposed = true;
this._inputListeners = {};
}
+
+ /**
+ * Show devtool in the browser programmatically.
+ */
+ public showDevtool(): void {}
+
+ /**
+ * Show devtool in the browser programmatically.
+ */
+ public hideDevtool(): void {}
}
export default ClientSocketManagerStub;
diff --git a/packages/core/src/devtool/FixedQueue.ts b/packages/core/src/devtool/FixedQueue.ts
index 928c4df..e795632 100644
--- a/packages/core/src/devtool/FixedQueue.ts
+++ b/packages/core/src/devtool/FixedQueue.ts
@@ -62,4 +62,11 @@ export class FixedQueue {
get length(): number {
return this._queue.length;
}
+
+ /**
+ * Clears the queue.
+ */
+ clear(): void {
+ this._queue = [];
+ }
}
diff --git a/packages/core/src/devtool/devtool.test.ts b/packages/core/src/devtool/devtool.test.ts
index 972ddb0..c9c159b 100644
--- a/packages/core/src/devtool/devtool.test.ts
+++ b/packages/core/src/devtool/devtool.test.ts
@@ -10,7 +10,14 @@ import * as devtool from "./devtool.ts";
describe("Devtool", () => {
beforeEach(() => {
- devtool.init();
+ devtool.render({
+ force: true,
+ action: s => {
+ s.channels.clear();
+ s.status = devtool.Status.UNKNOWN;
+ s.logs.clear();
+ },
+ });
});
afterEach(() => {
@@ -28,7 +35,7 @@ describe("Devtool", () => {
it("should not double-init", () => {
const originalWrapper = devtool.getDevtoolWrapperElement();
- devtool.init(); // re-init should do nothing
+ devtool.render(); // re-init should do nothing
expect(document.querySelectorAll(`#${DEVTOOL_WRAPPER_ID}`)).toHaveLength(1);
expect(devtool.getDevtoolWrapperElement()).toBe(originalWrapper);
});
@@ -40,8 +47,10 @@ describe("Devtool", () => {
it("should be able to update status of the socket using render", () => {
Object.values(Status).forEach(status => {
- devtool.render(state => {
- state.status = status;
+ devtool.render({
+ action: state => {
+ state.status = status;
+ },
});
expect(devtool.getDevtoolInfoElement()!.innerHTML).toContain(status);
@@ -55,10 +64,12 @@ describe("Devtool", () => {
date: new Date(),
};
- devtool.render(state => {
- state.logs.enqueue(mockLog);
- state.channels.add("my-channel");
- state.status = Status.DISCONNECTED;
+ devtool.render({
+ action: state => {
+ state.logs.enqueue(mockLog);
+ state.channels.add("my-channel");
+ state.status = Status.DISCONNECTED;
+ },
});
const info = devtool.getDevtoolInfoElement();
@@ -85,12 +96,14 @@ describe("Devtool", () => {
// Fill the logs
for (let i = 0; i < LOG_CAPACITY; i++) {
- devtool.render(state => {
- state.logs.enqueue({
- type: LogType.CONNECTION_ERROR,
- detail: `log-${i}`,
- date: new Date(),
- });
+ devtool.render({
+ action: state => {
+ state.logs.enqueue({
+ type: LogType.CONNECTION_ERROR,
+ detail: `log-${i}`,
+ date: new Date(),
+ });
+ },
});
}
@@ -104,12 +117,14 @@ 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(state => {
- state.logs.enqueue({
- type: LogType.CONNECTION_ERROR,
- detail: `log-${LOG_CAPACITY}`,
- date: new Date(),
- });
+ devtool.render({
+ action: 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 3a1a72f..154648f 100644
--- a/packages/core/src/devtool/devtool.ts
+++ b/packages/core/src/devtool/devtool.ts
@@ -65,6 +65,9 @@ export const renderChipGroup = (items: string[]) => {
"border-radius": "999px",
padding: "0 0.5rem",
"list-style-type": "none",
+ overflow: "hidden",
+ "white-space": "nowrap",
+ "text-overflow": "ellipsis",
});
const chipGroupStyle = generateInlineStyle({
@@ -73,6 +76,9 @@ export const renderChipGroup = (items: string[]) => {
gap: "0.5rem",
padding: "0",
"flex-wrap": "wrap",
+ "overflow-x": "hidden",
+ "overflow-y": "auto",
+ "max-height": "6rem",
});
return ``;
@@ -212,7 +218,7 @@ export const renderDevtoolInfo = () => {
transform: "scale(0)",
"transform-origin": "0 0",
transition: "opacity 0.2s, transform 0.2s",
- width: "12rem",
+ width: "14rem",
}),
});
@@ -267,7 +273,7 @@ export const updateInfoSection = () => {
return infoSection;
};
-export const init = () => {
+const init = () => {
if (active) return;
active = true;
@@ -275,6 +281,8 @@ export const init = () => {
const devtoolWrapper = document.createElement("div");
devtoolWrapper.style.position = "fixed";
+ devtoolWrapper.style.top = "8px";
+ devtoolWrapper.style.left = "8px";
devtoolWrapper.id = DEVTOOL_WRAPPER_ID;
devtoolWrapper.innerHTML = renderDevtool();
@@ -304,14 +312,7 @@ export const dispose = () => {
getDevtoolWrapperElement()?.remove();
active = false;
-};
-
-const updateUi = () => {
- const devtoolElement = getDevtoolElement();
-
- if (!devtoolElement) init();
-
- updateInfoSection();
+ expanded = false;
};
const toggle = () => {
@@ -330,11 +331,26 @@ const toggle = () => {
);
};
-export const render = (cb: (s: typeof devtool) => void) => {
- if (!active) return;
+type RenderOptions = {
+ action?: (s: typeof devtool) => void;
+ force?: boolean;
+};
- cb(devtool);
- updateUi();
+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();
};
export { LogType, Status };
diff --git a/packages/react/README.md b/packages/react/README.md
index fa7a40f..d0c70a9 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -11,7 +11,7 @@ seamless management of socket connections with `socket.io-client`.
-
+---
`ClientSocketManager` is a flexible and robust manager for handling socket
connections using `socket.io-client`. It provides easy setup and management of
@@ -83,7 +83,7 @@ const App = () => {
## API Reference
-### `SocketClientProvider` Component:
+### `SocketClientProvider` Component
```ts
const SocketClientProvider: (props: SocketClientProviderProps) => JSX.Element;
@@ -91,7 +91,7 @@ const SocketClientProvider: (props: SocketClientProviderProps) => JSX.Element;
Wraps your application to provide `ClientSocketManager` client.
-#### Parameters:
+#### Parameters
- `children`: The React tree to provide the socket client for.
- `uri`: The URI of the socket server.
@@ -101,7 +101,7 @@ Wraps your application to provide `ClientSocketManager` client.
scenarios.
- `options`: (optional): Configuration options for the socket connection.
-##### Options:
+##### Options
We have extended
[socket-io's options](https://socket.io/docs/v4/client-options/) to include
@@ -128,7 +128,7 @@ additional options:
useful for development and debugging purposes. In production environments,
it's recommended to leave this disabled.
-### `useSocketClient` Hook:
+### `useSocketClient` Hook
```ts
type ConnectionStatusValues = "connected" | "disconnected" | "reconnecting";
diff --git a/playground/client/index.ts b/playground/client/index.ts
index 6e9ca4a..156bdc9 100644
--- a/playground/client/index.ts
+++ b/playground/client/index.ts
@@ -130,9 +130,35 @@ const createUnsubBtn = () => {
return button;
};
+const createShowDevtoolBtn = () => {
+ const button = createButton("show devtool");
+
+ button.addEventListener("click", () => {
+ socketManager.showDevtool();
+ });
+
+ document.body.append(button);
+
+ return button;
+};
+
+const createHideDevtoolBtn = () => {
+ const button = createButton("hide devtool");
+
+ button.addEventListener("click", () => {
+ socketManager.hideDevtool();
+ });
+
+ document.body.append(button);
+
+ return button;
+};
+
createDisconnectBtn();
createReconnectBtn();
createDisposeBtn();
createSendMessageBtn();
createSubBtn();
createUnsubBtn();
+createShowDevtoolBtn();
+createHideDevtoolBtn();