Webcom iOS SDK
Webcom is the Orange Backend-as-a-Service / Serverless solution.
Webcom provides integrated functions of database and messages exchange platforms, authentication aggregation, data exposure and access control. This platform drastically reduce time and cost to implement and deploy mobile, web or IoT applications in production.
Table of Contents
1. Create a Webcom Application
- Sign up/in on Webcom console.
- Add an application with the identifier of your choice, e.g.
EmployeeDirectory
.
2. Setup your Swift Project
2.1. Integrate the Webcom Framework
2.1.1. Using Swift Package Manager
Swift Package Manager integration uses source code which will be compiled on your computer.
In your Package.swift
file, add the following dependency:
let package = Package(
...
dependencies: [
...
.package(name: "Webcom", url: "ssh://git@gitlab.tech.orange/webcom/sdk/ios.git", .upToNextMajor(from: "3.0.2")),
],
...
targets: [
...
.target(
...
dependencies: [
...
.product(name: "WebcomCore", package: "Webcom"),
],
...
),
...
]
)
2.1.2. Using CocoaPods
CocoaPods integration uses source code which will be compiled on your computer.
In your Podfile
:
source "https://cdn.cocoapods.org/"
source "ssh://git@gitlab.tech.orange/webcom/sdk/cocoapods.git"
platform :ios, "13.0"
use_frameworks!
target "MyApplication" do
pod "WebcomCore"
end
2.1.3. Using .xcframework
Files
Already compiled .xcframework
binary files can also be added manually in the project:
- Download the archive containing required
.xcframework
files: this .zip file (SHA-1). - Unzip the downloaded archive.
- Copy the
.xcframework
files you get in your Xcode project. - Add these
.xcframework
dependencies to your target.
2.2. Add Webcom Configuration in Info.plist
In your Info.plist
file, add a Webcom
property as follows:
Key | Type | Value | Description |
---|---|---|---|
▼ Webcom |
Dictionary |
root key for Webcom parameters | |
identifier |
String |
EmployeeDirectory |
identifier of the Webcom application |
More information about configuring Webcom applications is available on this page.
3. Start Coding
Webcom iOS SDK is written using Swift 5.11 and requires at least Xcode 16.2.
3.1. Import Webcom Module
import WebcomCore
3.2. Authenticate
The Authentication service makes it possible to reliably identify users of applications, in order to finely control their access to data.
Instantiate the Authentication service of the application:
let authenticationService = Webcom.defaultApplication.authenticationService
Authenticate using a guest account:
let guestMethod = AuthenticationMethodGuest()
authenticationService.authenticate(with: guestMethod)
Authenticate using an OAuth 2.0 provider:
let oAuth2Method = AuthenticationMethodOAuth2(provider: .orangeFrance, presentationContextProvider: self)
authenticationService.authenticate(with: oAuth2Method)
Authenticate using Orange Mobile Network as a second factor:
let mobileNetworkMethod = AuthenticationMethodOrangeMobileNetwork(mobileCountryCode: "ZZ")
authenticationService.authenticate(with: mobileNetworkMethod, context: .current)
You may be interested by the following types:
AuthenticationService
AuthenticationMethodOAuth2
AuthenticationMethodInternal
AuthenticationMethodOrangeMobileNetwork
AuthenticationMethodCustom
AuthenticationMethodGuest
3.3. Get References to Database Nodes
The Datasync service consists of a real-time database with a tree-like data model (equivalent to a JSON object).
Create a Datasync manager in your view controller:
let datasyncManager = Webcom.defaultApplication.datasyncService.createManager()
Get a reference to a node in the database tree:
let node = datasyncManager.node(for: "/path/to/node") // leading "/" is optional
or:
let node = datasyncManager / "path/to/node" // leading "/" may be added
Tip: Since both syntaxes returns an Optional
node, you may want to use the guard let node = ... else { ... }
construction in your code.
Get a reference to a descendant node:
let descendantNode = node?.relativeNode(for: "/some/descendant")
or:
let descendantNode = node / "some/descendant"
Get a reference to the parent node:
let parentNode = node?.parent
You may be interested by the following types:
3.4. Handle Data
Important: Datasync read and write operations are asynchronous.
In the following, we assume that the following type is defined:
struct Employee: Codable {
enum Sex: String, Codable {
case female
case male
}
let name: String
let yearOfBirth: Int
let sex: Sex
}
3.4.1. Write Data
Set the value of a node:
let god = Employee(name: "Steve", yearOfBirth: 1955, sex: .male)
let godNode = datasyncManager.node(for: "/employees/god")
godNode?.set(god)
or:
let god = Employee(name: "Steve", yearOfBirth: 1955, sex: .male)
let godNode = datasyncManager / "employees/god"
godNode? <- god
Push a new child to a node:
let ceo = Employee(name: "Tim", yearOfBirth: 1960, sex: .male)
let employeesNode = datasyncManager.node(for: "/employees")
employeesNode?.push(ceo)
or:
let ceo = Employee(name: "Tim", yearOfBirth: 1960, sex: .male)
let employeesNode = datasyncManager / "employees"
employeesNode? += ceo
3.4.2. Read Data
Get the value of a node once:
let godNode = datasyncManager / "employees/god"
godNode?.value { [weak self] result in
guard let god: Employee = result.value?.decoded() else {
return
}
self?.employeeNameLabel.text = god.name
}
You can simplify this code a bit using the .decodedValue(...)
helper:
let godNode = datasyncManager / "employees/god"
godNode?.decodedValue { [weak self] (result: WebcomResult<Employee>) in
self?.employeeNameLabel.text = result.value?.name
}
Subscribe to data to receive events:
let employeesNode = datasyncManager / "employees"
employeesNode?.subscribe(to: .childAddition) { [weak self] event in
guard let employee: Employee = event.value.decoded() else {
return
}
self?.employeeNameLabel.text = employee.name
}
You can simplify this code a bit using the .decodedValueSubscribe(...)
helper:
let employeesNode = datasyncManager / "employees"
employeesNode?.decodedValueSubscribe(to: .childAddition) { [weak self] (employee: Employee) in
self?.employeeNameLabel.text = employee.name
}
You may be interested by the following types:
3.5. Special Subscriptions
Subscribe to Datasync state changes:
datasyncManager.subscribeToStateChange { [weak self] state in
self?.onlineView.isHidden = !state.isAuthenticated || !state.isConnected
self?.offlineView.isHidden = !state.isAuthenticated || state.isConnected
self?.unauthenticatedView.isHidden = state.isAuthenticated
}
Subscribe to authenticated user changes:
datasyncManager.subscribeToCurrentUIDChange { [weak self] uid in
let state = Webcom.defaultApplication.datasyncService.state
self?.userNameLabel.text = state.authenticationDetails?.displayName
}
You may be interested by the following types:
3.6. Push Notifications
The typical use case is:
- The main application subscribes to push notifications when the value or the children of some node change.
- A
UNNotificationServiceExtension
is triggered when push notifications are sent to the application.
For this to work, the SDK needs to share information (WebcomConfiguration
values and secret keys when encryption is used) between the main application and the extension.
Thereby, they must have a common App Group
, configured in their respective Signing & Capabilities
configuration tabs.
The WebcomConfiguration
instance passed to the WebcomApplication must have its authStorage
field set to share
or key
(if key
field is used, you must also provide a value in authStorageKey
field). Those fields allows sharing of authentication state between app and extension.
Configure push notifications in the AppDelegate
:
import FirebaseCore
import FirebaseMessaging
import UIKit
import WebcomCore
// make the AppDelegate conforms to UNUserNotificationCenterDelegate and MessagingDelegate
final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
// complete the application(_:didFinishLaunchingWithOptions:) method:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Webcom.configure(groupName: "group.XXX.YYY.ZZZ") // same value as in didReceive(_:withContentHandler:) method of UNNotificationServiceExtension
FirebaseApp.configure()
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
application.registerForRemoteNotifications()
return true
}
// add the following method in the AppDelegate:
func messaging(_ messaging: Messaging, didReceiveRegistrationToken firebaseMessagingToken: String?) {
Webcom.deviceToken = firebaseMessagingToken
}
}
Subscribe to encrypted push notifications in a view controller:
let employeesNode = datasyncManager / "employees"
var descriptor = DataSubscriptionDescriptor(eventType: .child(addition: true, change: false, removal: false))
descriptor.encryptionAlgorithm = .A128CBCHS256
employeesNode?.dataNode.subscribeThroughNotifications(descriptor: descriptor)
Process received push notifications in a UNNotificationServiceExtension
:
import UserNotifications
import WebcomCore
final class NotificationService: UNNotificationServiceExtension {
private var manager: NotificationServiceManager?
private var notification: WebcomNotification?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
Webcom.configure(groupName: "group.XXX.YYY.ZZZ") // same value as in application(_:didFinishLaunchingWithOptions:) method of UIApplicationDelegate
guard let manager = NotificationServiceManager(request: request, contentHandler: contentHandler), let notification = WebcomNotification(request: request) else {
contentHandler(request.content)
return
}
self.manager = manager
self.notification = notification
manager.mutableContent.title = "Human Resources"
if let employee: Employee = notification.childrenUpdateFullDetails?.firstAdditionOrChange?.value.decoded() {
manager.mutableContent.body = "\(employee.name) has just been hired."
}
manager.wait() // synchronously, before each asynchronous call
DispatchQueue(label: "foo").async {
// asynchronous code, may use `notification`
manager.notify() // at the end of each asynchronous call
}
manager.complete(forces: false)
}
override func serviceExtensionTimeWillExpire() {
manager?.complete(forces: true)
}
}
You may be interested by the following types:
3.7. Extensions
Webcom iOS SDK is provided with extensions to facilitate its use with Combine framework.
3.7.1. Using with Combine
The classic API use a callback to process received events:
import WebcomCore
let datasyncManager = Webcom.defaultApplication.datasyncService.createManager()
datasyncManager.node(for: "employees")?.subscribe(to: .childAddition) { [weak self] event in
guard let employee: Employee = event.value.decoded() else {
return
}
self?.employeeNameLabel.text = employee.name
}
If you prefer, you can instead use publishers for the Combine framework:
import Combine
import WebcomCore
let datasyncManager = Webcom.defaultApplication.forCombine.datasyncService.createManager()
datasyncManager.node(for: "employees")?.eventPublisher(for: .childAddition).sink { _ in
// process revocation
} receiveValue: { [weak self] event in
guard let employee: Employee = event.value.decoded() else {
return
}
self?.employeeNameLabel.text = employee.name
}.store(in: datasyncManager)
You can simplify this code a bit using the .decodedValuePublisher(...)
helper:
import Combine
import WebcomCore
let datasyncManager = Webcom.defaultApplication.forCombine.datasyncService.createManager()
datasyncManager.node(for: "employees")?.decodedValuePublisher(for: .childAddition).sink { _ in
// process revocation
} receiveValue: { [weak self] (employee: Employee) in
self?.employeeNameLabel.text = employee.name
}.store(in: datasyncManager)
The WebcomCore
module also allows the use of Combine.Future
instead of completion callbacks:
import Combine
import WebcomCore
let datasyncManager = Webcom.defaultApplication.forCombine.datasyncService.createManager()
let newEmployee = Employee(name: "Katherine", yearOfBirth: 1_964, sex: .female)
datasyncManager.node(for: "employees")?.push(newEmployee).sink { completion in
if case let .failure(error) = completion {
print("error:", error)
}
} receiveValue: { childNode in
print("new employee id:", childNode.key)
}.store(in: datasyncManager)
Of course, you can mix both approaches in the same application, since they offer similar API.
You can switch from the classic approach to publishers using WebcomCombinable.forCombine
properties, and reciprocally from publishers to classic approach using WebcomWrapper.wrapped
properties.
3.8. Logs
To help you debug your application, you can pass it the Webcom.LogLevel
argument on launch (in the scheme properties)
with a value equal to fault
, error
, warning
, info
, debug
or verbose
to get more or less logs.
By default, these logs are sent to the logging system with a log object that uses com.orange.webcom
as subsystem and Webcom
as category.
You can see them in the Xcode console and in the Console application (don’t forget to include needed messages in the Action
menu).
You can set the Webcom.log
closure to customize logging and make it fit your needs.
4. More Information
- Study the source code of the Webcom Notes example applications (available for UIKit and SwiftUI) in the SDK Repository
- Contact support