Skip to content
Open
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
7 changes: 6 additions & 1 deletion packages/laravel-echo/src/connector/connector.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="window" />

import type { Channel, PresenceChannel } from "../channel";
import type { BroadcastDriver, EchoOptions } from "../echo";
import type { BroadcastDriver, EchoOptions, ConnectionStatus } from "../echo";

export type EchoOptionsWithDefaults<TBroadcaster extends BroadcastDriver> = {
broadcaster: TBroadcaster;
Expand Down Expand Up @@ -147,6 +147,11 @@ export abstract class Connector<
*/
abstract socketId(): string | undefined;

/**
* Get the current connection status.
*/
abstract connectionStatus(): ConnectionStatus;

/**
* Disconnect from the Echo server.
*/
Expand Down
8 changes: 8 additions & 0 deletions packages/laravel-echo/src/connector/null-connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
NullPresenceChannel,
NullEncryptedPrivateChannel,
} from "../channel";
import type { ConnectionStatus } from "../echo";

/**
* This class creates a null connector.
Expand Down Expand Up @@ -87,6 +88,13 @@ export class NullConnector extends Connector<
return "fake-socket-id";
}

/**
* Get the current connection status.
*/
connectionStatus(): ConnectionStatus {
return "connected";
}

/**
* Disconnect the connection.
*/
Expand Down
23 changes: 22 additions & 1 deletion packages/laravel-echo/src/connector/pusher-connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
PusherPresenceChannel,
PusherPrivateChannel,
} from "../channel";
import type { BroadcastDriver } from "../echo";
import type { BroadcastDriver, ConnectionStatus } from "../echo";
import { Connector, type EchoOptionsWithDefaults } from "./connector";

type AnyPusherChannel =
Expand Down Expand Up @@ -188,6 +188,27 @@ export class PusherConnector<
return this.pusher.connection.socket_id;
}

/**
* Get the current connection status.
*/
connectionStatus(): ConnectionStatus {
const state = this.pusher.connection.state;

switch (state) {
case "connected":
return "connected";
case "connecting":
return "connecting";
case "disconnected":
return "disconnected";
case "failed":
case "unavailable":
return "failed";
default:
return "disconnected";
}
}

/**
* Disconnect Pusher connection.
*/
Expand Down
27 changes: 27 additions & 0 deletions packages/laravel-echo/src/connector/socketio-connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Socket,
SocketOptions,
} from "socket.io-client";
import type { ConnectionStatus } from "../echo";

type AnySocketIoChannel =
| SocketIoChannel
Expand Down Expand Up @@ -155,6 +156,32 @@ export class SocketIoConnector extends Connector<
return this.socket.id;
}

/**
* Get the current connection status.
*/
connectionStatus(): ConnectionStatus {
if (this.socket.connected) {
return "connected";
}

// Check if socket is trying to reconnect
if (this.socket.io._reconnecting) {
return "reconnecting";
}

// Check if socket was previously connected (disconnected)
// or never connected (connecting/failed)
const wasConnected = this.socket.id !== undefined;

if (wasConnected) {
return "disconnected";
}

// Socket.io doesn't have explicit failed state, but we can infer
// if it's not connected and not reconnecting
return "connecting";
}

/**
* Disconnect Socketio connection.
*/
Expand Down
17 changes: 17 additions & 0 deletions packages/laravel-echo/src/echo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ import {
} from "./connector";
import { isConstructor } from "./util";

/**
* Connection status types for WebSocket connections
*/
export type ConnectionStatus =
| "connected"
| "disconnected"
| "connecting"
| "reconnecting"
| "failed";

/**
* This class is the primary API for interacting with broadcasting.
*/
Expand Down Expand Up @@ -175,6 +185,13 @@ export default class Echo<T extends keyof Broadcaster> {
return this.connector.socketId();
}

/**
* Get the current connection status.
*/
connectionStatus(): ConnectionStatus {
return this.connector.connectionStatus();
}

