TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸

.NET Core SignalR with React Native

October 16, 2019

code
# .NET Core 2.2
#npm i @aspnet/signalr
#yarn add @aspnet/signalr

# .NET Core 3.0
npm install @microsoft/signalr

@microsoft/signalr works with .NET Core 3.1 and has a withAutomaticReconnect() function that we can use for reconnecting if connection lost. This makes code simpler. And it works with WebSocket by default so we don't need the heartbeat either.

code
const connection = new SignalR.HubConnectionBuilder()
  .withUrl("https://api.mysite.com/signalhub", {
    accessTokenFactory: () => TOKEN,
  })
  .configureLogging(SignalR.LogLevel.Debug)
  .withAutomaticReconnect() // this will reconnect if connection lost
  .build();

Working code example (@aspnet/signalr)

code
// App.tsx
import React, { useEffect, useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import * as SignalR from "@aspnet/signalr";

export default function App() {
  const [connectionState, setConnectionState] = useState(false);
  const [connectionError, setConnectionError] = useState();
  useEffect(() => {
    signalr();
  }, []); // passing an empty array makes sure this only runs on mount/unmount and not everytime props/state changes. if you give it something inside the array it'll only update when that something updates

  function signalr() {
    // das function to start a connection with SignalR hub for our app
    const token = "mySuperSecretToken";
    const connection = new SignalR.HubConnectionBuilder()
      .withUrl("https://api.mysite.com/signalhub", {
        accessTokenFactory: () => token,
      })
      .configureLogging(SignalR.LogLevel.Debug)
      .build();

    async function start() {
      try {
        await connection.start();
        setConnectionState(true);

        setInterval(() => {
          connection.invoke("heartbeat");
          console.log(`beat`);
        }, 15000); // Send heartbeat every 15 secs.
        setConnectionError("");
        console.log("CONNECT ho gya!");
      } catch (error) {
        console.error(`Disconnected with status code ${error.statusCode}`, JSON.stringify(error));
        setConnectionError(JSON.stringify(error));
        setTimeout(() => start(), 5000); // Retry connection after 5secs
      }
    }

    // connection.serverTimeoutInMilliseconds = 3600000
    // connection.keepAliveIntervalInMilliseconds = 10000
    start();

    // Reconnect
    connection.onclose(async (error) => {
      setConnectionState(false);
      setConnectionError(JSON.stringify(error));
      console.log("awwww =( chala gya");
      console.error(`Disconnected with status code ${error.statusCode}`, JSON.stringify(error));
      await start();
    });

    // Listen for events
    connection.on("messageReceived", (data) => {
      console.log(`\n data: \n ${JSON.stringify(data, null, "\t")} \n`);
    });

    connection.on("notificationReceived", (data) => {
      console.log(`\n data: \n ${JSON.stringify(data, null, "\t")} \n`);
    });
  }

  return (
    <View style={styles.container}>
      <Text>
        You are{" "}
        <Text style={styles.boldText}>
          {connectionState ? (
            <Text style={styles.greenText}>connected</Text>
          ) : (
            <Text style={styles.redText}>disconnected</Text>
          )}
        </Text>
        <Text>{connectionError ? connectionError : null}</Text>
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  boldText: {
    fontWeight: "bold",
  },
  greenText: {
    color: "#2ECC71",
  },
  redText: {
    color: "#E74C3C",
  },
});

Notes

  • You need to invoke something at regular intervals in order to stay connnected. Otherwise it'll timeout the connection every 30/60 secs..
  • Doesn't matter if the method you're invoking exists. If it doesn't, you'll get a HubException error about method not existing, but you'll stay connected nonetheless.
  • i have tried setting the keepAliveIntervalInMilliseconds and serverTimeoutInMilliseconds values on the frontend but that didn't work
  • The { accessTokenFactory: () => token } bit alllows you to pass a token for authorization.
  • There are different LogLevels. Debug will give you more details