Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/chubby-boats-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@tapsioss/client-socket-manager": minor
---

- [BREAKING CHANGE] The `devtool` field within the `ClientSocketManager`'s constructor `options` has been updated from a boolean to an object. This change allows for more granular control over development tools.
- Previously: `devtool: boolean`
- Now: `devtool: { enabled: boolean; zIndex?: number; }`
- The `enabled` property maintains the previous boolean functionality, while the optional `zIndex` property allows for specifying the stacking order of devtool elements.

7 changes: 6 additions & 1 deletion packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,16 @@ and cleaned up.
#### `showDevtool`

```ts
showDevtool(): void;
showDevtool(options): void;
```

Show devtool in the browser programmatically.

##### Parameters

- `options`: Optional parameters.
- `zIndex`: Z-index of the devtool, overrides the previous z-index of the devtool.

#### `hideDevtool`

```ts
Expand Down
152 changes: 150 additions & 2 deletions packages/core/src/ClientSocketManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,9 @@ describe("ClientSocketManager: unit tests", () => {
socketManager.dispose();

socketManager = new ClientSocketManager(socketServerUri, {
devtool: true,
devtool: {
enabled: true,
},
});
expect(devtool.getDevtoolElement()).not.toBeNull();
});
Expand All @@ -311,7 +313,9 @@ describe("ClientSocketManager: unit tests", () => {
const diposeResolver = createPromiseResolvers();

socketManager = new ClientSocketManager(socketServerUri, {
devtool: true,
devtool: {
enabled: true,
},
eventHandlers: {
onSocketConnection() {
connectResolver.resolve();
Expand Down Expand Up @@ -393,4 +397,148 @@ describe("ClientSocketManager: unit tests", () => {
expect(socketManager.connected).toBe(false);
expect(socketManager.disposed).toBe(true);
});

it("should apply the specified z-index to the devtool and ensure correct click behavior", async () => {
const connectResolver = createPromiseResolvers();
const zIndex = 20;

// Create a div with a higher z-index to be on top of the devtool
const overlayDiv = document.createElement("div");

overlayDiv.style.position = "fixed";
overlayDiv.style.top = "0";
overlayDiv.style.left = "0";
overlayDiv.style.width = "100%";
overlayDiv.style.height = "100%";
overlayDiv.style.backgroundColor = "rgba(0,0,0,0.1)"; // Semi-transparent
overlayDiv.style.zIndex = (zIndex + 1).toString(); // Higher z-index
overlayDiv.id = "overlay-div";
document.body.appendChild(overlayDiv);

// Track clicks on the overlay and the devtool
let overlayClicked = false;
let devtoolClicked = false;

overlayDiv.addEventListener("click", () => {
overlayClicked = true;
});

socketManager = new ClientSocketManager(socketServerUri, {
devtool: {
enabled: true,
zIndex,
},
eventHandlers: {
onSocketConnection() {
connectResolver.resolve();
},
},
});

await connectResolver.promise;

const devtoolElement = devtool.getDevtoolElement();

expect(devtoolElement).not.toBeNull();

// Add a click listener to the devtool itself to detect if it's reachable
devtoolElement?.addEventListener("click", () => {
devtoolClicked = true;
});

if (overlayDiv) {
const { left, top, width, height } = overlayDiv.getBoundingClientRect();
const clientX = left + width / 2;
const clientY = top + height / 2;

const clickEvent = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX,
clientY,
});

overlayDiv.dispatchEvent(clickEvent); // Dispatch click on the overlay
}

// Assert that only the overlay was clicked, indicating it was on top
expect(overlayClicked).toBe(true);
expect(devtoolClicked).toBe(false); // This should now pass if the overlay intercepted the click

// Clean up the overlay div
document.body.removeChild(overlayDiv);
});

it("should capture click when devtool z-index is higher than overlay", async () => {
const connectResolver = createPromiseResolvers();
const devtoolZIndex = 30; // Devtool will have a higher z-index

// Create an overlay div with a lower z-index
const overlayDiv = document.createElement("div");

overlayDiv.style.position = "fixed";
overlayDiv.style.top = "0";
overlayDiv.style.left = "0";
overlayDiv.style.width = "100%";
overlayDiv.style.height = "100%";
overlayDiv.style.backgroundColor = "rgba(0,0,0,0.1)"; // Semi-transparent
overlayDiv.style.zIndex = (devtoolZIndex - 1).toString(); // Lower z-index than devtool
overlayDiv.id = "lower-overlay-div";
document.body.appendChild(overlayDiv);

let overlayClicked = false;
let devtoolClicked = false;

overlayDiv.addEventListener("click", () => {
overlayClicked = true;
});

socketManager = new ClientSocketManager(socketServerUri, {
devtool: {
enabled: true,
zIndex: devtoolZIndex, // Set devtool's z-index to be higher
},
eventHandlers: {
onSocketConnection() {
connectResolver.resolve();
},
},
});

await connectResolver.promise;

const devtoolElement = devtool.getDevtoolElement();

expect(devtoolElement).not.toBeNull();

devtoolElement?.addEventListener("click", () => {
devtoolClicked = true;
});

// Simulate a click on the devtool.
// Since the devtool's z-index is higher, it should receive the click.
if (devtoolElement) {
const { left, top, width, height } =
devtoolElement.getBoundingClientRect();

const clientX = left + width / 2;
const clientY = top + height / 2;

const clickEvent = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX,
clientY,
});

devtoolElement.dispatchEvent(clickEvent); // Dispatch click directly on the devtool
}

// Assert that the devtool was clicked, and the overlay was not
expect(devtoolClicked).toBe(true);
expect(overlayClicked).toBe(false);

// Clean up the overlay div
document.body.removeChild(overlayDiv);
});
});
8 changes: 6 additions & 2 deletions packages/core/src/ClientSocketManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ class ClientSocketManager<
reconnectionDelay = 500,
reconnectionDelayMax = 2000,
eventHandlers,
devtool: devtoolOpt = false,
devtool: devtoolOpt,
...restOptions
} = options ?? {};

const { enabled: devtoolEnabled = false, zIndex: devtoolZIndex = 999999 } =
devtoolOpt ?? {};

try {
this._socket = io(uri, {
...restOptions,
Expand All @@ -49,7 +52,8 @@ class ClientSocketManager<

this._inputListeners.onInit?.call(this);

if (devtoolOpt) {
if (devtoolEnabled) {
devtool.setZIndex(devtoolZIndex);
this.showDevtool();
}
} catch (err) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/devtool/devtool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as devtool from "./devtool.ts";

describe("Devtool", () => {
beforeEach(() => {
devtool.setZIndex(99999);
devtool.render({
force: true,
action: s => {
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/devtool/devtool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const devtool: DevtoolState = {

let active = false;
let expanded = false;
let zIndex: number = NaN;

export const renderDivider = () => {
return `<hr color="#222222" />`;
Expand Down Expand Up @@ -282,9 +283,16 @@ const init = () => {

const devtoolWrapper = document.createElement("div");

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";

devtoolWrapper.id = DEVTOOL_WRAPPER_ID;
devtoolWrapper.innerHTML = renderDevtool();

Expand Down Expand Up @@ -314,6 +322,10 @@ const init = () => {
});
};

export const setZIndex = (z: number) => {
zIndex = z;
};

export const dispose = () => {
getDevtoolWrapperElement()?.remove();

Expand Down
31 changes: 21 additions & 10 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,28 @@ export type ClientSocketManagerOptions = OverrideMembers<
*/
eventHandlers?: ClientSocketManagerListenerOptions;
/**
* Enables the in-browser DevTool panel for socket debugging.
*
* When set to `true`, a floating DevTool UI will appear in the browser that displays:
* - The current socket connection status (`connected`, `disconnected`, `reconnecting`)
* - A list of currently subscribed channels
* - A log panel showing socket events and debugging messages
* Client Socket Devtool options.
*
* This is useful for development and debugging purposes.
* In production environments, it's recommended to leave this disabled.
*
* @default false
* In production environments, it's recommended to leave this section empty.
*/
devtool?: boolean;
devtool?: {
/**
* Enables the in-browser DevTool panel for socket debugging.
*
* When set to `true`, a floating DevTool UI will appear in the browser that displays:
* - The current socket connection status (`connected`, `disconnected`, `reconnecting`)
* - A list of currently subscribed channels
* - A log panel showing socket events and debugging messages.
*
* @default false
*/
enabled: boolean;
/**
* The `z-index` of the devtool.
*
* @default 9999
*/
zIndex?: number;
};
};
4 changes: 3 additions & 1 deletion playground/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import { ClientSocketManager } from "@tapsioss/client-socket-manager";

const socketManager = new ClientSocketManager("http://localhost:3000", {
devtool: true,
devtool: {
enabled: true,
},
eventHandlers: {
onReconnectingError(err) {
console.log("reconnecting error", err);
Expand Down