kuzzle icon indicating copy to clipboard operation
kuzzle copied to clipboard

Losing subscription strangely

Open ScreamZ opened this issue 5 months ago • 4 comments

Hi, I'm using Kuzzle SDK JS for a React Native mobile application and ESP32 devices (using WebSocket custom calls in C/C++ for them).

Both are using API Keys for authentication. So for C++, I'm just setting the jwt key in the pushed JSON.

I'm observing the same behavior on both devices. Or at least something very close.

Context (Environment)

  • Kuzzle version: 2.27.4
  • Node.js version: 16.20.2
  • SDK version: 7.10.7
  • Authenticated through: API KEY (k-apikey)

Observations

Everything works well for some time. Then at some point, I'm losing real-time subscriptions.

  • On JS SDK side it looks like a disconnect event is triggered.
  • On ESP32 no disconnect event is emitted from the socket client (https://github.com/Links2004/arduinoWebSockets)
  • When losing a subscription the device is still connected as I can publish new events from it. It has just not received new events from the subscription as it looks not subscribed anymore.

Possible cause

I've seen using an observer on the server side that strange things occur

1. Subscribe on the Server side to presence (click to unfold code)
 kuzzle.realtime.subscribe(
    index,
    "presence",
    {},
    (notification) => {
      if (notification.type !== "user" || !notification.volatile.device) return;

      console.log(
        `Presence change for device ${notification.volatile.device} to ${notification.user}`,
      );
    },
    { users: UserOption.all, scope: ScopeOption.none },
  );
}
  1. Boot the device and subscribe.
  2. Disconnect abruptly (like killing the app, or pressing the restart button on an ESP32)
  3. and therefore boot and subscribe again.

-> Everything works.

Until the server receives the timeout event of the first socket being closed abruptly.

Presence change for device ESP_1 to in
Presence change for device ESP_1 to in
Presence change for device ESP_1 to out
Presence change for device ESP_1 to out

-> It seems it clears all new subscriptions. ?

Possible cause

If confirmed, I'm pretty sure the issue is about hotelClerk thinking it's the same connection and, therefore, the same subscription and clear all ? But it seems the ws instance is generating a uuid v4 for connectionID, so I don't know how its possible.

Might it be related to API Keys usage ?

Issue happens on SDK JS too, so I'm not sure this is related to my implementation

Workaround

  • Regularly sending a subscribe request with the same payload as the subscription ID is idempotent ?
  • Rely on polling at regular intervals ?

ScreamZ avatar Jan 18 '24 12:01 ScreamZ

This could possibly be due to the websocket layer sending a PING message and the device no responding with a PONG message? Can you check this?

https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#pings_and_pongs_the_heartbeat_of_websockets

etrousset avatar Jan 18 '24 14:01 etrousset

@etrousset Thanks for helping me with that :)

While this could justify the issue with the disconnection, it seems it has been handled already by ArduinoWebsocket library:

https://github.com/Links2004/arduinoWebSockets/blob/93707d455fff3c667aedd92b485276101f5e6eba/src/WebSockets.h#L269

https://github.com/Links2004/arduinoWebSockets/blob/93707d455fff3c667aedd92b485276101f5e6eba/src/WebSockets.cpp#L490-L500

But what about losing the subscription in case of reconnecting with the JavaScript SDK?

ScreamZ avatar Jan 18 '24 18:01 ScreamZ

After building my own minimal client with reconnection I can confirm that Kuzzle sdk JS seems not be the issue.

After losing a connection and reconnect I still receive notifications for a small duration then at some point I get nothing.

Now I need to check if it's related to react native or Kuzzle server itself.

ScreamZ avatar Jan 30 '24 10:01 ScreamZ

I made a custom websocket proxy and i can confirm that now I don't lose my subscriptions anymore.

I can confirm that kuzzle works strangely under the scenario of a react native mobile application going out of range of a local area network and coming back. The websocket reconnect (connection 2) and connection 1 drop after the server timeout resulting in connection 2 losing subscriptions.

Here is the proxy server code.

You can just use bun to power a uWebSocket server.

import type { ServerWebSocket } from "bun";

const kuzzleSocket = new WebSocket("ws://192.168.1.140:7512");
let reactSocketSet = new Set<ServerWebSocket<unknown>>();
let heartBeatRef: NodeJS.Timer | null = null;

kuzzleSocket.addEventListener("open", () => {
  console.log("✅ Connected to Kuzzle");
  heartBeatRef = setInterval(() => kuzzleSocket.send(JSON.stringify({ p: 1 })), 5000);
});

kuzzleSocket.addEventListener("message", (event) => {
  if (event.data !== '{"p":2}') console.log(`🦝 Kuzzle Received -> forwarding to React`);
  reactSocketSet.forEach((reactSocket) => reactSocket.send(event.data));
});

kuzzleSocket.addEventListener("close", () => {
  heartBeatRef && clearInterval(heartBeatRef);
  console.log("❌ Kuzzle closed socket");
});

const server = Bun.serve({
  fetch(req, server) {
    const success = server.upgrade(req);
    if (success) {
      // Bun automatically returns a 101 Switching Protocols
      // if the upgrade succeeds
      return undefined;
    }

    return new Response("Not supported!");
  },
  websocket: {
    open(ws) {
      reactSocketSet.add(ws);
      console.log("✅ React opened socket. Socket set size :", reactSocketSet.size);
    },
    async message(ws, message) {
      if (message !== '{"p":1}') {
        console.log(`⚛️ React Received -> forwarding to Kuzzle`);
      }
      kuzzleSocket.send(message);
    },
    close(ws) {
      reactSocketSet.delete(ws);
      console.log("❌ React closed socket. Socket set size :", reactSocketSet.size);
    },
  },
});

ScreamZ avatar Jan 30 '24 13:01 ScreamZ