mattkhaw / cordova-plugin-callkit

Cordova plugin that enables CallKit + PushKit (iOS) & ConnectionService (Android) functionality to display native UI
MIT License
16 stars 48 forks source link

Websocket getting cut off when working with CallKit API, iOS 17.5 and higher #19

Open seoplague opened 1 month ago

seoplague commented 1 month ago

Before the iOS 17.5 update, calling worked relatively stable. However, starting from this version and on, Safari instantly force-closes all open websocket connections when the "Answer" button on the calling UI is hit. So, basically, the call ends right after it answers, because in our case websockets are crucial for the SIP negotiation process.

Firstly, I inspected the Safari console, and there is a new red error saying: WebSocket connection to 'wss://home.thirdlane.com/wss' failed: The operation couldn’t be completed. Software caused connection abort.

Secondly, I checked the Xcode logs, and there are several warnings saying Invalidating grant <invalid NS/CF object> failed every time the call is answered.

@mattkhaw Maybe you also encountered this problem after upgrading to 17.5 and found a solution to this problem?

mattkhaw commented 1 month ago

Mine seems to work fine. Maybe the problem lies with the SIP used? I'm using WebRTC to handle this though. So far so good. Even works on the latest version of iOS.

seoplague commented 1 month ago

@mattkhaw I also use WebRTC with https://github.com/cordova-rtc/cordova-plugin-iosrtc. After updating to iOS 17.5 I see the following behavior of my app when the phone is locked and the app is closed: I receive a VoiP push and see iOS native call screen. When I pick up the phone I see a sip transport on the sip server for a few seconds, which closes after a few seconds. I can continue speak with caller, but I can handle any sip events correctly: bye, hold and other. And I don't understand what's going on. Maybe you have some suggestions on this?

mattkhaw commented 1 month ago

@seoplague Not sure this post helps but it seems to be similar to your case based on the error that you mentioned in the first post -> Receiving invalid_grant, but only during App Store Review

I understand that it might not be the exact issue that you are facing, but it seems that it might be caused by the backend and according to that link, the backend is using the same token to retry and hence, causes this error. Hope this helps.

seoplague commented 1 month ago

@mattkhaw could you check please your app on IOS > 17.5 this case:

  1. Close App
  2. Block Phone
  3. Make a call
  4. Pickup a phone from Voip Push
  5. Try to open App from Native Call screen
  6. Continue conversation

If in this case your App is working correctly it will helps me in debug my app. Very big thanks.

mattkhaw commented 1 month ago

@mattkhaw could you check please your app on IOS > 17.5 this case:

  1. Close App
  2. Block Phone
  3. Make a call
  4. Pickup a phone from Voip Push
  5. Try to open App from Native Call screen
  6. Continue conversation

If in this case your App is working correctly it will helps me in debug my app. Very big thanks.

Like I mentioned in my previous post, my app seems to be working as intended. This problem is not related to the plugin itself. Most likely caused by your backend.

Qvadis commented 1 month ago

Same behaviour here. After I answer via callkit, sometimes it works and sometimes it doesn't. What I see is that the app code stops working and I see an "Invalid grant" trace in Xcode. Here's another post related https://forums.developer.apple.com/forums/thread/758232?page=1#798910022

seoplague commented 1 month ago

Like I mentioned in my previous post, my app seems to be working as intended. This problem is not related to the plugin itself. Most likely caused by your backend.

@Qvadis or @mattkhaw thanks, but it's my thread :) without answer. if you have Cordova VoiP App which works correctly in my case. Can you share your experience? I use:

  1. "cordova-ios": "^7.1.0",
  2. "cordova-plugin-background-mode": "^0.7.3",
  3. "cordova-plugin-callkit": "^1.0.0",
  4. "cordova-plugin-device": "^3.0.0",
  5. "cordova-plugin-ionic-webview": "^5.0.1",
  6. "cordova-plugin-iosrtc": "^8.0.4",
  7. "cordova-plugin-wkwebview-ionic-xhr": "^2.1.1",
  8. "sip.js": "0.15.11"

And use the following behavior:

cordova.plugins.CordovaCall.on('receiveCall', function () {
      console.log('On Receive Call');
      const ua = new SIP.UA({...});
      ua.connect();
  });

cordova.plugins.CordovaCall.on('answer', function () {
    console.log('On Call Answer');
    answer();
});

After receiveCall in 3-5 seconds ua.transport stoped. Do you use cordova-plugin-backgroundmode ? How ? When you say backend what you mean ?

Qvadis commented 1 month ago

My app is definitely working like yours, @seoplague. I'm experiencing the same (including the Invalidating grant <invalid NS/CF object> failed). I'm very interested in getting to the bottom of this. I've been fighting this for weeks,

Every time I receive the voip it wakes up. It executes code and "dies". Then I answer and again it executes some code and "dies" and the same when I hang up.

