1 - Configuration de l'application
On va créer une application iOS de type single view et la configurer comme suit :
Aller dans les propriétés du projet, puis dans l'onglet "Capabilities" et ajouter les fonctionnalités suivantes à l'aide du bouton "+" en haut à gauche dans Xcode :
- Push Notifications
- Background Modes et cocher la case "Remote Notifications"
On va avoir besoin d'un certificats pour signer les requêtes des notifications vers les serveurs de Notifications d'Apple.
1 .1 - Création d'une demande de certificat
Depuis l'Application "Trousseau d'accès" dans macOS, créer une demande de certificat à une autorité de certificat.
Menu > Trousseau d'accès > Assistant de certification > "Demander un certificat à une autorité de certificat"
Informations sur le certificat :
- Adresse e-mail de l'utilisateur : email compte développeur
- Nom commun : Laisser le nom par défaut
- La requête est : Enregistrée sur le disque
Sauvegarder le fichier et reserver le pour la suite.
1.2 - Création d'un certificat de développement pour l'application
Depuis l'administration des clés et certificats sur developer.apple.com :
Dans le volet gauche, cliquer sur "Certificates, IDs & Profiles".
1.2.1 - Création d'un App ID
Dans le volet de gauche, cliquer sur "App IDs", puis ajouter un ID en cliquant sur "+"
- Name : AppName
- App Services : [cocher]
App ID Suffix :
- [x] Explicit App ID
- Bundle ID : com.domain.appname
- [x] Push Notifications
1.2.2 - Configuration du service de notification pour l'App ID
Dans la liste des App IDs, sélectionner l'App ID et cliquer sur "Configure", puis dans "Development SSL Certificate" ou "Production SSL Certificate" cliquer sur "Create Certificate".
Si l'application dispose déjà d'un profil de provisionnement pour l'AppStore, le régénérer.
Convertir le certificat et clé PKCS#12 en PEM avec les étapes suivantes :
Environnement de développement
Étape 1 : Créer le certificat .pem à partir du certificat .p12
openssl x509 -in aps_development.cer -inform der -out PushChatCert.pem
Étape 2: Créer la clé .pem from depuis la clé .p12
openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12
Étape 3 : Facultatif (Si vous souhaiter supprimer la passphrase présente à l'étape 2)
openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem
Étape 4 : Maintenant nous devons fusionner la clé et le certificat dans un fichier .pem
cat PushChatCert.pem apns-dev-key-noenc.pem > apns-dev.pem (Si 3e étape effectuée)
cat PushChatCert.pem apns-dev-key.pem > apns-dev.pem (Si 3e étape non effectuée)
Étape 5 : Vérification de la validité du certificat et de la connectivité vers les serveurs APNS
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key apns-dev-key.pem (Si 3e étape effectuée)
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key apns-dev-key-noenc.pem (Si 3e étape non effectuée)
Environnement de production
Renouveler les mêmes étapes avec le certificat de production.
2 - Implémentation du Bundle Symfony d'envoie de notification Push
2.1 - Installation du bundle MobileNotifBundle
GitHub : https://github.com/Linkvalue-Interne/MobileNotifBundle
php composer.phar require linkvalue/mobile-notif-bundle
2.2 - Configuration du bundle
- Enregistrement du bundle dans l'AppKernel :
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new LinkValue\MobileNotifBundle\LinkValueMobileNotifBundle()
);
}
- Ajout du certificat dans le projet Symfony :
Ajouter le fichier apns-push-cert.pem à la racine du projet.
Ajout des paramètres dans config.yml
# Push Notification LinkValueMobileNotifBundle
#
# Development server: tls://gateway.sandbox.push.apple.com:2195
# Production server: tls://gateway.push.apple.com:2195
link_value_mobile_notif:
clients:
apns:
my_apns_application:
params:
ssl_pem_path: "%kernel.root_dir%/../apns-push-cert-dev.pem"
ssl_passphrase: "PassPhrase"
endpoint: "tls://gateway.sandbox.push.apple.com:2195"
3 - Implémentation des remotes notifications dans l'AppDelegate de l'application iOS
Dans le fichier AppDelegate.swift :
- Ajouter la directive : import UserNotifications
- Implémenter les méthodes suivantes :
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
print("deviceToken: \(tokenString)")
}
//Called if unable to register for APNS.
private func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
print(error)
}
// FIXED
internal func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
print(error)
}
Puis dans la méthode func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool), Implémenter la demande de device token :
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
application.registerForRemoteNotifications()
//FIXED
center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}
On va choisir le ViewController qui est le point d'entrée de notre application et lui implémenter la demande de permission pour les notifications :
Ajouter la directive : import UserNotifications
Implémenter la méthode :
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
4 - Récupération du device token et test de l'envoi de message
Lors de l'exécution de l'application, la console va afficher le device token que l'on va récupérer pour tester l'envoie de notifications avec Symfony :
php bin/console link_value_mobile_notif:apns:push "device_token" "Hello World!"
5 - Création d'une entité Device dans le modèle Symfony
On crée une entité qui modélise la device et va y stocker le deviceToken. On va ensuite ajouter une relation vers l'entité utilisateur : un utilisateur possède au moins une ou plusieurs devices.
id;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* @return string
*/
public function getDeviceToken()
{
return $this->deviceToken;
}
/**
* @param string $deviceToken
*/
public function setDeviceToken($deviceToken)
{
$this->deviceToken = $deviceToken;
}
/**
* @return User
*/
public function getUser()
{
return $this->user;
}
/**
* @param User $user
*/
public function setUser($user)
{
$this->user = $user;
}
}
Et la relation coté User :
devices = new ArrayCollection();
}
/**
* @param $device
* @return $this
*/
public function addDevice($device)
{
$this->devices[] = $device;
$device->setUser($this);
return $this;
}
/**
* @param $device
* @return $this
*/
public function removeDevice($device)
{
if ($this->devices->contains($device)) {
$this->devices->remove($device);
}
return $this;
}
/**
* @param $devices
*/
public function setDevices($devices)
{
$this->devices->clear();
foreach ($devices as $device) {
$this->addDevice($device);
}
}
/**
* @return mixed
*/
public function getDevices()
{
return $this->devices;
}
// ...
}
6 - Création d'un service Symfony pour l'envoie des notifications
Création du service :
namespace Greenmine\CommonBundle\Service;
use Doctrine\ORM\EntityManager;
use Greenmine\CommonBundle\Entity\User;
use LinkValue\MobileNotif\Model\ApnsMessage;
use Symfony\Component\DependencyInjection\Container;
class APNService
{
private $entityManager;
private $container;
/**
* APNService constructor.
* @param Container $container
*/
public function __construct(EntityManager $entityManager, Container $container)
{
$this->entityManager = $entityManager;
$this->container = $container;
}
/**
* Push notifications
* @param $payload
* @throws \Exception
*/
public function pushNotification(string $title, string $body)
{
$message = new ApnsMessage();
$message->setAlertTitle($title);
$message->setAlertBody($body);
// bingbong.aiff is the new default sound
$message->setSound('bingbong.aiff');
$message->setBadge(1);
$user = $this->entityManager->getRepository('GreenmineCommonBundle:User')->find(1);
$tokens = $this->getTokensFromUser($user);
$message->setTokens($tokens);
$this->container->get('link_value_mobile_notif.clients.apns.my_apns_application')->push($message);
}
/**
* Community Push notifications
* @param $payload
* @throws \Exception
*/
public function communityPushNotification(string $title, string $body, array $users)
{
$message = new ApnsMessage();
$message->setAlertTitle($title);
$message->setAlertBody($body);
// bingbong.aiff is the new default sound
$message->setSound('bingbong.aiff');
$message->setBadge(1);
$tokens = $this->getTokenFromCommunity();
$message->setTokens($tokens);
$this->container->get('link_value_mobile_notif.clients.apns.my_apns_application')->push($message);
}
/**
* @param User $user
* @return array
*/
private function getTokensFromUser(User $user)
{
$tokenArr = [];
$devices = $user->getDevices();
foreach ($devices as $device) {
$tokenArr[] = $device->getDeviceToken();
}
return $tokenArr;
}
/**
* @return array
*/
private function getTokenFromCommunity()
{
$users = $this->entityManager->getRepository('GreenmineCommonBundle:User')->findAll();
$tokenArr = [];
foreach ($users as $user) {
foreach ($user->getDevices() as $device) {
$tokenArr[] = $device->getDeviceToken();
}
}
return $tokenArr;
}
}
Déclaration du service :
Dans le fichier de déclaration des services du Bundle concerné, déclarer comme suit :
greenmine_common.apn_service:
class: Greenmine\CommonBundle\Service\APNService
arguments: ['@doctrine.orm.entity_manager', '@service_container']
7 - Génération d'un notification depuis un controller Symfony
Nous allons maintenant pourvoir générer une notification depuis un controller Symfony :
get('greenmine_common.apn_service')->pushNotification('Title', 'Hello World');
return $this->render('@TestTest/Test/index.html.twig');
}
}
8 - TestFlight et environnement de production
Lors de la distribution d'une app via TestFlight ou l'App Store, configurer les propriétés du Bundle Symfony LinkValueMobileNotifBundle dans app/config/config.yml :
link_value_mobile_notif:
clients:
apns:
my_apns_application:
params:
ssl_pem_path: "%kernel.root_dir%/../apns-push-cert-prod.pem"
ssl_passphrase: "PassPhrase"
endpoint: "tls://gateway.push.apple.com:2195"