DennyHiu
DennyHiu

Reputation: 6060

How to implement websocket client in Electron (Next.js/React)?

I have a working websocket server. I use a websocket as client in web browser/react before, but I'm unable to use Websocket inside electron app since WebSocket depends on browser's compatibility and for some reason, this feature is unavailable in Electron.

I use nextron (nextjs/react + electron) boilerplate.

yarn create nextron-app MY_APP --example with-typescript-material-ui

import React from 'react';
import Head from 'next/head';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import { theme } from '../lib/theme';
import type { AppProps } from 'next/app';

export default function (props: AppProps) {
  const { Component, pageProps } = props;

  // where to put ws here ? this placement generates an error
  const ws = new WebSocket("ws://192.168.100.8:8081/")
  console.log("file: _app.tsx:11 ~ ws", ws)

  React.useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);

  return (
    <React.Fragment>
      <Head>
        <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
      </Head>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </React.Fragment>
  );
}

enter image description here

Upvotes: 3

Views: 1560

Answers (1)

DennyHiu
DennyHiu

Reputation: 6060

Apparently it can't be done. Electron blocks any attempt to use websocket inside the client (React). The only solution so far I use is to convert websocket messages and events into ipcRenderer(https://www.electronjs.org/docs/latest/api/ipc-renderer)

the process is fairly simple: React receives an event and calls the IPCRenderer on the client, The IPCRenderer receive an event in the renderer (node.js) and call upon the ws or the websocket in the node.js

Sending:

React -> IPC -> Node.js -> websocket -> Websocket Server (main server) 

Receiving:

Websocket Server (main server) -> websocket -> Node.js -> IPC -> React

I use the ws module fro node.js from here

I hope this can help someone or myself in the future

If you want to send/broadcast an event to websocket

// in the client side usually the root component, or in my case _app.tsx:
import electron from 'electron'
const ipcRenderer = electron.ipcRenderer

class _app extends React.Component<any, any> {
  constructor(props: any) {...}
  componentDidMount() {...}

  // this will send a message for a button click
  handleClick(msg: string) {
      // reply will be true if it succeed
      let reply = ipcRenderer.sendSync('some-event', msg)
  }
}

later in the main window app.js:

import { app, ipcMain } from 'electron'
import WebSocket from 'ws'

// connect with ws
let ws = new WebSocket(`ws://${YOUR_WS_SERVER_IP}:${YOUR_WS_PORT}/`)

// find the electron main window, mine's in background.ts
const mainWindow = createWindow('main', {
    width: 1366,
    height: 768,
    minWidth: 1366,
    minHeight: 768
})

// .... some electron code here ....

// when ipcMain receive an event named 'some-event'
ipcMain.on('some-event', (event, msg) => {
  ws.send(msg) // send message using websocket here
  event.returnValue = true // give a return value true 
})

If you want to handle an event received from websocket

import { app, ipcMain } from 'electron'
import WebSocket from 'ws'

// connect with ws
let ws = new WebSocket(`ws://${YOUR_WS_SERVER_IP}:${YOUR_WS_PORT}/`)

// find the electron main window, mine's in background.ts
const mainWindow = createWindow('main', {
  width: 1366,
  height: 768,
  minWidth: 1366,
  minHeight: 768
})

ws.on("message", (message: any) => {
   var str = message.toString()
   console.log("Message received: ", str)
   
   mainWindow.webContents.send('some-event', str)
})

on the react component (App.tsx) :

// in the client side usually the root component, or in my case _app.tsx:
import electron from 'electron'
const ipcRenderer = electron.ipcRenderer

class _app extends React.Component<any, any> {
  constructor(props: any) {
      super(props)
      // I put this since I use class based component. 
      // a functional ones won't need this
      this.handleIpc = this.handleIpc.bind(this)
  }
  componentDidMount() {
      this.handleIpc() // to make sure the ipcRenderer manipulate the component state AFTER the whole component was loaded first
  }

  handleIpc() {
    var self = this
    ipcRenderer.on("some-event", function (e, data) {
       console.log("Message received: ", data)
    })
  }
}

Upvotes: 2

Related Questions