"cordova-ios": "^7.1.0", "cordova-plugin-background-mode-fixed": "^0.7.9", "cordova-plugin-ionic-webview": "^5.0.0", "cordova-plugin-iosrtc": "^8.0.4", "cordova-plugin-media": "^7.0.0", "cordova-plugin-nativeaudio": "^3.0.9"

In my case Callkit and background plugins mode are forks but with no significant changes related to this.

I opened a feedback assistant to apple and I attached them the sysdiagnose to see if they can shed some light here.

Let's see if we can help each other!

mattkhaw commented 1 month ago

@seoplague I see. The reason why you don't understand "backend" is because you are using sip.js library and that library is just using P2P. My current setup consists of client app and also a custom dedicated service to help with connection establishment. As for packet routing, I have my own coturn server to handle video streams. The client apps won't communicate with one another using this method. In your case, the client apps are treated as their own servers. I think the problem lies in the architecture and app design.

@Qvadis For your case, maybe you can refer to the paragraph below and see whether this helps or not.

As we all know, there's a restriction for running apps in the background in iOS. Here's a useful resource albeit is from another developer instead of Apple -> iOS Background Execution Limits. I've encountered certain limitations on how to make this work on older iOS devices. The way that I solve this is by implementing a task deferring for connection establishment. Basically, what I did is to create a queue array and insert the intended task into this queue array and process this queue when the app is ready.

Another point I want to note is that, the headache that we all have is mostly the integration between native iOS code and Javascript for this kind of functionality. Native code can run in the background, however JavaScript only runs in the foreground. Basically, for JavaScript to run, the browser container has to be launched successfully and in the ready state. The problem I always have is that the library I'm using for handling my app state and UI is not ready when this happens. For this issue, I implement app wide logging and check from the logs to determine when I should answer/reject the call. Once I know where it is, this never becomes an issue.

