adobkin / libcapn

A simple C Library for interact with the Apple Push Notification Service (APNs)
MIT License
100 stars 35 forks source link

libcapn

Build Status MIT

libcapn is a C Library to interact with the Apple Push Notification Service (APNs for short) using simple and intuitive API. With the library you can easily send push notifications to iOS and OS X (>= 10.8) devices.

Version 2.0 isn't compatible with 1.0

Table of Contents

Installation

on *nix

Requirements

Build instructions

$ git clone https://github.com/adobkin/libcapn.git
$ git submodule update --init
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr ../
$ make
$ sudo make install

on Windows

Requirements

Build instructions

  1. Download the latest source archive from GitHub and extract it somewhere on your disk, e.g. C:\libcapn

  2. Open command console (Win-R ==> "cmd" => Enter)

  3. Go to the libcapn directory and run win_build\build.bat

cd C:\libcapn
win_build\build.bat

Quick Start

First, initialize the library by calling apn_library_init(). This function must be called at least once before any calls to other libcapn functions. Because apn_library_init() is not thread-safe, you must not call it while any other thread in the program is running.
The reason is that apn_library_init() calls initialization functions of SSL library that are not thread-safe.

Initialize and configure context

Create a new apn context and specify the path to a certificate file and the path to a private key file using apn_set_certificate(). If the private key is password protected, pass it as well, otherwise pass NULL. The Certificate and the private key must be in PEM format. An alternative way is to use a .p12 file instead of a certificate and a private key. Use apn_set_pkcs12_file() to specify the path to a .p12 file . If a .p12 file is specified, certificate and private key will be ignored.

apn_ctx_t *ctx = apn_init();
if(!ctx) {
    // error
}

// Uses certificate and private key (in PEM format)
apn_set_certificate(ctx, "push_test.pem", "push_test_key.pem", "12345678");

// Uses .p12 file
apn_set_pkcs12_file(ctx, "push_test.p12", "123");

By default the library uses production environment to interact with Apple Push Notification Service (APNs). Call apn_set_mode() passing APN_MODE_SANDBOX to use sandbox environment.

Certificate and private key (or .p12 file) must conform to the specified mode, otherwise the notifications will not be transported to the device.

 apn_set_mode(ctx,  APN_MODE_SANDBOX);

To specify behavior call apn_set_behavior(). Function takes one or more bit flags as a parameter:

 apn_set_behavior(apn_ctx, APN_OPTION_RECONNECT | APN_OPTION_LOG_STDERR);

Available flags:

Logging

For logging specify log level and pointer to callback-function using apn_set_log_level() and apn_set_log_callback():

void logfunc(apn_log_level level, const char * const message, uint32_t len) {
    printf("======> %s\n", message);
}

apn_set_log_level(ctx, APN_LOG_LEVEL_INFO | APN_LOG_LEVEL_ERROR | APN_LOG_LEVEL_DEBUG);
apn_set_log_callback(ctx, logfunc);

Connection

To establishes connection to the APNs, calling apn_connect():

if(APN_ERROR == apn_connect(ctx)) {
    printf("Could not connected to Apple Push Notification Service: %s (errno: %d)\n", apn_error_string(errno), errno);
    // error
}

Sending notifications

The notification payload

Every remote notification includes a payload. The payload contains information about how the system should alert the user as well as any custom data you provide.

To create a payload you need to use apn_payload_init(); to set general properties of the payload, use apn_payload_set_*() functions. You can also specify custom properties using apn_payload_add_custom_property_*() functions:

apn_payload_t *payload = apn_payload_init();
if(!payload) {
    printf("Unable to init payload: %s (%d)\n", apn_error_string(errno), errno);
    // error
}

apn_payload_set_badge(payload, 10);           
apn_payload_set_body(payload, "Test Push Message");

// Custom property
apn_payload_add_custom_property_integer(payload, "custom_property_integer", 100);
...

In iOS 8 and later, the maximum size allowed for a payload is 2 kilobytes; prior to iOS 8 and in OS X, the maximum payload size is 256 bytes. APNs rejects any notification that exceeds this limit.

A payload may contain the content-available property. If this property is set to a value of 1, it lets the remote notification act as a “silent” notification. When a silent notification arrives, iOS wakes up your app in the background so that you can get new data from your server or do background information processing. Users aren’t told about the new or changed information that results from a silent notification.

By default the library uses default priority to notifications. Call apn_payload_set_priority(), passing APN_NOTIFICATION_PRIORITY_HIGH to use high priority:

apn_payload_set_priority(payload, APN_NOTIFICATION_PRIORITY_HIGH);

When you set high priority, notifications are sent immediately to devices. The notification must trigger an alert, sound, or badge on the device. It is an error to use this priority for a push that contains only the content-available key. When you set default priority, notifications are sent at a time that conserves power on the device receiving them.

Tokens

Next, create array of tokens and add the device tokens as either a hexadecimal string to array:

apn_array_t *tokens = apn_array_init(2, NULL, NULL);
if(tokens) {
    apn_array_insert(tokens, "XXXXXXXX");
    apn_array_insert(tokens, "YYYYYYYY");
    apn_array_insert(tokens, "ZZZZZZZZ");
}

Each push environment will issue a different token(s) for the same device or computer. The device token(s) for production is different from the development one. If you are using a production mode, you must use a production token(s) and vice versa

Send

To send notification to devices call apn_send(), passing context, payload, array of device tokens and pointer to a invalid tokens array. The array should be freed - call apn_array_free():


apn_array_t *invalid_tokens = NULL;

