
Partager:
Abdul est défenseur des développeurs chez Vonage. Il a travaillé dans le domaine des produits de consommation en tant qu'ingénieur iOS. Pendant son temps libre, il aime faire du vélo, écouter de la musique et conseiller ceux qui commencent leur parcours dans la technologie.
Construire une application audio avec SwiftUI et Vapor - Partie 1
Temps de lecture : 10 minutes
Note : Certains des outils ou méthodes décrits dans cet article peuvent ne plus être pris en charge ou ne plus être d'actualité. Pour un contenu mis à jour ou une assistance, consultez nos derniers articles ou contactez-nous sur le site Communauté Vonage Slack
Introduction
Les applications audio intégrées sont de plus en plus populaires. Clubhouse, Soapbox, Twitter Spaces et d'autres gagnent en popularité. Dans ce tutoriel, vous utiliserez l Conversation API avec le Client SDK pour créer votre propre application audio. Le tutoriel est divisé en deux parties : la première partie couvre le serveur dorsal, et la seconde partie couvre le système de gestion de l'information. la seconde partie couvrira l'application iOS.
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
Conditions préalables
Xcode 12 et Swift 5 ou supérieur
Vapor 4.0 installé sur votre machine
ngrok pour exposer votre machine locale à l'internet
Notre interface en ligne de commande, que vous pouvez installer avec
npm install @vonage/cli -g.
Création d'une application Vonage
Pour créer l'application, vous utiliserez l'interface de ligne de commande de Vonage. Si vous n'avez pas encore configuré l'interface de ligne de commande, exécutez vonage config:set --apiKey=API_KEY --apiSecret=API_SECRET dans votre terminal, en remplaçant la clé API et le secret par les valeurs trouvées sur la page des paramètres de votre compte Account, en remplaçant la clé API et le secret par les valeurs trouvées sur la page des paramètres de votre compte.
Créez d'abord un répertoire à l'aide de mkdir vonageapipuis naviguez dans le répertoire avec cd vonageapi. Ensuite, créez l'application Vonage avec vonage apps:create VaporConvAPI --rtc_event_url=https://example.com/. Cette commande enregistrera la clé privée de votre application dans le fichier vaporconvapi.key et produira l'identifiant de votre application. Vous aurez besoin de ces deux valeurs pour les étapes suivantes.
Créer un projet vapeur
Créez un projet Vapor en utilisant la commande new project vapor new VaporConvAPI dans votre terminal. Le terminal vous demandera plusieurs fois, d'abord si vous souhaitez utiliser Fluent. Répondez par l'affirmative et choisissez SQLite comme base de données. Ensuite, on vous demandera si vous souhaitez utiliser Leaf, dites non à cette question.
Fluent est un cadre de mappage objet-relationnel que nous utiliserons pour stocker les informations sur les utilisateurs dans la base de données. Une fois la commande terminée, changez de répertoire dans le dossier du projet en utilisant la commande cd VaporConvAPI.

Ensuite, copiez votre fichier vaporconvapi.key du répertoire racine de votre projet vers le dossier du projet Vapor. Sources/App/ du projet Vapor. Une fois cela fait, vous pouvez ouvrir le projet dans Xcode en utilisant vapor xcode. Lorsque Xcode s'ouvre, il commence à télécharger les dépendances sur lesquelles Vapor s'appuie, en utilisant le Swift Package Manager (SPM). Pour visualiser les dépendances, vous pouvez ouvrir le fichier Package.swift fichier.
Par défaut, Xcode exécute votre application à partir d'un répertoire local aléatoire. Puisque vous allez charger le fichier vaporconvapi.key vous devez définir un répertoire de travail personnalisé. Allez à Produit > Schéma > Modifier le schéma... et définissez le répertoire de travail comme étant le dossier racine de votre projet.