FYI, my app doesn't use any background plugins since most if not all background plugins don't work for this type of functionality and just outright gets rejected by Apple and I don't want to deal with this kind of headache. Hence the design of my app won't have issues like this. As for Android (I know it is unrelated but it's good to know), this is not an issue at all since Android apps are queued in the background for continuous processing once it is opened.

From what you guys have described, my suspicion is still on the P2P architecture being not suitable for hybrid apps. There's also another situation might cause this issue is one of the client app calls the other party but hangs up immediately. As a result, the client app that receives the call doesn't have a point to connect to hence it might error out if not handled properly. Not sure whether this is related to your issue or not but no harm in checking that as well.

If you guys are interested to know what plugins I used, here's the list:-

  1. "cordova-ios": "^6.2.0"
  2. "cordova-plugin-iosrtc": "6.0.21"

The reason I don't bother with listing other plugins is because they are unrelated to this issue. As you can see, I'm not using the latest cordova-ios version nor using ionic for my app. This is because my app is not ready to be updated to the latest versions without breaking some other plugins or core functionality but this will be done when the time comes.

Sorry for the lengthy reply. I hope my suggestions help.

seoplague commented 1 month ago

@mattkhaw Thank you for your detailed answer. Could you describe in more details your approach for this point.

The problem I always have is that the library I'm using for handling my app state and UI is not ready when this happens. For this issue, I implement app wide logging and check from the logs to determine when I should answer/reject the call. Once I know where it is, this never becomes an issue.

I guess it can helps me for solving my issue. Thanks.

mattkhaw commented 1 month ago

@mattkhaw Thank you for your detailed answer. Could you describe in more details your approach for this point.

The problem I always have is that the library I'm using for handling my app state and UI is not ready when this happens. For this issue, I implement app wide logging and check from the logs to determine when I should answer/reject the call. Once I know where it is, this never becomes an issue.

I guess it can helps me for solving my issue. Thanks.

Regarding this issue, this is an isolated incident since I'm using a third party library to handle my UI design and this library also has some lifecycle event handling built into it. Since the library I'm using also has event bus, I used this to handle the plugin's events for the JavaScript portion. In my case, there's an issue where these custom events don't fire because the library is not in the ready state hence those events don't get attached and ultimately, code doesn't get executed.

Qvadis commented 1 month ago

Thanks a lot @mattkhaw for your time.

Let me share a more detailed experience. I don't know if the plugin has nothing to do since it happens from iOS 17.5 and it seems to be something related to resources optimization (?).

When I receive the VOIP notification the app gets ready and executes the code inside the notification listener, and if the system allows it, it logins into our SIP server (asterisk is the server and JsSIP is the library I'm using). When the system stop the code, the thread freezes and when I answer it resumes from this point. This could be dangerous since the app has calling states and it may lead to unknown scenarios.

After the answer, my app, that is already (or not) registered and connected to the SIP server, tries to establish the call. If iOS allows it, it establish the call and the communication happens. The app, however, freeze again and thus the call progress, the states and listeners doesn't. What is the result of this? The callee doesn't get notified when the caller hangs up, doesn't receive SIP messages or other important tasks cannot get executed.

Once I hang up, the app resumes and the events that happened during the call are lost.

So, to wrap things up: The javascript container gets executed and the app is ready, but something is freezing the app blocking it from progress normally (since 17.5, this is important).

Every time it freezes and stop the code I see the "Invalidating grant <invalid NS/CF object> failed"

Cheers, Borja.

seoplague commented 1 month ago

@mattkhaw @Qvadis the problem was solved after updating IOS to 17.6.1.

Qvadis commented 1 month ago

@seoplague I'm experiencing the same. Do you see the invalid grant message gone as well?

EDIT: Something changed because with the app store app it seems to be working (I've made 10 tests, gotta test it more) but with the development app still the same :S

seoplague commented 1 month ago

@Qvadis The invalid grant message did not disappear. But the sip socket remains connected and everything is working.

mattkhaw commented 1 month ago

@seoplague @Qvadis I've updated my Xcode app over the weekend to 15.2 since my MacBook only can support up to Ventura. I think there's something to do with their SDK. The reason why I've updated it is because I couldn't publish my app since I'm still using SDK 16 and I've tried using that to build a new version and install it on my phone. Every time I've tried to start the app, it just hangs there perpetually but on some restarts, my app works like normal and yes, I've checked the logs in Xcode and invalid grant is in the log entries.

I need some time to experiment with it. I can honestly say it is not the plugin's issue. I'll try to use the latest builds for everything and see whether it fixes itself or not.

mattkhaw commented 1 month ago

In my previous post, I did mention I have some issues where there's White Screen of Death (WSOD). This is just caused by the window.screen.orientation object in JavaScript. Turns out that lock and unlock functions have been completely removed in later versions. After I disable this in my code, it works fine.

Now onto "invalid grant" issue, not sure whether this will fix it for you but it did fix for me. Turns out that after upgrading Xcode, all the caches have to be deleted and rebuild again. Once I deleted all the caches, that "invalid grant" messages have completely gone.

The caches I mentioned that needs to be rebuild are Xcode Caches and Project Build Data and Indexes. The image below as reference. This option can be accessed via Settings > Storage > Developer.

This issue is totally unrelated to Cordova. It's just an Xcode issue.

mattkhaw commented 1 month ago

Thanks a lot @mattkhaw for your time.

Let me share a more detailed experience. I don't know if the plugin has nothing to do since it happens from iOS 17.5 and it seems to be something related to resources optimization (?).

When I receive the VOIP notification the app gets ready and executes the code inside the notification listener, and if the system allows it, it logins into our SIP server (asterisk is the server and JsSIP is the library I'm using). When the system stop the code, the thread freezes and when I answer it resumes from this point. This could be dangerous since the app has calling states and it may lead to unknown scenarios.

After the answer, my app, that is already (or not) registered and connected to the SIP server, tries to establish the call. If iOS allows it, it establish the call and the communication happens. The app, however, freeze again and thus the call progress, the states and listeners doesn't. What is the result of this? The callee doesn't get notified when the caller hangs up, doesn't receive SIP messages or other important tasks cannot get executed.

Once I hang up, the app resumes and the events that happened during the call are lost.

So, to wrap things up: The javascript container gets executed and the app is ready, but something is freezing the app blocking it from progress normally (since 17.5, this is important).

Every time it freezes and stop the code I see the "Invalidating grant <invalid NS/CF object> failed"

Cheers, Borja.

@Qvadis Have you tried putting some processes in background? Seems like it might be a threading issue. If your processes freezes the UI, JavaScript will just stop running and also, if the call UI screen is up, your app will be put in the background as well and that also will cause JavaScript to temporarily stop.

I assume that your login process is automated right? What I mean by this is the login credentials are stored somewhere locally and just use those information to login to your server? If yes, then what I can suggest is to relocate the code to answer call.

Decline is pretty simple, that is basically handled by your signalling server anyways. As for answer, when the user answers a call, this will redirect back to your app and from there, just try to login to your server and establish the call. If there's an error, just terminate. I think this is a much better solution.

Just bear in mind that, don't do any complicated processing in the notification listener of the plugin. The window is just too short. It's just enough to allow the app to be kept alive. Not sure this helps but that's all I got.

Qvadis commented 1 month ago

@mattkhaw thanks for the tips! I really appreciate your help.

Apparently Apple has fixed the issue because it started working again. Although the invalid grant is present, the code doesn't freeze at all (in production. I have to test it in development).

Have you tried putting some processes in background? Seems like it might be a threading issue. If your processes freezes the UI, JavaScript will just stop running and also, if the call UI screen is up, your app will be put in the background as well and that also will cause JavaScript to temporarily stop.

I didn't and frankly, I don't know which and how, but I thought that eventhough the callkit UI is up the javascript container is also working :S

I assume that your login process is automated right? What I mean by this is the login credentials are stored somewhere locally and just use those information to login to your server? If yes, then what I can suggest is to relocate the code to answer call.

Yes, it is. I though that too and now I don't login there but I do some checks and restart the SIP connections if necessary. That is a good approach indeed.

Thanks for all. I'll keep you guys posted with any new information