We are noticing issues with Safari iOS not calling Websocket events when the Websocket connection is lost. Our web application has no clue the Websocket's connection has been lost. On Android devices, as soon as the connection is severed, the close and error Websocket events are fired.
We created a quick example.
Websocket server in nodeJS
const WebSocket = require('ws');
const wss = new WebSocket.Server({port: 8080});
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
ws.send(`You sent: ${message}`);
});
ws.on('close', function close() {
console.log('Client has disconnected');
});
});
Simple client
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Example</title>
</head>
<body>
<h1>WebSocket Example</h1>
<div id="output"></div>
<form>
<label>
Message:
<input type="text" id="message">
</label>
<button type="submit" id="send">Send</button>
</form>
<script>
const output = document.getElementById('output');
const messageInput = document.getElementById('message');
const sendButton = document.getElementById('send');
const ws = new WebSocket('ws://localhost:8080');
ws.addEventListener('open', function (event) {
console.log((new Date()).toISOString(), '********************** OPEN **********************');
});
ws.addEventListener('close', function (event) {
console.log((new Date()).toISOString(), '********************** CLOSE **********************');
});
ws.addEventListener('error', function (event) {
console.log((new Date()).toISOString(), '********************** ERROR **********************');
});
ws.addEventListener('message', function (event) {
console.log((new Date()).toISOString(), '********************** MESSAGE **********************');
// Append the message to the output div
const message = document.createElement('p');
message.textContent = event.data;
output.appendChild(message);
});
sendButton.addEventListener('click', function (event) {
event.preventDefault();
const message = messageInput.value;
// Send the message to the server
ws.send(message);
});
</script>
</body>
</html>
When the above code runs, iOS mobile Safari does not fire the events close
or error
when the Websocket connection is closed.
Examples of closing the Websocket are:
- Putting device on airplane mode
- Powering off wifi router
- Turning device's wifi off
As mentioned before this works fine on Android and other devices, only iOS Safari behaves this way, has anyone ever encountered this with their Web Applications?
EDIT 2023 05 18:
There is a Webkit bug reported related to this behavior here: https://bugs.webkit.org/show_bug.cgi?id=247943
A temp workaround would be to handle the window.onoffline event to warn users etc. But, hopefully they can fix this soon.
window.onoffline
is a nice trick but in my testing I found that if the connection lapses for more than ~15 seconds, it never recovers and no event like onerror is raised leaving a zombie connection I don't know is dead. I ended up implementing my own heartbeat protocol over the websocket to detect the dropped connection and force recreate – Illfounded