Sinch VoIP ManagedPush Notifications Not Working (Swift) - swift

I have been trying to implement VoIP ManagedPush notifications however they aren't working. The following are the steps I have taken to implement ManagedPush:
1) Created a VoIP Services Certificate in Apple Developer. Uploaded a .p12 file of the certificate to the Sinch Dashboard.
2) Instantiated the sinchClient and the push client var sinchClient: SINClient!; var push: SINManagedPush!
3) Initialized the push client and sinch client in didFinishLaunchingWithOptions function:
// Register for Sinch Notifications (for calling)
self.push = Sinch.managedPushWithAPSEnvironment(.Development)
self.push.delegate = self
self.push.setDesiredPushTypeAutomatically()
// Sinch
NSNotificationCenter.defaultCenter().addObserverForName("UserDidLoginNotification", object: nil, queue: nil, usingBlock: {(note: NSNotification) -> Void in
self.initializeSinch(note.userInfo!["userId"] as! String)
})
4) Initialized sinchClient. called when a user is logged in via facebook.
func initializeSinch(loginId: String){
sinchClient = Sinch.clientWithApplicationKey("key", applicationSecret: "secret", environmentHost: "sandbox.sinch.com", userId: loginId)
sinchClient.callClient().delegate = self
sinchClient.setSupportCalling(true)
sinchClient.enableManagedPushNotifications()
sinchClient.start()
sinchClient.startListeningOnActiveConnection()
self.push.registerUserNotificationSettings()
}
5) In function didRegisterForRemoteNotificationsWithDeviceToken I placed the following line of code:
self.push.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
6) In function didRecieveRemoteNotification I placed the following line of code:
self.push.application(application, didReceiveRemoteNotification: userInfo)
7) Created the delegate function for SINManagedPushDelegate:
extension AppDelegate: SINManagedPushDelegate{
func managedPush(managedPush: SINManagedPush!, didReceiveIncomingPushWithPayload payload: [NSObject : AnyObject]!, forType pushType: String!) {
self.handleRemoteNotification(payload)
print("HERE GOT A REMOTE NOTIFICATIOn")
}
func handleRemoteNotification(userInfo: [NSObject : AnyObject]) {
if (sinchClient == nil) {
let userManager = UserManager()
initializeSinch(String(userManager.user.facebookId))
}
self.sinchClient.relayRemotePushNotification(userInfo)
}
}
So my question really comes down to what am I doing wrong that the ManagedPush is not working. I have read SinchCallingPush and converted and tried to follow the code almost exactly yet it doesn't work. I think that maybe its something to do with not implementing PushKit. If this is the case could someone please point out how to correctly import it and where to use it?
I have also looked at a similar question at Instant Message Push Notification Using Sinch not coming on iOS

Something happened and it started working. Now I am getting an error that says SIN_INCOMING_CALL. I am sure I can google and fix this up.

Related

SwiftUI - KV Observe completion from Combine does not get triggered