Authentification de l'utilisateur
Lors de l'utilisation de votre application, vous devrez authentifier les utilisateurs pour utiliser le Client SDK dans l'application iOS. La Conversation API a un concept de utilisateurs, un objet qui identifie un utilisateur unique de Vonage dans le contexte de votre application Vonage. Votre serveur dorsal gardera également la trace des utilisateurs, qui correspondront à l'utilisateur de Vonage. Pour faire la différence entre les deux, les utilisateurs sur votre serveur dorsal seront appelés utilisateurs de la base de données. Une fois que vous avez un utilisateur enregistré et sauvegardé, le serveur l'utilisera pour générer un jeton Web JSON (JWT) pour que le Client SDK puisse se connecter.
Créer le modèle utilisateur de la base de données
Dans le dossier Models supprimer le fichier Todo.swift et créez un nouveau fichier nommé User.swift en allant dans Fichier > Nouveau > Fichier (CMD + N). Ensuite, créer une nouvelle classe appelée User qui sera le modèle Fluent pour les utilisateurs de la base de données :
import Fluent
final class User: Model {
static let schema = "users"
@ID(custom: "id", generatedBy: .user) var id: String?
@Field(key: "name") var name: String
init() {}
init(id: String?, name: String) {
self.id = id
self.name = name
}
}La propriété schema sera le nom de la table dans la base de données ; les propriétés id et name sont les champs de la table. La propriété id est facultative et générée par l'utilisateur car l'identifiant de l'utilisateur Vonage sera utilisé ici mais n'est pas encore disponible.
Créer la migration des utilisateurs de la base de données
Pour créer la table dans la base de données, vous aurez besoin d'un fichier de migration. Les migrations définissent les modifications à apporter à la base de données, dans ce cas la création de la table Utilisateur. Dans le dossier Migrations supprimez le fichier CreateTodo.swift et créez un nouveau fichier nommé CreateUser.swift. Créez ensuite une nouvelle structure appelée CreateUser:
import Fluent
struct CreateUser: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema(User.schema)
.field("id", .string, .identifier(auto: false))
.field("name", .string, .required)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema(User.schema).delete()
}
}
Les deux fonctions, prepare et revertsont requises par le protocole Mirgration Le protocole de migration. prepare est appelée lors de l'exécution de la migration ; notez que le schéma et les champs correspondent à la classe User que vous venez de créer. La propriété id est définie comme un identifiant qui ne s'incrémente pas automatiquement, car l'identifiant de l'utilisateur de Vonage sera utilisé comme indiqué précédemment.
Vous pouvez maintenant ajouter les migrations à votre projet, ouvrir le fichier configure.swift et supprimez la ligne app.migrations.add(CreateTodo()) et ajoutez :
app.migrations.add(CreateUser())
try app.autoMigrate().wait()Cela exécutera la migration automatiquement pour vous lorsque votre serveur démarre et seulement lorsque cela est nécessaire. CreateUser automatiquement au démarrage de votre serveur et seulement lorsque c'est nécessaire.
Générer le JWT
La Conversation API et les SDK de Vonage Client utilisent les JWT pour l'authentification. Les JWT sont une méthode de représentation sécurisée des demandes entre deux parties. Pour en savoir plus sur les JWT, consultez le site JWT.io ou sur les demandes prises en charge par la Conversation API dans la Documentation de l'API Conversation. Ouvrez le fichier Package.swift et ajoutez une dépendance pour Swift-JWT dans le tableau de niveau supérieur dependencies de niveau supérieur ainsi que dans le tableau dependencies pour la cible :
...
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"),
.package(name: "SwiftJWT", url: "https://github.com/Kitura/Swift-JWT.git", from: "3.0.0")
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
.product(name: "Vapor", package: "vapor"),
.product(name: "SwiftJWT", package: "SwiftJWT")
],
swiftSettings: [
// Enable better optimizations when building in Release configuration. Despite the use of
// the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
// builds. See <https://github.com/swift-server/guides#building-for-production> for details.
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
...
Lorsque vous enregistrez le fichier, SPM télécharge SwiftJWT. Pour l'utiliser, créez un nouveau fichier dans le dossier Models appelé Auth.swift:
import Vapor
import SwiftJWT
struct Auth {
private let applicationId: String
lazy var adminJWT: String = {
return makeJwt()
}()
private let jwtSigner: JWTSigner = {
let privateKeyPath = URL(fileURLWithPath: "Sources/App/vaporconvapi.key")
let privateKey: Data = try! Data(contentsOf: privateKeyPath, options: .alwaysMapped)
return JWTSigner.rs256(privateKey: privateKey)
}()
init(applicationId: String) {
self.applicationId = applicationId
}
func makeJwt(sub: String? = nil, acl: JwtClaim.Paths? = nil) -> String {
let iat = Date().timeIntervalSince1970.rounded()
let exp = iat.advanced(by: 21600.0)
let claims = JwtClaim(applicationId: applicationId, iat: iat, jti: UUID(), exp: exp, sub: sub, acl: acl)
var jwt = JWT(claims: claims)
return try! jwt.sign(using: jwtSigner)
}
}
La propriété jwtSigner utilise la clé privée de votre application Vonage pour signer votre JWT. Elle est utilisée dans la fonction makeJwt qui prend un sujet optionnel (sub) et une liste de contrôle d'accès (ACL). Les JWT administratifs sont créés sans fournir de sub, ce qui, dans le cas de l'API Conversation, une réclamation sub serait le nom d'utilisateur d'un utilisateur de Vonage. Pour encoder correctement les revendications, SwiftJWT fournit un protocole Claim et nous créons une nouvelle structure conforme au protocole Claim dans le même fichier :
struct JwtClaim: Claims {
typealias Paths = [String: [String: [String: String]]]
let applicationId: String
let iat: TimeInterval
let jti: UUID
let exp: TimeInterval
let sub: String?
let acl: Paths?
enum CodingKeys: String, CodingKey {
case iat, jti, exp, sub, acl
case applicationId = "application_id"
}
static let defaultPaths: Paths = ["paths":
[
#"/*/users/**"#: [:],
#"/*/conversations/**"#: [:],
#"/*/sessions/**"#: [:],
#"/*/devices/**"#: [:],
#"/*/image/**"#: [:],
#"/*/media/**"#: [:],
#"/*/push/**"#: [:],
#"/*/knocking/**"#: [:],
#"/*/legs/**"#: [:]
]
]
}Les propriétés de la JwtClaim correspondent aux déclarations attendues par l Conversation API. Dans un environnement de production, vous devriez avoir un délai d'expiration court pour le JWT et ne fournir que les chemins d'accès ACL nécessaires.
Créez maintenant une instance de la Auth dans le fichier routes.swift en utilisant l'identifiant de votre application Vonage :
import Fluent
import Vapor
func routes(_ app: Application) throws {
var auth = Auth(applicationId: "APP_ID")
} Création d'un utilisateur Vonage
Ensuite, vous pouvez commencer à créer les points de terminaison pour l'application iOS. Le premier point de terminaison servira à l'authentification. Le serveur vérifiera d'abord si un utilisateur de la base de données correspondant au nom d'utilisateur entrant existe. Si c'est le cas, le serveur renverra un JWT. Si l'utilisateur de la base de données n'existe pas, il appellera l'API Conversation pour créer un utilisateur Vonage, enregistrera les détails dans la base de données, puis renverra un JWT.

Tout d'abord, créez un nouveau fichier appelé APIModels.swift dans le répertoire Models dans le répertoire C'est dans ce fichier que vous créerez toutes les structures nécessaires pour les points d'extrémité. La première structure que vous devez créer est la AuthBody struct :
import Vapor
struct AuthBody: Content {
let name: String
}C'est ce que le client iOS enverra au serveur. La structure est conforme au Content de Vapor. Un avantage important de l'utilisation de Vapor est que vous pouvez vous appuyer sur la sécurité des types du langage Swift. Vous pouvez modéliser les entrées et les sorties vers votre serveur en utilisant des structures qui se conforment au Codable comme Content qui est conforme à Codable.
Les structures suivantes modélisent l'entrée attendue de l'API Conversation, la réponse de l'API Conversation et la réponse que le serveur enverra à l'application iOS :
struct IDResponse: Content {
let id: String
}
struct UserAuth: Content {
struct Body: Content {
let name: String
let displayName: String
let imageURL: String
init(name: String) {
self.name = name
self.displayName = name
self.imageURL = "https://example.com/image.png"
}
enum CodingKeys: String, CodingKey {
case name
case displayName = "display_name"
case imageURL = "image_url"
}
}
struct Response: Content {
let name: String
let jwt: String
}
}Des valeurs par défaut ont été fournies pour imageURL et displayName pour les besoins de ce tutoriel. Les API de Vonage s'attendent à ce que les champs soient en casse serpent, les structures ont donc l'enum CodingKeys pour faire correspondre les noms de leurs propriétés à leur équivalent en casse serpent.
Maintenant que les modèles sont en place, vous pouvez ajouter la nouvelle route à la fonction routes dans le fichier routes.swift dans le fichier
func routes(_ app: Application) throws {
var auth = Auth(applicationId: "APP_ID")
app.post("auth") { req -> EventLoopFuture<UserAuth.Response> in
let authBody = try req.content.decode(AuthBody.self)
return User.query(on: req.db)
.filter(\.$name == authBody.name)
.first()
.flatMap { user -> EventLoopFuture<UserAuth.Response> in
if let user = user {
let userAuthResponse = UserAuth.Response(
name: user.name,
jwt: auth.makeJwt(sub: user.name, acl: JwtClaim.defaultPaths))
return req.eventLoop.makeSucceededFuture(userAuthResponse)
} else {
}
}
}
}
Cette fonction définit une nouvelle route au /auth du serveur, qui renvoie un futur avec un UserAuth.Response le type attendu par l'application iOS.
Le corps de la requête envoyée au serveur est décodé dans la variable authBody dans la variable Le corps est ensuite utilisé pour filtrer les utilisateurs de la base de données. Puisque vous recherchez un seul utilisateur (et que les noms d'utilisateur sont uniques), .first() est utilisé sur la réponse de la requête de la base de données, qui renvoie le type EventLoopFuture<User?>. Celui-ci est alors transformé en type attendu de EventLoopFuture<UserAuth.Response> avec la fermeture flatMap fermeture.

La deuxième partie du flux se poursuit dans la flatMap . Si l'option d'utilisation de la base de données est nulle, alors faites un appel à /v0.1/users de la Conversation API pour créer un utilisateur :
...
app.post("auth") { req -> EventLoopFuture<UserAuth.Response> in
...
.flatMap { user -> EventLoopFuture<UserAuth.Response> in
if let user = user {
...
} else {
return req.client.post(URI(scheme: "https", host: "api.nexmo.com", path: "v0.1/users")) { req in
req.headers.add(name: .authorization, value: "Bearer \(auth.adminJWT)")
try req.content.encode(UserAuth.Body(name: authBody.name), as: .json)
}.flatMap { response -> EventLoopFuture<UserAuth.Response> in
let responseBody = try! response.content.decode(IDResponse.self)
let user = User(id: responseBody.id, name: authBody.name)
let userAuthResponse = UserAuth.Response(
name: user.name,
jwt: auth.makeJwt(sub: user.name, acl: JwtClaim.defaultPaths))
return user.save(on: req.db).map { userAuthResponse }
}
}
}
...
Lorsqu'une demande est adressée à la Conversation API, un en-tête authorization est ajouté à la requête, ainsi qu'une UserAuth.Body codée en tant que corps de la demande. La réponse, l'identifiant Vonage de l'utilisateur créé, est à nouveau transformée en une structure de type flatMap en fonction du type attendu de EventLoopFuture<UserAuth.Response>.
Cette fois-ci, il y a une étape supplémentaire qui consiste à créer un utilisateur de base de données et à le sauvegarder. Dans un environnement de production, vous devriez utiliser un mot de passe pour sécuriser l'accès des utilisateurs à votre système, et vous pourriez aller plus loin et renvoyer un jeton d'authentification pour les futures demandes adressées à votre serveur.

L'itinéraire complet étant terminé, vous pouvez maintenant voir le flux de données de l'entrée au serveur et, grâce à une série de transformations enchaînées, vous obtenez la sortie souhaitée.
Conversations sur les listes
Une fois l'application iOS authentifiée, elle affichera une liste de salles audio que l'utilisateur peut rejoindre. Les salons audio sont l'équivalent de la fonction conversation de la Conversation API. Pour obtenir une liste des conversations disponibles pour votre application Vonage, vous pouvez appeler . /v0.2/conversations. Ajoutez les modèles nécessaires au fichier APIModels au fichier
...
struct Conversation: Content {
struct Response: Content {
let embedded: Embedded
enum CodingKeys: String, CodingKey {
case embedded = "_embedded"
}
struct Embedded: Content {
let data: Conversation.Response.Data
}
struct Data: Content {
let conversations: [Conv]
}
struct Conv: Content {
let id: String
let displayName: String
enum CodingKeys: String, CodingKey {
case id
case displayName = "display_name"
}
}
}
}
...Créez ensuite un nouvel itinéraire dans la fonction routes fonction :
...
app.get("rooms") { req -> EventLoopFuture<[Conversation.Response.Conv]> in
return req.client.get(URI(scheme: "https", host: "api.nexmo.com", path: "v0.2/conversations")) { req in
req.headers.add(name: .authorization, value: "Bearer \(auth.adminJWT)")
}.map { response -> [Conversation.Response.Conv] in
let responseBody = try! response.content.decode(Conversation.Response.self)
return responseBody.embedded.data.conversations
}
}
...
Comme pour l'appel précédent à l'API Conversation, un en-tête authorization est ajouté à la demande. La réponse est ensuite transformée en type de retour attendu pour l'application.
Créer une conversation
L'application iOS a besoin de créer de nouvelles conversations/salles. Pour créer une nouvelle conversation pour votre application Vonage, appelez . /v0.2/conversations. Ajoutez une Body à la structure Conversation dans le fichier APIModels dans le fichier
struct Conversation: Content {
...
struct Body: Content {
let name: String = UUID().uuidString
let displayName: String
let imageURL: String = "https://example.com/image.png"
let properties: [String: Int] = ["ttl": 300]
enum CodingKeys: String, CodingKey {
case name, properties
case displayName = "display_name"
case imageURL = "image_url"
}
}
}Les valeurs par défaut ont été fournies à nouveau pour les besoins du tutoriel. Les noms des conversations dans la Conversation API doivent être uniques, c'est pourquoi un UUID aléatoire est utilisé. Créez ensuite une nouvelle route dans la fonction routes dans la fonction
...
app.post("rooms") { req -> EventLoopFuture<IDResponse> in
let conversationBody = try req.content.decode(Conversation.Body.self)
return req.client.post(URI(scheme: "https", host: "api.nexmo.com", path: "v0.1/conversations")) { req in
req.headers.add(name: .authorization, value: "Bearer \(auth.adminJWT)")
try req.content.encode(conversationBody, as: .json)
}.map { response -> IDResponse in
let responseBody = try! response.content.decode(IDResponse.self)
return responseBody
}
}
...
Tester le serveur
Maintenant que vos routes sont définies, vous pouvez construire et exécuter (CMD + R). Une fois terminé, votre serveur tournera localement sur le port 8080. Pour l'exposer à l'internet, vous pouvez utiliser ngrok.
Dans votre terminal, lancez ngrok http 8080. Ngrok va générer une URL publique qui redirige les appels vers votre machine locale.

L'URL ngrok est ce que l'application iOS utilisera pour communiquer avec le serveur. Vous pouvez tester les points de terminaison que vous avez créés à l'aide d'un outil API tel que Postman, Rested ou Hoppscotch:
POST
/auth:

POST
/rooms:

GET
/rooms:

Quelle est la prochaine étape ?
La deuxième partie de ce tutoriel permettra de construire une application iOS audio drop-in avec SwiftUI et le Client SDK, qui utilise le serveur que vous venez de créer.

Vous pouvez trouver le projet terminé sur GitHub. Pour en savoir plus sur la Conversation API, rendez-vous sur developer.vonage.comet sur Vapor sur vapor.codes.
Partager:
Abdul est défenseur des développeurs chez Vonage. Il a travaillé dans le domaine des produits de consommation en tant qu'ingénieur iOS. Pendant son temps libre, il aime faire du vélo, écouter de la musique et conseiller ceux qui commencent leur parcours dans la technologie.
