A decentralized voice chat application based on a private Tailscale network.
简体中文 this english version is translated by AI from simplified chinese version
RelayX is an innovative decentralized voice communication application that leverages the powerful network capabilities of Tailscale to provide a secure, low-latency voice communication experience. It utilizes a private Tailscale network controller Headscale to achieve fully controllable services. RelayX aims to create a user-controlled voice communication platform without the need for central servers.
This project is created using the Vite React TypeScript template, with the frontend built using React and TypeScript, and the backend using Electron and pure JavaScript.
This video is recorded using OBS at a resolution of 3840*1080, which is two 1080p screens horizontally stitched together. The left side shows a Hyper-V virtual machine and its running client, while the right side shows the client in the host machine.
- Decentralized Network: Based on Tailscale tunneling technology, it achieves true peer-to-peer connections, eliminating the need for central servers and ensuring user privacy and data security.
- Built-in Lightweight Tailscale Client: Uses a rebuilt lightweight Tailscale client, eliminating the dependency on additional Tailscale services.
- Voice Input Noise Reduction: Implements real-time voice input noise reduction using rnnoise from xiph.
- Cross-Platform Support: Currently only supports Windows.
- Channel Management: Allows users to create and manage channels, facilitating the organization and management of user groups.
- User Status Management: Real-time synchronization of user online status and voice channel status.
- Modern UI: Built with Shadcn/ui component library and Tailwind CSS to create a beautiful and responsive user interface.
- Screen Capture Sharing: During voice calls, users can capture screen content and share it with other users in the channel.
- Audio Capture Sharing: Captures audio-playing processes in the system and attaches them to the voice output, supported by an audio capture reconstruction plugin I wrote: win-process-audio-capture-0.0.4
- Frontend:
- React
- TypeScript
- Vite
- Shadcn/ui
- Tailwind CSS
- Zustand (State Management)
- React-resizable-panels (Resizable Panels)
- React-rnd (Draggable and Resizable Components)
- Framer Motion (Animation Library)
- Lucide React (Icon Library)
- Tailwind-merge (CSS Class Name Management)
- Backend:
- Electron
- Node.js (CommonJS)
- Tailscale Go SDK (Embedded Tailscale Client)
- Koffi (Node.js FFI Library for loading DLLs)
- win-process-audio-capture (Windows Process Audio Capture)
- Other:
- electron-builder (Electron Application Packaging)
- concurrently (Concurrent Execution of Commands)
- cross-env (Cross-Platform Environment Variable Setting)
- Chokidar (File Watching)
- dotenv (Environment Variable Management)
RelayX is a typical Electron application, and its architecture is mainly divided into two parts: the frontend (React) and the backend (Electron/Node.js), bridged by a preload script (preload.js
).
-
Frontend (React): Responsible for user interface rendering and user interaction logic. Uses Zustand for global state management, including channel information, user information, current user status, database configuration, etc. The component library used is Shadcn/ui, and Tailwind CSS is used for style customization.
-
Backend (Electron/Node.js): Responsible for system-level operations, Tailscale client integration, audio and video processing, inter-process communication (IPC), etc. Uses pure JavaScript and CommonJS modules for easy integration with the Electron environment.
-
Preload Script (preload.js): Executed before the rendering process is loaded, used to securely expose Node.js APIs to the rendering process, implementing a communication bridge between the frontend and backend.
-
Tailscale Integration: Through a Tailscale embedded client (
tailscale-embed
) written in Go and compiled into a DLL (Windows) dynamic library, Node.js loads and calls it through the Koffi FFI library. This embedded Tailscale client provides a proxy forwarding service on a locally specified port, implementing the forwarding of WebRTC requests from the frontend to the Tailscale network. -
Audio Processing: Uses the Web Audio API for audio stream processing, including noise reduction, gain control, audio analysis, etc. The audio node connection status is shown in the diagram below:
Audio Node Connection Diagram
graph TB A["sourceNode<script>mermaid.initialize({startOnLoad:true});</script>
MediaStreamSource"] --> B["gainNode
GainNode"] B --> C{isNoiseReductionEnabled} C -->|true| D["processorNode
NoiseProcessor"] D --> E["mergerNode
ChannelMerger"] C -->|false| Esubgraph Addon Audio Processing F["handleAddonDataNode<br/><i>AudioWorkletNode</i>"] --> G["addonGainNode<br/><i>GainNode</i>"] G --> H["addonDestinationNode<br/><i>MediaStreamDestination</i>"] H --> I["localAddonStream<br/><i>MediaStream</i>"] end G --> E E --> J["destinationNode<br/><i>MediaStreamDestination</i>"] E --> K["analyser<br/><i>AnalyserNode</i>"] %% Final Outputs J --> L["localFinalStream<br/><i>MediaStream</i>"] %% Input Source M["localOriginalStream<br/><i>MediaStream</i>"] --> A %% Style classDef default fill:#f9f,stroke:#333,stroke-width:2px; classDef stream fill:#bbf,stroke:#333,stroke-width:2px; class I,L,M stream;
-
State Management (Zustand): RelayX's core voice and connection management functions are built using Zustand.
Zustand State Management Diagram
graph LR<script>mermaid.initialize({startOnLoad:true});</script>subgraph stores subgraph internals subgraph audio useAudioDeviceStore["useAudioDeviceStore<br><i>src/stores/internals/audio/audioDeviceStore.ts</i>"] useAudioProcessing["useAudioProcessing<br><i>src/stores/internals/audio/audioProcessingStore.ts</i>"] end useBlankStreams["useBlankStreams<br><i>src/stores/internals/blankStreamsStore.ts</i>"] useMediaStream["useMediaStream<br><i>src/stores/internals/mediaStreamStore.ts</i>"] useRTC["useRTC<br><i>src/stores/internals/rtcStore.ts</i>"] useTailscale["useTailscale<br><i>src/stores/internals/tailscaleStore.ts</i>"] useDB["useDB<br><i>src/stores/internals/DBStore.ts</i>"] useChannel["useChannel<br><i>src/stores/internals/channelsStore.ts</i>"] useRemoteUserStore["useRemoteUserStore<br><i>src/stores/internals/remoteUserStore.ts</i>"] useCurrentUser["useCurrentUser<br><i>src/stores/internals/currentUserStore.ts</i>"] useScreenShare["useScreenShare<br><i>src/stores/internals/screenShareStore.ts</i>"] useCurrentChannel["useCurrentChannel<br><i>src/stores/internals/currentChannelStore.ts</i>"] end useMirror["useMirror<br><i>src/stores/mirrorStates.ts</i>"] usePopover["usePopover<br><i>src/stores/popoverStore.ts</i>"] end subgraph types subgraph internals_types subgraph audio_types AudioDeviceState["AudioDeviceState<br><i>src/types/internals/audio/audioDeviceTypes.ts</i>"] AudioProcessingState["AudioProcessingState<br><i>src/types/internals/audio/audioProcessingTypes.ts</i>"] end BlankStreamsStore["BlankStreamsStore<br><i>src/types/internals/blankStreamsStoreTypes.ts</i>"] MediaStreamStore["MediaStreamStore<br><i>src/types/internals/mediaStreamStoreTypes.ts</i>"] RTCStore["RTCStore<br><i>src/types/internals/rtcTypes.ts</i>"] TailscaleStore["TailscaleStore<br><i>src/types/internals/tailscaleStoreTypes.ts</i>"] DBStore["DBStore<br><i>src/types/internals/DBStoreTypes.ts</i>"] ChannelStore["ChannelStore<br><i>src/types/internals/channelsStoreTypes.ts</i>"] RemoteUserState["RemoteUserState<br><i>src/types/internals/remoteUserStoreTypes.ts</i>"] CurrentUserStore["CurrentUserStore<br><i>src/types/internals/currentUserStoreTypes.ts</i>"] ScreenShareStore_type["ScreenShareStore<br><i>src/types/internals/screenShareStore.ts</i>"] CurrentChannelStore_type["CurrentChannelStore<br><i>src/types/internals/currentChannelStoreTypes.ts</i>"] end MirrorState["MirrorState<br><i>src/stores/mirrorStates.ts</i>"] PopoverState["PopoverState<br><i>src/stores/popoverStore.ts</i>"] end useAudioDeviceStore --> AudioDeviceState useAudioProcessing --> AudioProcessingState useBlankStreams --> BlankStreamsStore useMediaStream --> MediaStreamStore useRTC --> RTCStore useTailscale --> TailscaleStore useDB --> DBStore useChannel --> ChannelStore useRemoteUserStore --> RemoteUserState useCurrentUser --> CurrentUserStore useScreenShare --> ScreenShareStore_type useCurrentChannel --> CurrentChannelStore_type useMirror --> MirrorState usePopover --> PopoverState useCurrentChannel --> useCurrentUser useCurrentChannel --> useChannel useAudioDeviceStore --> useAudioProcessing style stores fill:#f9f,stroke:#333,stroke-width:2px style types fill:#ccf,stroke:#333,stroke-width:2px classDef storeNode fill:#f9f,stroke:#333,stroke-width:2px; classDef typeNode fill:#ccf,stroke:#333,stroke-width:2px; class use*,useMirror,usePopover storeNode class Audio*,Blank*,Media*,RTC*,Tailscale*,DB*,Channel*,Remote*,Current*,MirrorState,PopoverState typeNode class ScreenShareStore_type typeNode class CurrentChannelStore_type typeNode
This mermaid diagram is generated by AI, please refer to the actual code for accuracy.
- Node.js >= 20.0.0 (electron-builder requires Node.js 20+)
- Yarn (Recommended package manager)
- Go (For compiling the Tailscale embedded client)
- Windows development environment (Current file synchronization script and some features are only tested on Windows)
- Hyper-V Windows virtual machine (Optional, for running multiple clients)
- Enable Windows Hyper-V feature
- Install Windows in the virtual machine (Windows 11 is not recommended)
- Install development environment and configuration tools
Refer to the project copy tool files-sync
The tests
directory of this project contains a file synchronization tool tests/files-sync. The core code of this tool differs slightly from the standalone repo mentioned above, but due to the intermittent nature of Windows file sharing, it is recommended to use the aforementioned files-sync to copy the project to Hyper-V.
- Clone the code repository
git clone https://github.com/Need-an-AwP/RelayX.git cd RelayX
- Install dependencies
yarn install cd tailscale-embed go mod tidy
go mod tidy
is optional. This project repository already includes pre-compiled DLL files. You can skip this step if you do not need to recompile. - Configure environment variables
Create a
.env
file in the project root directory and configure environment variables as shown in .env.sample.CONTROL_URL
is the Headscale controller address.SERVER_URL
is the RelayX channel management server address.NODE_AUTH_KEY
is the Tailscale authentication key.HEADSCALE_AUTH_KEY
is the Headscale authentication key.FULLCOPY
is whether to enable full file synchronization.BASE_DESTINATION
is the target path for file synchronization.
NODE_AUTH_KEY
,FULLCOPY
,BASE_DESTINATION
,SERVER_URL
are all optional configurations. - Start the project in development mode
yarn dev
The RNN noise reduction model will be loaded when development starts. Its file is large and will occupy about 30 seconds of Vite preparation time. This issue will not occur in the startup after packaging. In the future, a WASM version of the noise reduction model will be used to replace the existing pure JS version.
- Recompile the Tailscale embedded client (Optional)
yarn dev:build
Before running this command, please ensure that the Go environment is installed and
go mod tidy
has been run.
yarn build:fe
yarn build:win
- Replace the existing pure JavaScript version of the noise reduction model with a WASM version to improve performance and reduce loading time.
- Improve the connection handling logic for preset channel users and channel users obtained from the channel service.
- Add support for more platforms, such as full support for macOS and Linux platforms.
- Add file transfer functionality.
- Add user text chat.
- Add file transfer functionality.
- Add user status indicators.
- Improve unit tests and integration tests to enhance code quality and stability.