I am trying to build a VOIP app using lib called VailerSIPLib. As the library was built using Obj-C and heavily using NotificationCenter to to publish the changes the active states all over the place.
I currently at the CallView part of the project, I can manage to start, end, reject calls. However, I need to implement connectionStatus in the view which will give information about the call like duration, "connecting..", "disconnected", "ringing" etc.
The below code is all in CallViewModel: ObservableObject;
Variables:
var activeCall: VSLCall!
#Published var connectionStatus: String = ""
Initializer:
override init(){
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.listen(_:)), name: Notification.Name.VSLCallStateChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateInboundCallAccepted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateOutboundCallStarted, object: nil)
}
Methods:
func setCall(_ call: VSLCall) {
self.activeCall = call
self.activeCall.observe(\.callStateText) { (asd, change) in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
#objc func listen(_ notification: Notification) {
if let _ = self.activeCall {
print(self.activeCall.callStateText)
}
}
#objc func buildCallView(_ notification: Notification) {
print("inbound call")
self.isOnCall = true
}
Problem:
It prints out every thing except the completionBlock in setCall(_:). listen(_:) function validates that the state of the activeCall is changing and I would want to use that directly, however it does not work correct all the time. It should be triggered when the call is answered with callState value of .confirmed but sometime it does. This how I will know that it is time start the timer.
Other point is, in the example project of the VialerSIPLib they used self.activeCall.addObserver(_:) and it works fine. The problem for that is it throws a runtime error at the method something like didObservedValueChange(_:) and logs An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)' is unused
which I could not find anything related to it.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)'
This is telling you what the problem is. The observe(_:options:changeHandler:) method is only incompletely documented. It returns an object of type NSKeyValueObservation which represents your registration as a key-value observer. You need to save this object, because when the NSKeyValueObservation is destroyed, it unregisters you. So you need to add a property to CallViewModel to store it:
class CallViewModel: ObservableObject {
private var callStateTextObservation: NSKeyValueObservation?
...
And then you need to store the observation:
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextObservation = activeCall.observe(\.callStateText) { _, change in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
You could choose to use the Combine API for KVO instead, although it is even less documented than the Foundation API. You get a Publisher whose output is each new value of the observed property. It works like this:
class CallViewModel: ObservableObject {
private var callStateTextTicket: AnyCancellable?
...
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextTicket = self.activeCall.publisher(for: \.callStateText, options: [])
.sink { print("callId: \(call.callId), callStateText: \($0)") }
}
There's no specific reason to use the Combine API in your sample code, but in general a Publisher is more flexible than an NSKeyValueObservation because Combine provides so many ways to operate on Publishers.
Your error with addObserver(_:forKeyPath:options:context:) happens because that is a much older API. It was added to NSObject long before Swift was invented. In fact, it was added before Objective-C even had blocks (closures). When you use that method, all notifications are sent to the observeValue(forKeyPath:of:change:context:) method of the observer. If you don't implement the observeValue method, the default implementation in NSObject receives the notification and raises an exception.

OAuth2 Code Parsing Problem in Xcode 11 and Swift 5

I'm working on a school project where I need to implement OAuth2 to get access to our student profiles and display them. The app has two views - a search bar and detailed user info on the next page. I'm fairly new to this, so any advice will be greatly appreciated.
I was mostly following this tutorial https://grokswift.com/alamofire-OAuth2/ but a lot of stuff is deprecated. I can easily get all data using postman and user credentials but when I implement OAuth2 I can't parse the code I receive. So all my functions are in ContentView (pretty sure it's not the right way, but I couldn't make it work otherwise for now). I have a search button that looks like this:
Button(
action: {self.handleAPI()},
label: {
Text("Search")
}
And a handleAPI function that calls other functions, and func startOAuth2Login() among them:
if let authURL:NSURL = NSURL(string: authPath)
{
UIApplication.shared.open(authURL as URL, options: [:], completionHandler: nil)
print("here1")
}
Everything works as expected, redirects me to the login screen, where I enter my credentials, press "Authorize" and successfully return to the app. The print statement is also visible. However, I can't find the link I receive to parse the auth code. I know it should be in AppDelegate and this is what I have for now but it's not being called.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
ContentView.sharedInstance.processOAuthStep1Response(url: url as NSURL)
print("url: \(url)")
return true
}
processOAuthStep1Response(url) is just another function in my handleAPI main function. I also have this line in my ContentView : static let sharedInstance = ContentView().
So the question is how to call my function that will parse the code? How can I even see this code? XCode doesn't give any errors and everything works, just after "here1" nothing else is called. Thank you in advance!

Customizing sandboxed NSSavePanel alert

I am validating urls from NSSavePanel using the delegate's panel(_:validate) method, throwing error in case of invalid url. In such case the NSSavePanel presents an alert, which I want to customize (meaning present some human readable description) depending on the error thrown, keeping the save panel window open and then letting you choose another path.
LocalizedError works just fine when not using App Sandbox but in a sandboxed app the getter for error description is never called and the message in the alert is generic "Operation couldn't be completed. (#yourErrorType)", which I guess is somehow caused by the different inheritance chain for sandboxed NSSavePanels.
I am struggling figuring a way around this - is it possible to customize the alert somehow while still keeping the app sandboxed?
Addendum: Permissions for User Selected File => r/w. Running the following example produces different alerts with/without sandbox.
func runSavePanel()
{
let panel = NSSavePanel()
let delegate = SavePanelDelegate()
panel.delegate = delegate
_ = panel.runModal()
}
class SavePanelDelegate: NSObject, NSOpenSavePanelDelegate {
func panel(_ sender: Any, validate url: URL) throws {
throw CustomError.whatever
}
}
enum CustomError: LocalizedError {
case whatever
var errorDescription: String? {
get {
return "my description"
}
}
}
So, after a bit of further digging I can tell the solution of the riddle finally although I can only guess the reasons why it was made tricky by Apple. Apparently NSError exclusively needs to be used. The customization has to be done in userInfo, say
let userInfo = [NSLocalizedDescriptionKey: "yourLocalizedDescription", NSLocalizedRecoverySuggestionErrorKey: "yourSuggestion"]
throw NSError(domain: "whatever", code: 0, userInfo: userInfo)
etc. By the way subclassing NSError doesn't work, the Sandbox will just happily ignore you :)

Combining WatchConnectivity and Complications

I want my complication to get data from the iPhone via Watch Connectivity. I am using sendMessage Instant Messaging technology.
I don't want my iPhone app to be open when I try to get data, so this needs to work in the background.
In my ViewController on my iPhone:
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
var session: WCSession!
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
self.session = WCSession.defaultSession()
self.session.delegate = self
self.session.activateSession()
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
if message.count != 1 { return }
if message["request"] != nil {
replyHandler(["response" : "data"])
}
}
And in my ComplicationController
var session: WCSession!
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
if complication.family != .ModularSmall {
handler(nil)
}
if WCSession.isSupported() {
self.session = WCSession.defaultSession()
self.session.delegate = self
self.session.activateSession()
}
var respondedString = "not"
session.sendMessage(["request" : ""], replyHandler: {
(resp) -> Void in
respondedString = resp["response"]
}, errorHandler: nil)
let circularTemplate = CLKComplicationTemplateModularSmallSimpleText()
circularTemplate.textProvider = CLKSimpleTextProvider(text: respondedString)
let timelineEntry = CLKComplicationTimelineEntry(date: NSDate(), complicationTemplate: circularTemplate)
handler(timelineEntry)
}
The only thing I can see on my Watch is "not". Why doesn't the complication show the received data?
The main issue is that you're trying to make an asynchronous call within your complication controller.
The code following your sendMessage: call will be executed before your reply handler has even gotten a response. This is why your complication shows "not" as the template's text has been set, before you have received a reply.
Sometime later, after getCurrentTimelineEntryForComplication has returned, sendMessage will receive a response and call the reply hander, which will merely set respondedString, then exit that block.
What you should avoid doing:
You should consider Apple's recommendations, and not try to fetch any data within the complication controller.
The job of your data source class is to provide ClockKit with any requested data as quickly as possible. The implementations of your data source methods should be minimal. Do not use your data source methods to fetch data from the network, compute values, or do anything that might delay the delivery of that data. If you need to fetch or compute the data for your complication, do it in your iOS app or in other parts of your WatchKit extension, and cache the data in a place where your complication data source can access it. The only thing your data source methods should do is take the cached data and put it into the format that ClockKit requires.
Also, any activity you perform within your data source will needlessly use up the daily execution time budget that is allotted to your complication.
How can you provide data to your complication?
Apple provides a Watch Connectivity transferCurrentComplicationUserInfo method which will immediately transfer (a dictionary of) complication info from the phone to the watch.
When your iOS app receives updated data intended for your complication, it can use the Watch Connectivity framework to update your complication right away. The transferCurrentComplicationUserInfo: method of WCSession sends a high priority message to your WatchKit extension, waking it up as needed to deliver the data. Upon receiving the data, extend or reload your timeline as needed to force ClockKit to request the new data from your data source.
On the watch side, you have your WCSessionDelegate handle didReceiveUserInfo and use the data you received to update your complication:
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let ... { // Retrieve values from dictionary
// Update complication
let complicationServer = CLKComplicationServer.sharedInstance()
guard let activeComplications = complicationServer.activeComplications else { // watchOS 2.2
return
}
for complication in activeComplications {
complicationServer.reloadTimelineForComplication(complication)
}
}
}
Apple engineers generally recommend setting up a data manager to hold the data. In your complication controller, you would retrieve the latest information from the data manager to use for your timeline.
There are several existing projects on GitHub which use this approach.
If you still prefer to request data from the watch side:
You'd want to move your WCSession code out of the complication controller, into the watch extension, and activate it as part of the WKExtension init.
The key is to have the reply handler manually update the complication once the data is received.
When your session delegate's reply handler is called, you can use the update complication code I supplied earlier to reload your complication's timeline.
If you use scheduled complication updates to trigger this, the downside to that particular approach is that you'll be performing two updates. The first update would initiate the request for data, but not have any new data to use. The second (manual) update happens after the data is received, and this is when the new data would appear on the timeline.
This is why the approach of supplying data in the background from the phone works better, as it only requires one update.