if(APN_ERROR == apn_send(ctx, payload, tokens, &invalid_tokens))  {
        printf("Could not sent push: %s (errno: %d)\n", apn_error_string(errno), errno)
} else {
    if (invalid_tokens) {
        printf, "Invalid tokens:\n");
        uint32_t i = 0;
        for (; i < apn_array_count(invalid_tokens); i++) {
            printf("    %u. %s\n", i, apn_array_item_at_index(invalid_tokens, i));
        }
        apn_array_free(invalid_tokens);
    }
}

The APNs drops the connection if it receives an invalid token. You'll need to reconnect and send notification to token(s) following it, again.

If flag APN_OPTION_RECONNECT is specified, the apn_send() automatically establishes new connection to APNs when connection is dropped

Advanced, you can take invalid token, just specify a pointer to callback-function using apn_set_invalid_token_callback:

void invalid_token(const char * const token, uint32_t index) {
    printf("======> Invalid token: %s (index: %d)\n", token, index);
}

...

apn_ctx_t *ctx = ...
apn_set_invalid_token_callback(ctx, invalid_token);

The callback function will be called for each invalid token. Function has the following prototype:

void (*invalid_token_callback)(const char * const token, uint32_t index)

Example

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <capn/apn.h>

void __apn_logging(apn_log_levels level, const char * const message, uint32_t len) {
    printf("======> %s\n", message);
}

void __apn_invalid_token(const char * const token, uint32_t index) {
    printf("======> Invalid token: %s (index: %d)\n", token, index);
}

int main() {
    apn_payload_t *payload = NULL;
    apn_ctx_t *ctx = NULL;
    time_t time_now = 0;
    char *invalid_token = NULL;

    assert(apn_library_init() == APN_SUCCESS);

    time(&time_now);

    if(NULL == (ctx = apn_init())) {
        printf("Unable to init context: %d\n", errno);
        apn_library_free();
        return -1;
    }

    apn_set_pkcs12_file(ctx, "push_test.p12", "12345678");
    apn_set_mode(ctx,  APN_MODE_SANDBOX); //APN_MODE_PRODUCTION or APN_MODE_SANDBOX
    apn_set_behavior(ctx, APN_OPTION_RECONNECT);
    apn_set_log_level(ctx, APN_LOG_LEVEL_INFO | APN_LOG_LEVEL_ERROR | APN_LOG_LEVEL_DEBUG);
    apn_set_log_callback(ctx, __apn_logging);
    apn_set_invalid_token_callback(ctx, __apn_invalid_token);

    if(NULL == (payload = apn_payload_init())) {
        printf("Unable to init payload: %d\n", errno);
        apn_free(ctx);
        apn_library_free();
        return -1;
    }

    apn_payload_set_badge(payload, 10); // Icon badge
    apn_payload_set_body(payload, "Test Push Message");  // Notification text
    apn_payload_set_expiry(payload, time_now + 3600); // Expires
    apn_payload_set_priority(payload, APN_NOTIFICATION_PRIORITY_HIGH);  // Notification priority
    apn_payload_add_custom_property_integer(payload, "custom_property_integer", 100); // Custom property

    apn_array_t *tokens = apn_array_init(2, NULL, NULL);
    if(!tokens) {
        apn_free(ctx);
        apn_payload_free(payload);
        apn_library_free();
        return -1;
    }

    apn_array_insert(tokens, "XXXXXXXX");
    apn_array_insert(tokens, "YYYYYYYY");
    apn_array_insert(tokens, "ZZZZZZZZ");

    if(APN_ERROR == apn_connect(ctx)) {
        printf("Could not connect to Apple Push Notification Service: %s (errno: %d)\n", apn_error_string(errno), errno);
        apn_free(ctx);
        apn_payload_free(payload);
        apn_array_free(tokens);
        apn_library_free();
        return -1;
    }

    apn_array_t *invalid_tokens = NULL;
    int ret = 0;
    if (APN_ERROR == apn_send(ctx, payload, tokens, &invalid_tokens)) {
        printf("Could not send push: %s (errno: %d)\n", apn_error_string(errno), errno);
        ret = -1;
    } else {
        printf("Notification was successfully sent to %u device(s)\n",
            apn_array_count(tokens) - ((invalid_tokens) ? apn_array_count(invalid_tokens) : 0));
        if (invalid_tokens) {
            printf("Invalid tokens:\n");
            uint32_t i = 0;
            for (; i < apn_array_count(invalid_tokens); i++) {
                printf("    %u. %s\n", i, apn_array_item_at_index(invalid_tokens, i));
            }
            apn_array_free(invalid_tokens);
        }
    }

    apn_free(ctx);
    apn_payload_free(payload);
    apn_array_free(tokens);
    apn_library_free();

    return ret;
}

apn-pusher

apn-pusher - simple command line tool to send push notifications to iOS and OS X devices:

apn-pusher -c ./test_push.p12 -p -d -m 'Test' -t 1D2EE2B3A38689E0D43E6608FEDEFCA534BBAC6AD6930BFDA6F5CD72A808832B:1D2EE2B3A38689E0D43E6608FEDEFCA534BBAC6AD6930BFDA6F5CD72A808832A
apn-pusher -c ./test_push.p12 -p -d -m 'Test' -T ./tokens.txt -v

Options:

Usage: apn-pusher [OPTION]
    -h Print this message and exit
    -c Path to .p12 file (required)
    -p Passphrase for .p12 file. Will be asked from the tty
    -d Use sandbox mode
    -m Body of the alert to send in notification
    -a Indicates content available
    -b Badge number to set with notification
    -s Name of a sound file in the app bundle
    -i Name of an image file in the app bundle
    -y Category name of notification
    -t Tokens, separated with ':' (required)
    -T Path to file with tokens
    -v Make the operation more talkative