/**
* Register 3rd party request interceptors. These are used to automatically
* send a connections socket id to a Laravel app with a X-Socket-Id header.
Expand Down
8 changes: 8 additions & 0 deletions packages/laravel-echo/tests/echo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,12 @@ describe("Echo", () => {
() => new Echo({ broadcaster: "foo", withoutInterceptors: true }),
).toThrow("Broadcaster string foo is not supported.");
});

test("it can get connection status", () => {
const echo = new Echo({
broadcaster: "null",
withoutInterceptors: true,
});
expect(echo.connectionStatus()).toBe("connected");
});
});
46 changes: 46 additions & 0 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,52 @@ In the above example, the configuration would also fill in the following keys if
}
```

## Connection Status

You can get the current WebSocket connection status using the `useConnectionStatus` hook or the `getConnectionStatus` utility function:

```ts
import { useConnectionStatus, getConnectionStatus } from "@laravel/echo-react";

// Using the hook (recommended for React components)
const status = useConnectionStatus(); // Returns: "connected" | "disconnected" | "connecting" | "reconnecting" | "failed"

// Or using the utility function
const status = getConnectionStatus(); // Same return type
```

The possible status values are:

- `"connected"` - Successfully connected to the WebSocket server
- `"disconnected"` - Not connected and not attempting to reconnect
- `"connecting"` - Initial connection attempt in progress
- `"reconnecting"` - Attempting to reconnect after a disconnection
- `"failed"` - Connection failed and won't retry

You can use this to show connection status indicators in your UI:

```ts
function ConnectionIndicator() {
const status = useConnectionStatus();

const getStatusColor = (status: string) => {
switch (status) {
case "connected": return "green";
case "connecting": return "yellow";
case "reconnecting": return "orange";
case "failed": return "red";
default: return "gray";
}
};

return (
<div style={{ color: getStatusColor(status) }}>
Connection: {status}
</div>
);
}
```

## `useEcho` Hook

Connect to private channel:
Expand Down
15 changes: 14 additions & 1 deletion packages/react/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import Echo, { type BroadcastDriver, type EchoOptions } from "laravel-echo";
import Echo, {
type BroadcastDriver,
type EchoOptions,
type ConnectionStatus,
} from "laravel-echo";
import Pusher from "pusher-js";
import type { ConfigDefaults } from "../types";

Expand Down Expand Up @@ -84,3 +88,12 @@ export const echo = <T extends BroadcastDriver>(): Echo<T> =>
getEchoInstance<T>();

export const echoIsConfigured = () => echoConfig !== null;

/**
* Get the current WebSocket connection status
*
* @returns ConnectionStatus - The current connection status
*/
export const getConnectionStatus = (): ConnectionStatus => {
return getEchoInstance().connectionStatus();
};
11 changes: 10 additions & 1 deletion packages/react/src/hooks/use-echo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type BroadcastDriver } from "laravel-echo";
import { type BroadcastDriver, type ConnectionStatus } from "laravel-echo";
import { useCallback, useEffect, useRef } from "react";
import { echo } from "../config";
import type {
Expand Down Expand Up @@ -312,3 +312,12 @@ export const useEchoModel = <
"private",
);
};

/**
* Hook to get the current WebSocket connection status
*
* @returns ConnectionStatus - The current connection status
*/
export const useConnectionStatus = (): ConnectionStatus => {
return echo().connectionStatus();
};
8 changes: 7 additions & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
export { configureEcho, echo, echoIsConfigured } from "./config/index";
export {
configureEcho,
echo,
echoIsConfigured,
getConnectionStatus,
} from "./config/index";
export {
useEcho,
useEchoModel,
useEchoNotification,
useEchoPresence,
useEchoPublic,
useConnectionStatus,
} from "./hooks/use-echo";
27 changes: 27 additions & 0 deletions packages/react/tests/use-echo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ vi.mock("laravel-echo", () => {
Echo.prototype.leaveChannel = vi.fn();
Echo.prototype.leaveAllChannels = vi.fn();
Echo.prototype.join = vi.fn(() => mockPresenceChannel);
Echo.prototype.connectionStatus = vi.fn(() => "connected");

return { default: Echo };
});
Expand Down Expand Up @@ -1147,3 +1148,29 @@ describe("useEchoNotification hook", async () => {
expect(result.current.channel).not.toBeNull();
});
});

describe("useConnectionStatus hook", async () => {
let echoModule: typeof import("../src/hooks/use-echo");
let configModule: typeof import("../src/config/index");

beforeEach(async () => {
vi.resetModules();

echoModule = await getEchoModule();
configModule = await getConfigModule();

configModule.configureEcho({
broadcaster: "null",
});
});

afterEach(() => {
vi.clearAllMocks();
});

it("returns the connection status from echo instance", async () => {
const { result } = renderHook(() => echoModule.useConnectionStatus());

expect(result.current).toBe("connected");
});
});
53 changes: 53 additions & 0 deletions packages/vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,59 @@ In the above example, the configuration would also fill in the following keys if
}
```

## Connection Status

You can get the current WebSocket connection status using the `useConnectionStatus` composable or the `getConnectionStatus` utility function:

```ts
import { useConnectionStatus, getConnectionStatus } from "@laravel/echo-vue";

// Using the composable (recommended for Vue components)
const status = useConnectionStatus(); // Returns: "connected" | "disconnected" | "connecting" | "reconnecting" | "failed"

// Or using the utility function
const status = getConnectionStatus(); // Same return type
```

The possible status values are:

- `"connected"` - Successfully connected to the WebSocket server
- `"disconnected"` - Not connected and not attempting to reconnect
- `"connecting"` - Initial connection attempt in progress
- `"reconnecting"` - Attempting to reconnect after a disconnection
- `"failed"` - Connection failed and won't retry

You can use this to show connection status indicators in your UI:

```vue
<script setup>
import { useConnectionStatus } from "@laravel/echo-vue";

const status = useConnectionStatus();

const getStatusColor = (status) => {
switch (status) {
case "connected":
return "green";
case "connecting":
return "yellow";
case "reconnecting":
return "orange";
case "failed":
return "red";
default:
return "gray";
}
};
</script>

<template>
<div :style="{ color: getStatusColor(status) }">
Connection: {{ status }}
</div>
</template>
```

## `useEcho` Hook

Connect to private channel:
Expand Down
11 changes: 10 additions & 1 deletion packages/vue/src/composables/useEcho.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type BroadcastDriver } from "laravel-echo";
import { type BroadcastDriver, type ConnectionStatus } from "laravel-echo";
import { onMounted, onUnmounted, ref, watch } from "vue";
import { echo } from "../config";
import type {
Expand Down Expand Up @@ -317,3 +317,12 @@ export const useEchoModel = <
"private",
);
};

/**
* Composable to get the current WebSocket connection status
*
* @returns ConnectionStatus - The current connection status
*/
export const useConnectionStatus = (): ConnectionStatus => {
return echo().connectionStatus();
};
Loading