Javi Moreno Apuntes Fichas de Lectura Archivo Sobre mi
Blog Logo

Javi


10 minutos de lectura

  • Okay, Houston, we've had a problem here.
  • This is Houston. Say again, please!
  • Uh, Houston, we've had a problem.

CAPCOM Apollo XIII, Jack Swigert - Jack R. Lousma - James A. Lovell.

Aprovechando que Rafa Aguilar (aka @rais38) ha publicado en Objective-C.es un par de entradas sobre las notificaciones Push en iOS he decidido aparcar un par de cosillas que me tenían bastante atareado y continuar con el análisis de Helios.io que había prometido.

Yo no me voy a detener a explicar en que consisten las notificaciones Push y que tipos podemos encontrar en iOS ya que Rafa lo ha explicado estupendamente si no que voy a contar como se desarrollaría el ejemplo de la segunda entrada con Ruby en lugar de con PHP. Para ello, usaré Houston, una gema desarrollada por @mattt. La versión actual de Helios (que tiene unas pocas horas) ya incluye Houston de serie así que Helios y Orbiter nos proporcionan un backend completo para el envío de notificaciones Push.

Os dejo los enlaces a los artículos de Rafa por si los queréis consultar antes de empezar:

Envío de notificaciones en iOS (Parte 1)

Envío de notificaciones en iOS (Parte 2)

Helios y Orbiter. Preparando el CAPCOM.

Si habéis seguido las indicaciones del artículo de Objective-C.es, tendréis una aplicación instalada en vuestro dispositivo que vuelca al log de Xcode el token que le devuelven los servidores de Apple para las notificaciones Push. Helios y Orbiter nos proporcionan un sistema para almacenar esos tokens en un servidor, esto nos será útil cuando queramos enviar notificaciones desde nuestro servidor a los dispositivos que tengamos vinculados a nuestra aplicación. No será necesario pasar siempre el token al servicio si no que podría bastar con usar el alias.

Para ello, nos descargamos Orbiter de GitHub y lo incorporamos a nuestro proyecto o bien usamos CocoaPods, lo que hagamos habitualmente y modificamos el siguiente método del AppDelegate:

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    NSURL *serverURL = [NSURL URLWithString:@"http://192.168.1.105:3000"];
    NSURLCredential *serverCredential = [NSURLCredential credentialWithUser:@"YourUsername" password:@"YourPassword" persistence:NSURLCredentialPersistencePermanent];
    Orbiter *orbiter = [[Orbiter alloc] initWithBaseURL:serverURL credential:serverCredential];
    [orbiter registerDeviceToken:deviceToken withAlias:@"ApoloXIII" success:^(id responseObject) {
        NSLog(@"Registration Success: %@", responseObject);
    } failure:^(NSError *error) {
        NSLog(@"Registration Error: %@", error);
    }];
}

El servidor que voy a utilizar para almacenar los tokens y enviar las notificaciones es el mismo de la entrada anterior sobre almacenamiento de datos y sincronización. Si recordáis, una de las cosas que hicimos fue poner seguridad por lo que necesitamos pasarle un usuario y una contraseña. Orbiter usa NSURLCredential para estos fines.

Si hemos hecho todo correctamente, al arrancar la aplicación en el dispositivo obtendremos el siguiente resultado:

2013-05-01 11:07:01.903 CAPCOM[24399:907] Registration Success: {
device =     {
    alias = ApoloXIII;
    badge = 0;
    id = 1;
    "ip_address" = "<null>";
    language = es;
    lat = "<null>";
    lng = "<null>";
    locale = "es_ES";
    tags =         (
        "iPhone OS 6.1.3",
        "v1.0",
        iPhone
    );
    timezone = "Europe/Madrid";
    token = 1F0E7706D1A343BF17615051DB944743F18156C52EFF0F8DD43DDA23F156862A;
    tsv = "'1f0e7706d1a343bf17615051db944743f18156c52eff0f8dd43dda23f156862a':1 'apoloxiii':2 'es':3,4 'europe/madrid':5";
    };
}

Y en el panel de administración de Helios veremos lo siguiente:

Ahora tenemos que preparar nuestro servidor para que realice el envío de notificaciones Push. Como ya hemos comentado al principio, esta funcionalidad no la traía Helios en su primera versión y nos obligaba a nosotros a incluir y configurar esta gema. Hace pocas horas han actualizado el repositorio de Helios incluyendo la funcionalidad de envío de mensajes mediante Houston. Como el merge en GitHub es tan reciente, la gema que hay en RubyGems.org todavía no está actualizada así que para poder usarla en nuestro proyecto Rails he tenido que indicar en el gemfile que tome el fuente de GitHub. Al hacerlo de esta forma, las dependencias no son visibles por el Bundler por lo que, para evitar errores, he indicado también que necesitamos la gema Houston.

gem 'helios', :git => 'git://github.com/helios-framework/helios.git'
gem 'houston'

Ahora tendremos que guardar el certificado para realizar APS en alguna carpeta de nuestro proyecto y decirle a Helios donde está. Los pasos que hay que seguir para trabajar con los certificados están en el post de Rafa, lo único que haremos diferente es el paso de exportación del certificado, al que le daremos un nombre más comodo para nuestra configuración:

$ openssl pkcs12 -in CAPCOMAPNDEV.p12 -out apple_push_notification.pem -nodes -clcerts

El fichero .pem resultante lo dejaremos en la carpeta config de nuestro proyecto (junto al xcdatamodel) y modificaremos la configuración de Helios en nuestro application.rb de la siguiente manera:

