且听疯吟 如此生活三十年
ejabberd: Apple Push Notification Service

主要是通过 hook ejabberd 的离线消息处理,从而实现针对离线消息进行推送

1. Hook offline message

  • 记录 device token
    这一步没啥好说的,iOS 连接到 APNs 后获取到 device token,发给服务器,服务器负责维护好对应关系即可。
    注意客户端退出时清理掉相应记录。

  • 在 ejabberd 中新增一个模块,注册 offline_message_hook,大致如下:

    start(Host, Opts) ->
    ?INFO_MSG("Starting mod_push_service", []),
    register(?MODULE, spawn(?MODULE, init, [Host, Opts])),
    ok.
    
    init(Host, _Opts) ->
        ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, send_notification, 10),
        ok.
    
    stop(Host) ->
        ?INFO_MSG("Stopping mod_push_service", []),
        ejabberd_hooks:delete(offline_message_hook, Host,
            ?MODULE, send_notification, 10),
        ok.
    
    send_notification(From, To, Packet) ->
        dosomething.
    

2. Implement APNs

  • 从 Apple 获取推送证书

  • 在服务端使用该证书建立 ssl 连接

  • 按照 Apple 文档生成指定格式的 Payload 并发送

  • Apple 同时提供了 feedback channel 来获取失效 token 便于服务端获取记录,实现方式和发送消息类似,区别在于一个是发送消息,一个是接收消息

  • 连接的粗略代码如下

    -module(apntest).
    -export([connect/0]).
    
    connect() ->
        application:start(asn1),
        application:start(crypto),
        application:start(public_key),
        application:start(ssl),
        Address = "gateway.sandbox.push.apple.com",
        Port = 2195,
        Cert = "cert.pem",
        Options = [{certfile, Cert}, {mode, binary}],
        Timeout = 10000,
        case ssl:connect(Address, Port, Options, Timeout) of
            {ok, Socket} ->
                PayloadString = "{\"aps\":{\"alert\":\"Hello world.\",\"sound\":\"chime\", \"badge\":1}}",
                Payload = list_to_binary(PayloadString),
                PayloadLength = size(Payload),
                Packet = <<0:8,
                32:16/big,                16#ff496f96352abb7c875bedfc755287XXXXXXe14bfadade9ff6ba75360de65441:256/big,
                PayloadLength:16/big,
                Payload/binary>>,
                ssl:send(Socket, Packet),
                ssl:close(Socket),
                PayloadString;
            {error, Reason} ->
                Reason
        end.
    
  • 有一些开源的 Erlang APNs 推送服务实现,比如 apns4erl,不过这个项目实在引用了太多不必要的东西了 XD,还是推荐自己看懂后根据需求重新实现,代码也不复杂