Skip to content

Commit 5d0ea4b

Browse files
committed
2.0
1 parent f262e07 commit 5d0ea4b

File tree

7 files changed

+222
-180
lines changed

7 files changed

+222
-180
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "socket.io-react-hook",
3-
"version": "1.0.17",
3+
"version": "2.0.0",
44
"description": "",
55
"main": "lib/cjs/index.js",
66
"author": {

src/IoContext.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ import { IoContextInterface } from "./types";
55
const IoContext = React.createContext<IoContextInterface<any>>({
66
createConnection: () => undefined,
77
getConnection: () => undefined,
8-
getLastMessage: () => undefined,
9-
setLastMessage: () => undefined,
108
registerSharedListener: () => undefined,
11-
getError: () => undefined,
12-
setError: () => undefined,
13-
getStatus: () => "disconnected",
149
});
1510

1611
export default IoContext;

src/IoProvider.tsx

Lines changed: 140 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,140 @@
1-
import React, { useRef, useState } from "react";
2-
import io from "socket.io-client";
3-
import IoContext from "./IoContext";
4-
5-
import {
6-
CreateConnectionFunc,
7-
IoConnection,
8-
IoNamespace,
9-
GetConnectionFunc,
10-
SocketLikeWithNamespace,
11-
} from "./types";
12-
13-
const IoProvider = function ({ children }: React.PropsWithChildren<{}>) {
14-
const connections = useRef<Record<string, number>>({});
15-
const sockets = useRef<Record<IoNamespace, IoConnection>>({});
16-
17-
const [statuses, setStatuses] = useState<
18-
Record<IoNamespace, "disconnected" | "connecting" | "connected">
19-
>({});
20-
const [lastMessages, setLastMessages] = useState<Record<string, any>>({});
21-
const [errors, setErrors] = useState<Record<string, any>>({});
22-
23-
const createConnection: CreateConnectionFunc<any> = (
24-
urlConfig,
25-
options = {}
26-
) => {
27-
const connectionKey = urlConfig.id;
28-
29-
if (!(connectionKey in connections.current)) {
30-
connections.current[connectionKey] = 1;
31-
} else {
32-
connections.current[connectionKey] += 1;
33-
}
34-
35-
const cleanup = () => {
36-
if (--connections.current[connectionKey] === 0) {
37-
const socketsToClose = Object.keys(sockets.current).filter((key) =>
38-
key.includes(connectionKey)
39-
);
40-
41-
for (const key of socketsToClose) {
42-
sockets.current[key].disconnect();
43-
delete sockets.current[key];
44-
}
45-
}
46-
};
47-
48-
const namespaceKey = `${connectionKey}${urlConfig.path}`;
49-
50-
// By default socket.io-client creates a new connection for the same namespace
51-
// The next line prevents that
52-
if (sockets.current[namespaceKey]) {
53-
sockets.current[namespaceKey].connect();
54-
return { socket: sockets.current[namespaceKey], cleanup };
55-
}
56-
const handleConnect = () =>
57-
setStatuses((state) => ({ ...state, [namespaceKey]: "connected" }));
58-
59-
const handleDisconnect = () =>
60-
setStatuses((state) => ({ ...state, [namespaceKey]: "disconnected" }));
61-
62-
const socket = io(urlConfig.source, options) as SocketLikeWithNamespace;
63-
socket.namespaceKey = namespaceKey;
64-
65-
sockets.current = Object.assign({}, sockets.current, {
66-
[namespaceKey]: socket,
67-
});
68-
socket.on("error", (error) => setError(namespaceKey, error));
69-
socket.on("connect", handleConnect);
70-
socket.on("disconnect", handleDisconnect);
71-
return { socket, cleanup };
72-
};
73-
74-
const getLastMessage = (namespaceKey = "", forEvent = "") =>
75-
lastMessages[`${namespaceKey}${forEvent}`];
76-
const setLastMessage = (
77-
namespaceKey: string,
78-
forEvent: string,
79-
message: any
80-
) =>
81-
setLastMessages((state) => ({
82-
...state,
83-
[`${namespaceKey}${forEvent}`]: message,
84-
}));
85-
86-
const getConnection: GetConnectionFunc<any> = (namespaceKey = "") =>
87-
sockets.current[namespaceKey];
88-
const getStatus = (namespaceKey = "") => statuses[namespaceKey];
89-
const getError = (namespaceKey = "") => errors[namespaceKey];
90-
const setError = (namespaceKey = "", error: any) =>
91-
setErrors((state) => ({
92-
...state,
93-
[namespaceKey]: error,
94-
}));
95-
96-
const registerSharedListener = (namespaceKey = "", forEvent = "") => {
97-
if (
98-
sockets.current[namespaceKey] &&
99-
!sockets.current[namespaceKey].hasListeners(forEvent)
100-
) {
101-
sockets.current[namespaceKey].on(forEvent, (message) =>
102-
setLastMessage(namespaceKey, forEvent, message)
103-
);
104-
}
105-
};
106-
107-
return (
108-
<IoContext.Provider
109-
value={{
110-
createConnection,
111-
getConnection,
112-
getLastMessage,
113-
setLastMessage,
114-
getError,
115-
setError,
116-
getStatus,
117-
registerSharedListener,
118-
}}
119-
>
120-
{children}
121-
</IoContext.Provider>
122-
);
123-
};
124-
125-
export default IoProvider;
1+
import React, { useRef } from "react";
2+
import io from "socket.io-client";
3+
import IoContext from "./IoContext";
4+
5+
import {
6+
CreateConnectionFunc,
7+
IoConnection,
8+
IoNamespace,
9+
GetConnectionFunc,
10+
SocketLike,
11+
SocketState,
12+
} from "./types";
13+
14+
const IoProvider = function ({ children }: React.PropsWithChildren<{}>) {
15+
const connections = useRef<Record<string, number>>({});
16+
const sockets = useRef<
17+
Record<
18+
IoNamespace,
19+
{
20+
socket: IoConnection;
21+
} & SocketState
22+
>
23+
>({});
24+
25+
const createConnection: CreateConnectionFunc<any> = (
26+
urlConfig,
27+
options = {}
28+
) => {
29+
const connectionKey = urlConfig.id;
30+
31+
if (!(connectionKey in connections.current)) {
32+
connections.current[connectionKey] = 1;
33+
} else {
34+
connections.current[connectionKey] += 1;
35+
}
36+
37+
const cleanup = () => {
38+
if (--connections.current[connectionKey] === 0) {
39+
const socketsToClose = Object.keys(sockets.current).filter((key) =>
40+
key.includes(connectionKey)
41+
);
42+
43+
for (const key of socketsToClose) {
44+
sockets.current[key].socket.disconnect();
45+
sockets.current[key].subscribers.clear();
46+
delete sockets.current[key];
47+
}
48+
}
49+
};
50+
51+
const namespaceKey = `${connectionKey}${urlConfig.path}`;
52+
53+
// By default socket.io-client creates a new connection for the same namespace
54+
// The next line prevents that
55+
if (sockets.current[namespaceKey]) {
56+
sockets.current[namespaceKey].socket.connect();
57+
return {
58+
cleanup,
59+
...sockets.current[namespaceKey],
60+
};
61+
}
62+
63+
const handleConnect = () => {
64+
sockets.current[namespaceKey].state.status = "connected";
65+
sockets.current[namespaceKey].notify();
66+
};
67+
68+
const handleDisconnect = () => {
69+
sockets.current[namespaceKey].state.status = "disconnected";
70+
sockets.current[namespaceKey].notify();
71+
};
72+
73+
const socket = io(urlConfig.source, options) as SocketLike;
74+
socket.namespaceKey = namespaceKey;
75+
76+
sockets.current = Object.assign({}, sockets.current, {
77+
[namespaceKey]: {
78+
socket,
79+
state: {
80+
status: "disconnected",
81+
lastMessage: {},
82+
error: null,
83+
},
84+
notify: () => {
85+
sockets.current[namespaceKey].subscribers.forEach((callback) =>
86+
callback(sockets.current[namespaceKey].state)
87+
);
88+
},
89+
subscribers: new Set(),
90+
subscribe: (callback) => {
91+
sockets.current[namespaceKey].subscribers.add(callback);
92+
return () =>
93+
sockets.current[namespaceKey].subscribers.delete(callback);
94+
},
95+
},
96+
});
97+
98+
socket.on("error", (error) => {
99+
sockets.current[namespaceKey].state.error = error;
100+
sockets.current[namespaceKey].notify();
101+
});
102+
103+
socket.on("connect", handleConnect);
104+
socket.on("disconnect", handleDisconnect);
105+
106+
return {
107+
cleanup,
108+
...sockets.current[namespaceKey],
109+
};
110+
};
111+
112+
const getConnection: GetConnectionFunc<any> = (namespaceKey = "") =>
113+
sockets.current[namespaceKey];
114+
115+
const registerSharedListener = (namespaceKey = "", forEvent = "") => {
116+
if (
117+
sockets.current[namespaceKey] &&
118+
!sockets.current[namespaceKey].socket.hasListeners(forEvent)
119+
) {
120+
sockets.current[namespaceKey].socket.on(forEvent, (message) => {
121+
sockets.current[namespaceKey].state.lastMessage[forEvent] = message;
122+
sockets.current[namespaceKey].notify();
123+
});
124+
}
125+
};
126+
127+
return (
128+
<IoContext.Provider
129+
value={{
130+
createConnection,
131+
getConnection,
132+
registerSharedListener,
133+
}}
134+
>
135+
{children}
136+
</IoContext.Provider>
137+
);
138+
};
139+
140+
export default IoProvider;

src/types.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,49 @@ export type IoNamespace = string;
44

55
export type IoConnection = Socket;
66

7-
export type SocketLikeWithNamespace<T extends Socket = Socket> = T & {
7+
export type SocketLike<T extends Socket = Socket> = T & {
88
namespaceKey: string;
99
};
1010

11+
export type SocketState = {
12+
state: {
13+
status: "disconnected" | "connecting" | "connected";
14+
error: Error | null;
15+
lastMessage: Record<string, any>;
16+
};
17+
notify: () => void;
18+
subscribe: (callback: (state: SocketState["state"]) => void) => () => void;
19+
subscribers: Set<(state: SocketState["state"]) => void>;
20+
};
21+
1122
export type CreateConnectionFuncReturnType<T extends Socket = Socket> = {
12-
socket: SocketLikeWithNamespace<T>;
23+
socket: SocketLike<T>;
1324
cleanup: () => void;
14-
};
25+
} & SocketState;
1526

1627
export type CreateConnectionFunc<T extends Socket = Socket> = (
1728
urlConfig: ReturnType<typeof url>,
1829
options?: Partial<ManagerOptions & SocketOptions> | undefined
1930
) => CreateConnectionFuncReturnType<T> | undefined;
2031

21-
export type GetConnectionFunc<T extends Socket> = (
22-
namespace?: IoNamespace
23-
) => T | undefined;
32+
export type GetConnectionFunc<T extends Socket> = (namespace?: IoNamespace) =>
33+
| ({
34+
socket: T;
35+
} & SocketState)
36+
| undefined;
2437

2538
export type IoContextInterface<T extends Socket> = {
2639
createConnection: CreateConnectionFunc<T>;
2740
getConnection: GetConnectionFunc<T>;
28-
getLastMessage: (namespace: string, forEvent: string) => any;
29-
setLastMessage: (namespace: string, forEvent: string, message: any) => void;
3041
registerSharedListener: (namespace: string, forEvent: string) => void;
31-
getError: (namespace: string) => any;
32-
setError: (namespace: string, error: any) => void;
33-
getStatus: (namespace: string) => "connecting" | "connected" | "disconnected";
3442
};
3543

3644
export type UseSocketOptions<I> = Partial<ManagerOptions & SocketOptions> & {
3745
enabled?: boolean;
3846
} & I;
3947

4048
export type UseSocketReturnType<T extends Socket> = {
41-
socket: SocketLikeWithNamespace<T>;
49+
socket: SocketLike<T>;
4250
connected: boolean;
4351
error: any;
4452
};

0 commit comments

Comments
 (0)