# Using framework Helios as a middleware for our app
config.middleware.use Helios::Application do
  service :data, model: './config/DealerErgoGo.xcdatamodel'
  service :push_notification, apn_certificate: '.config/apple_push_notification.pem', apn_environment: 'development'
  service :in_app_purchase
  service :passbook
end    

Para comprobar que todo está bien configurado, ejecutamos la siguiente instrucción desde el terminal (recordad que nuestro servidor tiene configurado usuario y contraseña):

curl -X POST --user "username:password" -d 'payload={"aps": {"alert":"Okay, stand by Thirteen, we are looking at it.","badge":"13","sound":"default"}}' http://localhost:3000/message 

Listo, parece que ya tenemos comunicación con Houston... o por lo menos recibimos sus mensajes. ;-)

Houston incluye una utilidad de terminal a la que le pasamos un token, la ubicación del certificado y el mensaje que queremos enviar. Esto no verifica que hayamos configurado correctamente ningún servidor, solo que el certificado y el token sean correctos:

$ apn push "1F0E7706D1A343BF17615051DB944743F18156C52EFF0F8DD43DDA23F156862A" -c "config/apple_push_notification.pem" -m "Okay, stand by Thirteen, we're looking at it."

Siguientes pasos

Nuevamente vuelvo a tener sentimientos encontrados. Aunque esta vez he terminado más satisfecho de forma general con los resultados obtenidos de Helios, Orbiter y Houston todavía queda mucho por hacer para tener algo que se parezca mínimamente a lo que nos ofrecen Parse o Urban Airship

Hemos almacenado los tokens en nuestro servidor y hemos sido capaces de enviar notificaciones a nuestro dispositivo desde el terminal. Podríamos realizar el envío de mensajes desde la propia aplicación con un método semejante a este:

- (void)useHoustonServiceWithAlert:(NSString *)alert 
{
    NSURL *url = [NSURL URLWithString:@"http://localhost:3000"];
    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
    [httpClient setAuthorizationHeaderWithUsername:@"GoogleReader" password:@"F0r3v3r"];
    
    NSDictionary *payload = @{@"aps": @{@"alert": alert, @"sound": @"default"}};
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload
                                                       options:0
                                                         error:nil];
    
    NSString *JSONString = [[NSString alloc] initWithBytes:[jsonData bytes] length:[jsonData length] encoding:NSUTF8StringEncoding];
    
    NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
                            JSONString, @"payload",
                            nil];

    [httpClient postPath:@"/message" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSString *responseStr = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
        NSLog(@"Request Successful, response '%@'", responseStr);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"[HTTPClient Error]: %@", error.localizedDescription);
    }];
}

Pero en realidad este mensaje no se lo estaríamos enviando al Apolo XIII solamente si no a toda la misión Apolo. Si nos atrevemos a mirar el código fuente de Helios, el fichero push-notification.rb es bastante clarificador: no solo admite el parámetro payload, también admite otro parámetro llamado tokens que es de tipo array. Es decir, si le pasamos un array de tokens el mensaje solo llegará a los dispositivos determinados por esos tokens.

Pero los tokens están almacenados en el servidor, hacemos el registro con Orbiter cada vez que un dispositivo en el que instalamos la aplicación acepta que le enviemos notificaciones Push. ¿Tenemos que descargarnos el listado completo de tokens al dispositivo y filtrar por los alias? Parece que no.

Si nos fijamos en el mismo fuente de antes, donde se define el método get devices, vemos que hay algunos parámetros que se le pueden pasar a dicho método: pages, per_page, limit, offset y q... los cuatro primeros son, obviamente, para controlar la paginación de los resultados y el quinto parece ser el indicado para hacer una query a la tabla Device pero, ¿qué es tsquery? Pues según la documentación de PostgreSQL es la forma de hacer búsqueda completa de texto dentro de una determinada entidad.

En realidad tsquery devolverá todas las registros en los que aparezca el lexema que estamos buscando, esto es, si queremos buscar el token del Apolo XIII haríamos una llamada como la siguiente:

$ curl -X GET --user "GoogleReader:F0r3v3r" http://localhost:3000/devices?q=ApoloXIII

Pero si, por ejemplo, quisiéramos buscar el token del Apolo XI:

$ curl -X GET --user "GoogleReader:F0r3v3r" http://localhost:3000/devices?q=ApoloXI

Recuperariamos los tokens de los Apolos XI, XII, XIII y XIV. Es más, como la búsqueda se hace en todos los campos de la entidad Device, pudiera ser que recuperáramos algún otro registro más. Por ejemplo, si en vez de misiones espaciales utilizáramos como alias los nombres de las lunas de Jupiter. Al buscar el token de Europe, la consulta nos devolvería el token del alias Europe y el de todos los registros de dispositivos con timezone europeo.

Para este caso particular, y viendo el funcionamiento de los servicios de Parse y Urban Airship, sería conveniente ampliar las funcionalidades de push-notification.rb para incluir los alias como parámetro y buscar sus tokens. Además sería conveniente hacer el envío de notificaciones en batch para los casos en los que hubiera muchos tokens. Sería recomendable que las notificaciones se almacenaran en base de datos y que el envío se hiciera en background... vamos, que esto es un no parar. :-)

Aun con todo el trabajo que queda por hacer, hemos podido ver lo sencillo que es montar un sistema de notificaciones para nuestras aplicaciones iOS. Si solo lo queremos para enviar, de vez en cuando, un mensajero para que nos compren alguna aplicación, con esto tenemos más que suficiente.