Using iOS Notifications, Cryptography and iCloud to build your own Chat App IX
I should start this article with a disclaimer, it based on iOS 13, Swift 5 and Xcode 11.x. If you reading this and those numbers look dated, be forewarned.
I should also warn you that notifications and iCloud code, involve Apple’s infrastructure which means you will need an Apple Developers account to use them.
Obviously this is part IX, you need to go back to the beginning for it to make any sense, indeed you need to look at the notifications series before do that else your struggle. Most of the parts are just 4 minutes long including code.
So where are we. Here is three screenshots. The first shows the data entry screen you get when you install the app.

The second seen here is the data entry screen in which you choose who is the owner of the device your using. Darth or Luke in our example.

The third is the screen that comes up after you choose the owner, showing who else has registered devices you can send messages too.

You get the picture. But wait we’re not done yet, developing an app as I hope your getting a feeling for is a long process. Now when we talked about our confirmation protocol for setting Luke in our case to talk to Darth, we mentioned using a shared secret. A shared secret we didn’t implement, instead we just used a simple alert. We should perhaps upgrade it, use a shared secret, so that when you Luke gets a request from Darth to connect, he can be sure it is his father and not some imposter.
We’ll go slowly. You’ll need to make changes to four files. Lets start with AppDelegate. Now when I request to talk I lookup the details of the individual and send them the request. When they get the request they send me back a confirmation and we’re in business.
We need to change that protocol slightly. When party B, gets a request from party A he needs to respond with a challenge, not just a confirmation. We saved challenges you recall in the database. But wait we saved them in the privateDB not the public one [obviously].
We can get the appDelegate to lookup our challenge/shared secret in the private database when it confirms it can talk, but there is an easier way. We can simply save our secret when we save a user. When we select a user as an owner and save their details, we can also save the secret to device. This codebyte is from ContentView.swift, it shows us doing just that.
.onTapGesture {
// *** 1ST ***
self.sender = self.users[self.selected]
UserDefaults.standard.set(self.sender, forKey: "name")
let success = rsa.generateKeyPair(keySize: 2048, privateTag: "ch.cqd.noob", publicTag: "ch.cqd.noob")
if success {
let privateK = rsa.getPrivateKey()
let publicK = rsa.getPublicKey()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let token = appDelegate.returnToken()
var timestamp = UInt64(floor(Date().timeIntervalSince1970 * 1000))
let random = String(timestamp, radix: 16)
UserDefaults.standard.set(random, forKey: "secret")
cloud.searchAndUpdate(name: self.sender, publicK: publicK!, privateK: privateK!, token: token, shared: random)
}
messagePublisher.send(self.sender + " Logged In")
self.disableUpperWheel = true
}
We send the challenge via a post, so we need to change our RemoteNotifications.swift file.
func postNotification(token:String, message:String, type: String, request: String, device:String) {
var jsonObject:[String:Any]?
if type == "background" {
let secret = UserDefaults.standard.string(forKey: "secret")
jsonObject = ["aps":["content-available":1],"request":request,"user":message,"device":device, "secret":secret]
} else {
jsonObject = ["aps":["sound":"bingbong.aiff","badge":1,"alert":["title":"Noob","body":message]]]
}
The background message here is send initially as a request and then back as a grant. It is the second time it is sent, as a grant that we use the secret key that is read here.
Imagine party A want to talk to party B and requests. Party A agrees and responds, it is in party B response we embed the secret. Now in appDelegate we running this piece of code.
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
debugPrint("Received: \(userInfo)")
let request = userInfo["request"] as? String
let user = userInfo["user"] as? String
let device = userInfo["device"] as? String
let secret = userInfo["secret"] as? String
if request == "request" {
DispatchQueue.main.async {
print("token ",device)
popPublisher.send((device!,user!))
}
}
if request == "grant" {
DispatchQueue.main.async {
print("token ",token)
enableMessaging.send((device!,secret!))
}
}
completionHandler(.newData)
}
On the when it initially runs it it is a silent request from party A to party B. When party B replies he does so including his shared secret in the message.
.onReceive(enableMessaging, perform: { (data, secret) in
print("Granted")
self.confirm = data
self.disableMessaging = false
self.showingGrant = true
self.code = secret
// cloud.saveAuthRequest2PrivateDB(name: self.sendingTo, token: self.confirm!)
})
.alert(isPresented:$showingGrant) {
Alert(title: Text(self.code), message: Text("What is on your mind?"), dismissButton: .default(Text("Clear")))
}
When I get the reply, it shows me the secret messages in the title. I made one other small change to the cloud.swift file. I edited a line in the authRequest so you can delete the saved tokens to repeatly test this.
func authRequest(auth:String, name: String, device:String) {
let predicate = NSPredicate(format: "name = %@", name)
let query = CKQuery(recordType: "directory", predicate: predicate)
privateDB.perform(query,inZoneWith: CKRecordZone.default().zoneID) { [weak self] results, error in
guard let _ = self else { return }
if let error = error {
DispatchQueue.main.async { print("error",error) }
return
}
guard let results = results else { return }
for result in results {
print("results ",result)
let token = result.object(forKey: "device") as? String
if token == nil || token == "" {
self!.authRequest2(auth: auth, name: name, device: device)
} else {
DispatchQueue.main.async { shortProtocol.send(token!) }
}
}
if results.count == 0 {
print("no name ",name)
self!.authRequest2(auth: auth, name: name, device: device)
}
}
}
Make the changes. Delete the test apps from your devices, and clear the databases and try and run it. Add Luke to the database on device A, and Darth to the database on device B. Identify Luke as the owner of device A and Darth as the owner of device B. Ask to send a message from Luke to Darth, you shoud see this.

On Darth device and …

On Luke. The ID you see here as the title is Darth’s shared secret. Of course the plan isn’t to display the secret on the screen. It is to present an alert that asks you type it in… read on.