Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS/iPadOS/Safari MacOS does not detect loss of websocket connectivity after network change causing data loss #139

Open
2 tasks done
rcbevans opened this issue Jun 1, 2023 · 1 comment
Assignees
Labels
bug Something isn't working

Comments

@rcbevans
Copy link

rcbevans commented Jun 1, 2023

Checklist

Describe the bug
There is an open bug in Webkit in which it doesn't raise the correct onerror/onclose handlers when the network is lost/changes. y-websocket does not detect a loss of connectivity and therefore doesn't handle it appropriately. This means the client doesn't know that it is offline and all updates are fired off into the abyss causing user data loss without local persistence.

https://bugs.webkit.org/show_bug.cgi?id=247943
https://stackoverflow.com/questions/75869629/ios-websocket-close-and-error-events-not-firing

To Reproduce
Steps to reproduce the behavior:

  1. Create a y-websocket connection on an iPhone when using Wi-Fi.
  2. Turn off Wi-Fi so network connectivity switches back to cellular.
  3. No onerror/onclose is raised by webkit and subsequently, by the websocket provider, so the client is unaware it is offline and any changes sent are lost.
  4. The connection never recovers, so even though other network calls will succeed, any y-doc changes will be lost.

Expected behavior
y-websocket should detect that connectivity has been lost and raise the appropriate connection lifecycle events so the user can be notified, and the underlying websocket recreated to re-establish a connection.

Environment Information

  • iPhone, any browser
  • iPad, any browser
  • MacOS, Safari

Additional context
This is not a y-websocket issue but an underlying issue in Webkit; but it is a pretty devastating bug for any products using it since user data is lost without any warning.

I was forced to implement a custom heartbeat in my websocket packets and detect loss of connectivity so I could manually recreate the websocket to reestablish a connection. (There is also an issue here, since calling provider.close() does not clean up internal state because it relies on _ws.onclose firing to do clean up, so it was necessary to manually clean up internal state between provider.close() and provider.connection().

I'm not sure if this is something y-websocket could/should try and handle itself, but I wanted to file an issue so anyone else using the library and investigating reports of iDevice data loss could hopefully see and workaround the problem since Apple are taking their time to address the root cause.

@rcbevans rcbevans added the bug Something isn't working label Jun 1, 2023
@grootgordon
Copy link

grootgordon commented Jul 3, 2023

I got the same error when use iOS15.0 WebView and iOS16.4.1 WebView.
UserAgent as follow
Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148')
Mozilla/5.0 (iPhone; CPU iPhone OS 16_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148

but Its ok in iOS14.0.3(safari 14.4.2) Webview.

I have tried to use ua-parser-js npm library to distinguish different os and webkit version, and emit close event by manual, the code snippet as follow:

import { UAParser } from 'ua-parser-js'; 
export class HocuspocusProviderWebsocket extends EventEmitter {

clientUA = new UAParser(window.navigator.userAgent)
  getEngineName(): string {
    return this.clientUA.getEngine().name?.toLowerCase() ?? '';
  }

  getOSName(): string{
    const os = this.clientUA.getOS();
    return os.name?.toLowerCase() ?? ''
  }

  getOSVersion(): number {
    const os = this.clientUA.getOS();
    return parseFloat(os.version ?? '0');
  }
}
....

if (
      this.getEngineName() === 'webkit' &&
      this.getOSName() === 'ios' &&
      this.getOSVersion() >= 15.0
    ) {

      const payload = {
        code: 4410,
        reason: 'Client Connection Timeout',
      }

      this.emit("close", { event: payload })
    } 

the result is: iOS 15.0 is fine when turn off wifi, it can switch to cellular automatically.
but iOS16 still hang and cannot receive 'close' event, that's very confusing :-(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants