Erlang Apple Push notification not getting response-error before disconnect - ssl

I'm currently testing a bit my push notification module.
When the Device Token is invalid, it disconnects...
According to Apple push notification developer documentation I should get an error-response packet just before the apple push server disconnect...
The thing is I get disconnected, but I do not get anything on the Socket just before that, and I need to know if the push failed because of a malformed push (so I can fix the bug), or an invalid device token (so I can remove it from the database).
Here's my code :
-module(pushiphone).
-behaviour(gen_server).
-export([start/1, init/1, handle_call/3, handle_cast/2, code_change/3, handle_info/2, terminate/2]).
-import(ssl, [connect/4]).
-record(push, {socket, state, cert, key}).
start(Provisioning) ->
gen_server:start_link(?MODULE, [Provisioning], []).
init([Provisioning]) ->
gen_server:cast(self(), {connect, Provisioning}),
{ok, #push{}}.
send(Socket, DT, Payload) ->
PayloadLen = length(Payload),
DTLen = size(DT),
PayloadBin = list_to_binary(Payload),
Packet = <<0:8,
DTLen:16/big,
DT/binary,
PayloadLen:16/big,
PayloadBin/binary>>,
ssl:send(Socket, Packet).
handle_call(_, _, P) ->
{noreply, P}.
handle_cast({connect, Provisioning}, P) ->
case Provisioning of
dev -> Address = "gateway.sandbox.push.apple.com";
prod -> Address = "gateway.push.apple.com"
end,
Port = 2195,
Cert="/apns-" ++ atom_to_list(Provisioning) ++ "-cert.pem",
Key="/apns-" ++ atom_to_list(Provisioning) ++ "-key.pem",
Options = [{certfile, Cert}, {keyfile, Key}, {password, "********"}, {mode, binary}, {active, true}],
Timeout = 1000,
{ok, Socket} = ssl:connect(Address, Port, Options, Timeout),
{noreply, P#push{socket=Socket}};
handle_cast(_, P) ->
{noreply, P}.
handle_info({ssl, Socket, Data}, P) ->
<<Command, Status, SomeID:32/big>> = Data,
io:fwrite("[PUSH][ERROR]: ~p / ~p / ~p~n", [Command, Status, SomeID]),
ssl:close(Socket),
{noreply, P};
handle_info({push, message, DT, Badge, [Message]}, P) ->
Payload = "{\"aps\":{\"alert\":\"" ++ Message ++ "\",\"badge\":" ++ Badge ++ ",\"sound\":\"" ++ "msg.caf" ++ "\"}}",
send(P#push.socket, DT, Payload),
{noreply, P};
handle_info({ssl_closed, _SslSocket}, P) ->
io:fwrite("SSL CLOSED !!!!!!~n"),
{stop, normal, P};
handle_info(AnythingElse, P) ->
io:fwrite("[ERROR][PUSH][ANYTHING ELSE] : ~p~n", [AnythingElse]),
{noreply, P}.
code_change(_, P, _) ->
{ok, P}.
terminate(_, _) ->
ok.
It works great when the payload and the deviceToken are both right. if deviceToken is invalid, it only get's disconnected.
Does anyone can spot the issue ? because after 4 hours of searching, I have only found out that I clearly can't !
Here's the error-response table :
Status code Description
0 No errors encountered
1 Processing error
2 Missing device token
3 Missing topic
4 Missing payload
5 Invalid token size
6 Invalid topic size
7 Invalid payload size
8 Invalid token
255 None (unknown)

You seem to be using the simple notification format as defined by figure 5-1 in the apple documentation you've linked to (judging by your send() function). When this format is used, no error response is provided when the request is malformed - you just get the disconnect.
To get the error response you should be using the enhanced notification format detailed in figure 5-2.

Related

Receive message from an Elm process

I'm toying around with Elm processes in order to learn more about how they work. In parts of this, I'm trying to implement a timer.
I bumped into an obstacle, however: I can't find a way to access the result of a process' task in the rest of the code.
For a second, I hoped that if I make the task resolve with a Cmd, the Elm runtime would be kind enough to perform that effect for me, but that was a naive idea:
type Msg
= Spawned Process.Id
| TimeIsUp
init _ =
( Nothing
, Task.perform Spawned (Process.spawn backgroundTask)
)
backgroundTask : Task.Task y (Platform.Cmd.Cmd Msg)
backgroundTask =
Process.sleep 1000
-- pathetic attempt to send a Msg starts here
|> Task.map ( always
<| Task.perform (always TimeIsUp)
<| Task.succeed ()
)
-- and ends here
|> Task.map (Debug.log "Timer finished") -- logs "Timer finished: <internals>"
update msg state =
case msg of
Spawned id ->
(Just id, Cmd.none)
TimeIsUp ->
(Nothing, Cmd.none)
view state =
case state of
Just id ->
text "Running"
Nothing ->
text "Time is up"
The docs say
there is no public API for processes to communicate with each other.
I'm not sure if that implies that a process can't cummunicate with the rest of the app.
Is there any way to have update function receive a TimeIsUp once the process exits?
There is one way but it requires a port of hell:
make a fake HTTP request from the process,
then intercept it via JavaScript
and pass it back to Elm.
port ofHell : (() -> msg) -> Sub msg
subscriptions _ =
ofHell (always TimeIsUp)
backgroundTask : Task.Task y (Http.Response String)
backgroundTask =
Process.sleep 1000
-- nasty hack starts here
|> Task.andThen ( always
<| Http.task { method = "EVIL"
, headers = []
, url = ""
, body = Http.emptyBody
, resolver = Http.stringResolver (always Ok "")
, timeout = Nothing
}
)
Under the hood, Http.task invokes new XMLHttpRequest(), so we can intercept it by redefining that constructor.
<script src="elm-app.js"></script>
<div id=hack></div>
<script>
var app = Elm.Hack.init({
node: document.getElementById('hack')
})
var orig = window.XMLHttpRequest
window.XMLHttpRequest = function () {
var req = new orig()
var orig = req.open
req.open = function (method) {
if (method == 'EVIL') {
app.ports.ofHell.send(null)
}
return orig.open.apply(this, arguments)
}
return req
}
</script>
The solution is not production ready, but it does let you continue playing around with Elm processes.
Elm Processes aren't a fully fledged API at the moment. It's not possible to do what you want with the Process library on its own.
See the notes in the docs for Process.spawn:
Note: This creates a relatively restricted kind of Process because it cannot receive any messages. More flexibility for user-defined processes will come in a later release!
and the whole Future Plans section, eg.:
Right now, this library is pretty sparse. For example, there is no public API for processes to communicate with each other.

Race-condition while sending http request

I have a situation that I need to dispatch two post requests, synchronously, and the second depends on the response of the first, the problem is that the second gets sent even before the first's response is allocated, thus sending wrong information:
update : msg -> model -> (model, Cmd msg)
update msg m =
case msg of
...
Submit -> (m,
send FirstResp <| post "/resource1"
(jsonBody <| encoderX m) int)
FirstResp (Ok x) -> ({m | pid = x},
send SecondResp <| post "/resource2"
(jsonBody <| encoderY m) int)
...
I tested it several times. If the server gives 3 in the first post, the pid gets sent as 0, but If I submit it again, the pid is sent as 3 and the answer from the server, 4, for example, is ignored.
How can I make the post to wait for the value to be allocated?
As data structures in elm are immutable {m | pid = x} doesn't change m but returns a new record. So you have no updated model when you pass it to your 2nd request.
Using {m | pid = x} twice would get you the result you are looking for (but it's not very beautiful)
FirstResp (Ok x) -> ({m | pid = x},
send SecondResp <| post "/resource2"
(jsonBody <| encoderY {m | pid = x}) int)
You can use let in to store the new model in a variable before you send the request. If you modify the model now you only have to look in one place.
FirstResp (Ok x) ->
let
newM = {m | pid = x}
in
(newM, send SecondResp <| post "/resource2"
(jsonBody <| encoderY newM) int)
If you don't need the result of the first request in your model the even better solution would be to chain the requests with Task.andThen. With this you don't need 2 separate messages (FirstResp, SecondResp).
request1 m =
post "/resource1" (jsonBody <| encoderX m) int)
|> Http.toTask
request2 m =
post "/resource1" (jsonBody <| encoderX m) int)
|> Http.toTask
Submit ->
( m
, request1 m
|> Task.andThen request2
|> Task.attempt Resp
)
Resp (Ok res2) ->
-- res2 is the result of your request2
If you need both results you can map them into a Tuple and extract it in the update function.
Submit ->
( m
, request1 m
|> Task.andThen
(\res1 -> request2 res1
|> Task.map ((,) res1)
)
|> Task.attempt Resp
)
Resp (Ok (res1, res2) ->
-- use res1 and res2
Elm packages - Task

Elm divide subscription?

I'm playing with Elm and WebRTC, so I made a listen port which gets some messages from js:
type alias Message =
{ channel : String
, data : String
}
port listen : (Message -> msg) -> Sub msg
Now I would like to be able to divide the messages to different parts of my app. For instance, the chat uses the "chat" channel, while the game logic uses "game".
Is it possible to create a listenTo String subscription that filters out the messages with the correct channel (only returning the data)? Or perhaps a different way of doing it?
Update:
What I currently have, is something like this:
In my main.elm I have an update that looks like this. It can receive messages (from rtc) itself, and send messages for chat to it. (I would later add a "ForGame" then too)
type Msg = Received WebRTC.Message | ForChat Chat.Msg
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Received message ->
let
_ = Debug.log ("Received message on \"" ++ message.channel ++ "\": " ++ message.data)
in
( model
, Cmd.none
)
ForChat msg ->
let
(chatModel, chatCmd) = Chat.update msg model.chat
in
({ model | chat = chatModel}, Cmd.map ForChat chatCmd)
Then I have subscriptions that combines all my subscriptions:
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ WebRTC.listen Received
, Sub.map ForChat <| Chat.subscriptions model.chat
]
In Chat.elm I have a similar structure, with an update that handles it's messages. The subscription of the chat listens to all messages from WebRTC, but filters only the ones with channel chat:
subscriptions : Model -> Sub Msg
subscriptions model = WebRTC.listen forChatMessages
forChatMessages : WebRTC.Message -> Msg
forChatMessages webrtcMessage =
if webrtcMessage.channel == "chat"
then
let
message = decodeMessage webrtcMessage.data
in
case message of
Ok msg -> Receive msg
Err error -> Debug.log ("Received unreadable message on chat channel \"" ++ toString webrtcMessage.data ++ "\" with error \"" ++ error ++ "\"") Ignore
else
Ignore
(Ignore is a Msg for chat, which just does nothing case msg of Ignore -> (model, Cmd.none). decodeMessage uses a decoder to decode a message decodeMessage : String -> Result String Message.)
I'm quite happy with this, because this way all logic for chat is in Chat.elm. So main.elm doesn't need to know what channels chat is using. Chat just follows the standard structure (Msg, update, view, subscriptions) and main forwards everything.
The only thing that's still not great, is that in Chat.elm I have the forChatMessages function. Used like: subscriptions model = WebRTC.listen forChatMessages. I would like to make this more reuseable, so it would become something like:
subscriptions model = WebRTC.listen for "chat" decodeMessage Receive Ignore
It would then be reusable by the game:
subscriptions model = WebRTC.listen for "game" decodeGameInfo UpdateInfo Ignore
Update 2:
I managed to generalize the forChatMessages function into:
for : String -> (String -> Result String d) -> (d -> msg) -> msg -> Message -> msg
for channel decoder good bad webrtcMessage =
if
webrtcMessage.channel == channel
then
let
decoded = decoder webrtcMessage.data
in
case decoded of
Ok data -> good data
Err error -> Debug.log ("Failed decoding message on " ++ channel ++ "channel \"" ++ toString webrtcMessage.data ++ "\" with error \"" ++ error ++ "\"") bad
else
bad
So I think I found the solution myself. Unless someones has comments on this. Perhaps there is a cleaner/nicer/better way of doing the same?
Let's say you have the following Msg definition:
type Msg
= Listen Message
| GameChannel String
| ChatChannel String
Your update function could then act upon the channel value and call update again with the correct channel, ignoring all channel values except for "game" and "chat":
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Listen message ->
case message.channel of
"game" ->
update (GameChannel message.data) model
"chat" ->
update (ChatChannel message.data) model
_ ->
model ! []
GameChannel data ->
...
ChatChannel data ->
...
Your subscription function would look something like this:
subscriptions : Model -> Sub Msg
subscriptions model =
listen Listen
I found a solution myself, and added it to the original question.
For clarity, this is the short version:
In my main.elm:
type Msg = Received WebRTC.Message | ForChat Chat.Msg
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Received message ->
let
_ = Debug.log ("Received message on \"" ++ message.channel ++ "\": " ++ message.data)
in
( model
, Cmd.none
)
ForChat msg ->
let
(chatModel, chatCmd) = Chat.update msg model.chat
in
({ model | chat = chatModel}, Cmd.map ForChat chatCmd)
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ WebRTC.listen Received
, Sub.map ForChat <| Chat.subscriptions model.chat
]
In Chat.elm:
subscriptions : Model -> Sub Msg
subscriptions model = WebRTC.listen <| for "game" decodeGameInfo UpdateInfo Ignore
In WebRTC.elm:
type alias Message =
{ channel : String
, data : String
}
port listen : (Message -> msg) -> Sub msg
for : String -> (String -> Result String d) -> (d -> msg) -> msg -> Message -> msg
for channel decoder good bad webrtcMessage =
if
webrtcMessage.channel == channel
then
let
decoded = decoder webrtcMessage.data
in
case decoded of
Ok data -> good data
Err error -> Debug.log ("Failed decoding message on " ++ channel ++ "channel \"" ++ toString webrtcMessage.data ++ "\" with error \"" ++ error ++ "\"") bad
else
bad

Elm read HTTP response body for non-200 response

How to read HTTP response body for a non 200 HTTP status
getJson : String -> String -> Effects Action
getJson url credentials =
Http.send Http.defaultSettings
{ verb = "GET"
, headers = [("Authorization", "Basic " ++ credentials)]
, url = url
, body = Http.empty
}
|> Http.fromJson decodeAccessToken
|> Task.toResult
|> Task.map UpdateAccessTokenFromServer
|> Effects.task
The above promotes the error from
Task.toResult : Task Http.Error a -> Task x (Result Http.Error a)
The value of which becomes
(BadResponse 400 ("Bad Request"))
My server responds with what is wrong with the request as a JSON payload in the response body. Please help me retrieve that from the Task x a into ServerResult below.
type alias ServerResult = { status : Int, message : String }
The Http package (v3.0.0) does not expose an easy way to treat HTTP codes outside of the 200 to 300 range as non-error responses. Looking at the source code, the handleResponse function is looking between the hardcoded 200 to 300 range
However, with a bit of copy and pasting from that Http package source code, you can create a custom function to replace Http.fromJson in order to handle HTTP status codes outside the normal "success" range.
Here's an example of the bare minimum you'll need to copy and paste to create a custom myFromJson function that acts the same as the Http package except for the fact it also treats a 400 as a success:
myFromJson : Json.Decoder a -> Task Http.RawError Http.Response -> Task Http.Error a
myFromJson decoder response =
let decode str =
case Json.decodeString decoder str of
Ok v -> Task.succeed v
Err msg -> Task.fail (Http.UnexpectedPayload msg)
in
Task.mapError promoteError response
`Task.andThen` myHandleResponse decode
myHandleResponse : (String -> Task Http.Error a) -> Http.Response -> Task Http.Error a
myHandleResponse handle response =
if (200 <= response.status && response.status < 300) || response.status == 400 then
case response.value of
Http.Text str ->
handle str
_ ->
Task.fail (Http.UnexpectedPayload "Response body is a blob, expecting a string.")
else
Task.fail (Http.BadResponse response.status response.statusText)
-- copied verbatim from Http package because it was not exposed
promoteError : Http.RawError -> Http.Error
promoteError rawError =
case rawError of
Http.RawTimeout -> Http.Timeout
Http.RawNetworkError -> Http.NetworkError
Again, that code snippet is almost entirely copy and pasted except for that 400 status check. Copying and pasting like that is usually a last resort, but because of the library restrictions, it seems to be one of your only options at this point.

Erlang + Apple Push notification [Issue with invalid token]

I'm currently trying to create a push notification module for Erlang.
When the token is valid, everything works great...
The issue is when an old device token (which is invalid by now) is rejected.
I understand that invalid token will be rejected by the apns with a 6 bytes socket message, and invalidate the connection (Which I think is really dumb, whatever...)
The thing is I do not seems to get the 6 bytes socket message that APNS should give me with my module, like the controlling process is not listening the socket.
Here's my code :
-module(pushiphone).
-behaviour(gen_server).
-export([start/1, init/1, handle_call/3, handle_cast/2, code_change/3, handle_info/2, terminate/2]).
-import(ssl, [connect/4]).
-record(push, {socket, pid, state, cert, key}).
start(Provisioning) ->
gen_server:start_link(?MODULE, [Provisioning], []).
init([Provisioning]) ->
gen_server:cast(self(), {connect, Provisioning}),
{ok, #push{pid=self()}}.
send(Socket, DT, Payload) ->
PayloadLen = length(Payload),
DTLen = size(DT),
PayloadBin = list_to_binary(Payload),
Packet = <<0:8,
DTLen:16/big,
DT/binary,
PayloadLen:16/big,
PayloadBin/binary>>,
ssl:send(Socket, Packet).
handle_call(_, _, P) ->
{noreply, P}.
handle_cast({connect, Provisioning}, P) ->
case Provisioning of
dev -> Address = "gateway.sandbox.push.apple.com";
prod -> Address = "gateway.push.apple.com"
end,
Port = 2195,
Cert="/apns-" ++ atom_to_list(Provisioning) ++ "-cert.pem",
Key="/apns-" ++ atom_to_list(Provisioning) ++ "-key.pem",
Options = [{certfile, Cert}, {keyfile, Key}, {password, "********"}, {mode, binary}, {active, false}],
Timeout = 1000,
{ok, Socket} = ssl:connect(Address, Port, Options, Timeout),
ssl:controlling_process(Socket, self()), %% Necessary ??
gproc:reg({n,l, pushiphone}),
{noreply, P#push{socket=Socket}};
handle_cast(_, P) ->
{noreply, P}.
handle_info({ssl, Socket, Data}, P) ->
<<Command, Status, SomeID:32/big>> = Data,
io:fwrite("[PUSH][ERROR]: ~p / ~p / ~p~n", [Command, Status, SomeID]),
ssl:close(Socket),
{noreply, P};
handle_info({push, message, DT, Badge, [Message]}, P) ->
Payload = "{\"aps\":{\"alert\":\"" ++ Message ++ "\",\"badge\":" ++ Badge ++ ",\"sound\":\"" ++ "msg.caf" ++ "\"}}",
send(P#push.socket, DT, Payload),
{noreply, P};
handle_info({ssl_closed, _SslSocket}, P) ->
io:fwrite("SSL CLOSED !!!!!!~n"),
{stop, normal, P};
handle_info(AnythingElse, P) ->
io:fwrite("[ERROR][PUSH][ANYTHING ELSE] : ~p~n", [AnythingElse]),
{noreply, P}.
code_change(_, P, _) ->
{ok, P}.
terminate(_, _) ->
ok.
So When I start the module, and I push to a valid token, the push is received on the phone, but when I push to an invalid token, and then to a valid token, the valid token won't receive any push...
I am aware I should listen to the feedback service in order to remove the Device token from my database, but I also need to know if the push gateway as invalidated my connection in order to reconnect.
So Here's the real question : Why my gen-server doesn't receive the error-response packet (which should match the handle_info({ssl, Socket, Data}, P) )?
Your socket is configured with active=false. You won't receive any messages unless you set it active=true (or repeatedly active=once). See the documentation for inet:setopts/2.
You also shouldn't have to set the controlling_process to self().