How can I trigger async requests out of view controller

I am building an iOS app and I just finished my login/register part ( requesting a sails.js rest Api)
At the moment I have 2 view controllers with duplicate code because i issue the rest calls on register/login button event listener of each class and there is a lot of similar code I can refactor.
What I want to do is to create a singleton called ApiManager that will contain all the calls that I need. (And the futur ones )
The problem is that with async calls I can't create a function func login(username,password) that will return data so I can store them and prepareforsegue.
What is the simple/proper way to achieve that correctly? Which means call ApiManager.myFunction and using the result wherever it's needed ( filling a tableview for data, initiating a segue for login or register with succes ) and to make this function reusable in another view controller even if it is for another usage. I am using swift.
EDIT : Here is how i did it so i hope it will help you
The function executing the rest call :
func login(#username: String, password: String, resultCallback: (finalresult: UserModel!,finalerror:String!) -> Void) {
Alamofire.request(.POST, AppConfiguration.ApiConfiguration.apiDomain+"/login", parameters: ["username": username,"password": password], encoding: .JSON)
.responseJSON { request, response, data, error in
if let anError = error
{
resultCallback(finalresult: nil,finalerror:anError.localizedDescription)
}else if(response!.statusCode == 200){
var user:UserModel = self.unserializeAuth(data!)//just processing the json using SwiftyJSON to get a easy to use object.
resultCallback(finalresult: user,finalerror:nil)
}else{
resultCallback(finalresult: nil,finalerror:"Username/Password incorrect!")
}
}.responseString{ (request, response, stringResponse, error) in
// print response as string for debugging, testing, etc.
println(stringResponse)
}
}
And this is how i call this function from my ViewController :
#IBAction func onLoginTapped(sender: AnyObject) {//When my user tap the login button
let username = loginInput.text;//taking the content of inputs
let password = passwordInput.text;
ApiManager.sharedInstance.login(username:username,password:password){
[unowned self] finalresult,finalerror in
if(finalresult !== nil){//if result is not null login is successful and we can now store the user in the singleton
ApiManager.sharedInstance.current_user=finalresult
self.performSegueWithIdentifier("showAfterLogin", sender: nil)//enter the actual app and leave the login process
}else{
self.displayAlert("Error!", message: finalerror)//it is basically launching a popup to the user telling him why it didnt work
}
}
}
Almost all of my apps end up with a Server class which is the only one that knows how to communicate with the server. It makes the call, parses the result into a Swift struct and returns it. Most of my servers return json so I use SwiftyJSON, but you can do whatever you want.
The point is, that since this is the only class that knows about server communication, if I need to change the library being used to do the communication (AFNetworking 1 vs 2 vs Parse, vs whatever) this is the only class I need to touch.
class Server {
static let instance = Server()
func loginWithUsername(username: String, password: String, resultCallback: (result: Either<User, NSError>) -> Void) {
// if login is successful call
resultCallback(result: .Left(self.user!))
// otherwise call
resultCallback(result: .Right(error))
}
}
An example of use:
let server = Server.instance
SVProgressHUD.showWithStatus("Loggin In...")
server.loginWithUsername(username, password: password) { [unowned self] result in
SVProgressHUD.dismiss()
switch result {
case .Left(let user):
self.presentUserType(user.userType)
case .Right(let error):
self.warnUserWithMessage("An error occured. \(error.localizedDescription)")
}
}
If the username/password are needed for all subsequent calls, then the server object will maintain a copy of them. If the login returns a token, then the server keeps a copy of that.
QED.
I usually have utility functions in a base class shared by my view controllers and use NSNotificationCenter for reacting to the results of the requests. It can also easily be achieved through delegation (protocol & delegate.
It is mostly about perception but I find it is easier to visualize that you can, for example, start an action on one controller and react on another because the call took this long and you were not blocking navigation in your app.