Skip to content

Commit 1ef49e4

Browse files
committed
React Native Experiment: Native socket.io
1 parent 405dc41 commit 1ef49e4

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

src/chat-api/socketClient.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,16 @@ import {
7070
onVoiceUserJoined,
7171
onVoiceUserLeft
7272
} from "./events/voiceEvents";
73-
74-
const socket = io(env.WS_URL || env.SERVER_URL, {
75-
transports: ["websocket"],
76-
autoConnect: false
77-
});
73+
import { reactNativeAPI, ReactSocketIO } from "@/common/ReactNative";
74+
import { isExperimentEnabled } from "@/common/experiments";
75+
76+
const socket =
77+
reactNativeAPI()?.isReactNative && isExperimentEnabled("RN_NATIVE_WS")()
78+
? new ReactSocketIO(env.WS_URL || env.SERVER_URL)
79+
: io(env.WS_URL || env.SERVER_URL, {
80+
transports: ["websocket"],
81+
autoConnect: false
82+
});
7883

7984
let token: undefined | string;
8085

src/common/ReactNative.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
import { onCleanup, onMount } from "solid-js";
23

34
interface CustomEventMap {
@@ -6,6 +7,7 @@ interface CustomEventMap {
67
registerFCM: { token: string };
78

89
openChannel: { userId: string; channelId: string; serverId?: string };
10+
sio_event: { event: string; payload: any };
911
}
1012

1113
type EventByType<T extends CustomEventMap> = {
@@ -22,6 +24,7 @@ interface WindowAPI {
2224

2325
authenticated(userId: string): string;
2426
logout(): void;
27+
post(event: string, payload: any): void;
2528

2629
on<K extends keyof CustomEventMap>(
2730
event: K,
@@ -63,3 +66,69 @@ export function useReactNativeEvent<K extends keyof CustomEventMap>(
6366
});
6467
});
6568
}
69+
70+
export class ReactSocketIO {
71+
url: string;
72+
id?: string;
73+
handlers: Record<string, Set<(data: any, ...args: any) => void>> = {};
74+
75+
io = {
76+
on: (event: string, callback: (data: any) => void) => {
77+
this.on(event, callback);
78+
},
79+
off: (event: string, callback: (data: any) => void) => {
80+
this.off(event, callback);
81+
}
82+
};
83+
84+
constructor(url: string) {
85+
this.url = url;
86+
87+
reactNativeAPI()?.on(
88+
"sio_event",
89+
(data: { event: string; payload: any; type?: "binary" }) => {
90+
if (data.event === "connect") {
91+
this.id = data.payload.id;
92+
}
93+
if (data.event === "disconnect") {
94+
const handlers = this.handlers[data.event];
95+
if (handlers) {
96+
handlers.forEach((handler) =>
97+
handler(data.payload.reason, data.payload.description)
98+
);
99+
}
100+
return;
101+
}
102+
if (data.event === "reconnect_attempt") {
103+
const handlers = this.handlers[data.event];
104+
if (handlers) {
105+
handlers.forEach((handler) => handler(data.payload.attempt));
106+
}
107+
return;
108+
}
109+
110+
if (data.type === "binary") {
111+
data.payload = new Uint8Array(data.payload).buffer;
112+
}
113+
114+
const handlers = this.handlers[data.event];
115+
if (handlers) {
116+
handlers.forEach((handler) => handler(data.payload));
117+
}
118+
}
119+
);
120+
}
121+
connect() {
122+
reactNativeAPI()?.post("sio_connect", { url: this.url });
123+
}
124+
on(event: string, callback: (data: any) => void) {
125+
if (!this.handlers[event]) this.handlers[event] = new Set();
126+
this.handlers[event]?.add(callback);
127+
}
128+
off(event: string, callback: (data: any) => void) {
129+
this.handlers[event]?.delete(callback);
130+
}
131+
emit(event: string, data: any) {
132+
reactNativeAPI()?.post("sio_emit", { event, payload: data });
133+
}
134+
}

src/common/experiments.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface Experiment {
66
name: string;
77
description?: string;
88
electron?: boolean;
9+
reactNative?: boolean;
910
reloadRequired?: boolean;
1011
onToggle?: () => void;
1112
}
@@ -22,6 +23,13 @@ export const Experiments = [
2223
name: "WebSocket Zstandard Compression",
2324
description:
2425
"Compress some events with Zstandard compression. This can reduce bandwidth usage."
26+
},
27+
{
28+
id: "RN_NATIVE_WS",
29+
name: "React Native Native WebSocket",
30+
reactNative: true,
31+
description:
32+
"Use the socket.io in react native instead of webview. Will be needed for native WebRTC for stable video calls on the mobile app."
2533
}
2634
] as const;
2735

src/components/settings/ExperimentSettings.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import LegacyModal from "../ui/legacy-modal/LegacyModal";
1313
import { useCustomPortal } from "../ui/custom-portal/CustomPortal";
1414

1515
import { useTransContext } from "@nerimity/solid-i18lite";
16+
import { reactNativeAPI } from "@/common/ReactNative";
1617

1718
const Container = styled("div")`
1819
display: flex;
@@ -65,6 +66,9 @@ const ExperimentItem = (props: { experiment: Experiment }) => {
6566
if (props.experiment.electron && !electronWindowAPI()?.isElectron) {
6667
return true;
6768
}
69+
if (props.experiment.reactNative && !reactNativeAPI()?.isReactNative) {
70+
return true;
71+
}
6872
return false;
6973
};
7074

0 commit comments

Comments
 (0)