We use Twilio SDK in our iOS app. It works fine but sometimes didStopListeningForIncomingConnections callback is called with error=31000 ("General error"). After that, the device turns to a strange state: it seems to be online but it's impossible to call it. And it shows "unconnected" state on the device.
So the questions are:
1. What does this 31000 error means?
2. What should we do in such a case? How to reconnect device to Twilio?
Megan from Twilio here.
You can see what an error for Twilio Client means here: https://www.twilio.com/docs/api/client/errors
However, 31000 is a rather vague and less than ideal error message as you describe. In this case, it is likely that the Twilio capability token has probably expired while the application is in the background, and if you merely call the listen method whenever they are receiving the 31000 generic error, it might cause the client SDK to result in a error-retry loop and crash the application eventually.
At the time of your writing with TwilioClient iOS SDK v1.2.5, it is suggested to use the following sample code in your did-stop-listening callback:
- (void)device:(TCDevice*)device didStopListeningForIncomingConnections:(NSError*)error {
if ( [self checkCapabilityTokenStillValid] ) {
// if the token has not yet expired, use the `listen` method of `TCDevice` to resume the listening state
[self.device listen];
}
else {
// restart all over by requesting a new capability token and initialize/update the `TCDevice` instance again
[self login];
}
}
The TwilioClient iOS SDK takes care of dispatching the listen and updateCapabilitiyToken: methods to the current thread for execution, therefore it's safe to call them directly in the didStopListeningForIncomingConnections. The did-stop-listening delegate method is always triggered with dispatch_get_main_queue() of Grand Central Dispatch.
Hope this may help others if they run into the same generic error.
This may or may not be the issue, we have encountered 31000 errors two times in our development and both were a result of generating the JWT on our server api. To be clear the error was a 31000 on the client, but the reason for this was in the construction of the JWT, and the params we wanted twilio to send back to our application.
When passing in an object to allow_client_outgoing or allow_client_incoming the twilio sdk concats this all in their scope attribute in their JWT. It added it to the scope:client:outgoing?appSid= which looks like a query string. That means it has a size limit of 2048. So exceeding this length generates a 31000 error.
In addition adding the objects doesn't seem to always implicitly serialize the object correctly, it introduces characters that can generate errors in their corresponding mobile sdks (but not their web sdk ... weird) so we took care of this by explicitly serializing objects to JSON before they are inserted into the JWT.
I hope both of these examples help you track down the issue.
Related
I'm using Twilio voice quickstart code https://github.com/twilio/voice-quickstart-swift.
When I make a client to client call, call doesn't connect. CallKit runs in the background though, I can see the green notification bar when I send app in the background.
Following is the error:
StartCallAction transaction request failed: The operation couldn’t be completed. (com.apple.CallKit.error.requesttransaction error 7.)
As you can see Googling doesn't help as there doesn't seem to be any solution around?
Does anyone know how to fix or debug it further?
Updated:
Attaching VoIP settings, it's certainly enabled.
Problem is in your code which you write to handle and initialise variables. There is nothing wrong in the Twilio sdk either so don't look there. Anything which you are doing beyond twilio sample code is the place to look for the problem.
I've also wasted months of my time on similar issue and found out that there was issue with initialising one variable.
You are trying to request CXStartCallAction right after another CXStartCallAction was requested. You need to end the first call correctly.
In any case you must follow correct sequence of actions. Once you user wrong action in a sequence, CallKit will return one or another error.
And DO NOT request one action immediately after another is processed. There should be some time between two requests. For example, you initiated CXStartCallAction, then you checked that user is offline and trying to end the call. If that check is quick, then "end action" may result in error. You need to wait a few milliseconds before cancelling the outgoing call.
Twilio developer evangelist here.
Have you enabled capabilities for Voice over IP in the project settings?
Try to initialize CXProvider and CXCallController sooner, before requesting CXStartCallAction
I had the same problem because the Provider and the CallController have been lazy loaded.
It looks like that the CXProvider initWithConfiguration runs asynchronously which means you need to call this early otherwise you run into the risk of having a call without the completion of the initWithConfiguration function.
Thanks to #Allen for pointing me in the right direction.
I'm working on embedding a soft phone into a web page that will go into Odoo (web based ERP system). It will allow inbound and outbound calls for employees.
The token expires every hour. So this means the user will have to refresh the page every hour. I could do an http refresh but if the user is on a call when it does the refresh it will knock them off the call.
How do we get around this so we can build a fully working dialer?
Twilio evangelist here.
I'd suggest using JavaScript to do an asynchronous HTTP request to get a new token from your server and then updating the instance of client with it.
Hope that helps.
Another Twilio evangelist here!
You can actually listen for the offline event on the Twilio.Device object. From the documentation:
.offline( handler(device) )
Register a handler function to be called when the offline event is
fired. This is triggered when the connection to Twilio drops or the
device's capability token is invalid/expired. In either of these
scenarios, the device cannot receive incoming connections or make
outgoing connections. If the token expires during an active connection
the offline event handler will be called, but the connection will not
be terminated. In this situation you will have to call
Twilio.Device.setup() with a valid token before attempting or
receiving the next connection.
So you want something like:
Twilio.Device.offline(function(device) {
fetchTokenFromServer(function(token) {
device.setup(token);
});
});
where fetchTokenFromServer makes the HTTP request that Devin suggested in his answer.
Let me know if this helps.
I just ran into this issue so hopefully my solution can help you and others.
I was using twilio.js v1.3 and tried implementing my offline callback like #philnash recommended, but kept getting the error device.setup is not a function. I then tried using Twilio.Device.setup(newToken) and was able to get the capability token refreshed but also ended up getting a new error: Cannot read property 'setToken' of undefined.
I ended up having to use twilio.js v1.4 to make the error go away. My working solution looks like this:
Twilio.Device.offline(function(device) {
$.ajax(urlToGetNewToken, type: 'get').done(function(newToken) {
Twilio.Device.setup(newToken)
})
})
Sometimes when I call [[FIRMessaging messaging] subscribeToTopic:myTopic] (or unsubscribe) I see errors logged in the console. Sometimes they have error codes, and other times it's just a message like this:
Cannot unsubscribe to topic: /topics/my_topic with token: (null)
This one appears to be because it doesn't think it has a token, though usually when I see this I've already given it a token.
There is, however, no apparent programmatic way to know when an error occurred. There's no callback passed to the subscribe methods, and their return types are void. I think I read somewhere in my Googling of these errors that the library will retry on its own, but I can't find that back now, and it's not in the documentation for FIRMessaging anywhere. The error codes are also not documented anywhere that I can find.
One error code I remember seeing specifically is 5. My implementation used to simplistically re-subscribe / unsubscribe from each topic when the user changed any of them; when I modified this to only update the topic that actually changed, that particular error went away, so maybe it was complaining because I was subscribing to a topic that I was already subscribed to, and vice versa?
How do I deal with these errors? Is it true that the library will retry on its own? And can someone link to an error code listing?
You don't have to deal with errors that result from subscribe and unsubscribe, it is retried by the SDK automatically. See docs for more.
Issues with the current error messages is a known, we will improve them in future releases.
TLDR: Is there some way I can use a callback or get a return value from react to my native code (iOS)? Or can I use a set of locks to enforce ordering for eventdispatcher and eventemitter listeners to enforce an ordering?
More Information:
So I'm using the event dispatcher in my iOS code (self.bridge.eventDispatcher) and that's working great. I'm able to send events with information to my react code.
However, I noticed that this works asynchronously. I currently use this because if I require information on my iOS side, I send a ping to my react side requesting this information. I then lock upon the request and wait for my react code to use NativeModules and invoke an iOS method where I get the requested data.
Basically, enforcing a synchronous pattern feels a little dangerous because I'm not sure whether bridging methods can fail. For example, I can send an event to react and then lock. If my react side does not get it, or fails to send a notification to the iOS side, then I will never unlock and then will have deadlock. So to this, I have two questions. Is bridging reliable enough to avoid deadlocking through this method? Or is there a better way to accomplish the same result and request information from my react side from my iOS code?
Awesome so I got it, turns out there's a structure called RCTResponseSenderBlock. I made another
iOS method:
-(void)tmpMethod:(RCTResponseSenderBlock)callback{
[self.bridge.eventDispatcher sendAppEventWithName:#"channel" body:#{#"Block":callback}
] ;
}
javascriptReciever:
EventEmitter.addListener("channel", async event => {
console.log(event)
event.Block(["Hello There"]);
return;
});
ios Method Invocation:
[self tmpMethod:^(NSArray* response){
NSLog((NSString*)[response objectAtIndex:0]); //prints Hello There
}];
UPDATE
Turns out when I try to do the same for Android, I can't. The Android platform uses WriteableMap or WriteableArray to send events using the following method:
private void sendEvent(ReactContext reactContext,
String eventName,
#Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
WriteableMap and WriteableArray both do not accept objects such as callbacks which made it not possible to request information from the Javascript side. I also attempted to pass in a Promise as opposed to a WriteableMap or WriteableArray and that threw an error. To communicate asynchronously in a synchronous context for android,
I will have to send an event from native to javascript
lock twice to prevent further execution in side native
on my javascript side invoke a method on my native side once viewing the request with the requested data
unlock within the native method invoked by javascript
resume execution since the program has been unlocked
unlock once further after handling whatever needed to be handled synchronously. (Comment if you want my code implementation)
EDIT AGAIN:
The above flow didn't work. I don't have any control over threads in Android since ReactNative always uses the main thread. So if I end up locking the main thread then react-native cannot enter another method to unlock, thus I have deadlock. So not possible to enforce synchronous exchange of data with android.
I'm using Evernote sdk on iOS and it works great.
But sometimes I'm sending several CreateNote methods in a row and, as Evernote sends them asynchronously, if one of them falls in error I can't say which one ...
The CreateNote method returns a Note object when success but a NSError when failure. And this one doesn't tell about which query it was.
How can I know which note creations failed ?
Thanks
The Evernote SDK does send the requests asynchronously, but its always one request at a time. So if a request fails, its always the last request that you made.