Skip to content

Instantly share code, notes, and snippets.

@szabba
Forked from maxhoffmann/Main.elm
Last active June 1, 2016 09:41
Show Gist options
  • Save szabba/a3df95d9777d184f7ff8e799dcbd3521 to your computer and use it in GitHub Desktop.
Save szabba/a3df95d9777d184f7ff8e799dcbd3521 to your computer and use it in GitHub Desktop.
Token HTTP Authentication in Elm
{
"version": "1.0.0",
"summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
"."
],
"exposed-modules": [],
"dependencies": {
"Fresheyeball/elm-tuple-extra": "2.1.0 <= v < 3.0.0",
"elm-community/list-extra": "2.0.0 <= v < 3.0.0",
"elm-lang/core": "4.0.1 <= v < 5.0.0",
"elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-http": "3.0.1 <= v < 4.0.0"
},
"elm-version": "0.17.0 <= v < 0.18.0"
}
module Main exposing (..)
import Char
import Html as H exposing (Html)
import Html.App as App
import Html.Events as HE
import Http
import Json.Decode as Json exposing ((:=))
import List.Extra as List
import Random exposing (Generator)
import String
import Task exposing (Task)
import Tuple2
import TokenRetry
main : Program Never
main =
App.program
{ init = init
, update = update
, view = view
, subscriptions = always Sub.none
}
-- MODEL
type alias Model =
{ tokenHandler : TokenRetry.Model Msg
, errors : List Http.Error
, ip : String
}
type Token
= Good String
| Bad String
| None
init : ( Model, Cmd Msg )
init =
let
model =
Model (TokenRetry.new fetchToken RetryMsg) [] ""
in
model ! [ TokenRetry.initCmd model.tokenHandler ]
-- UPDATE
type Msg
= GotIP String
| FetchIP
| RetryMsg (TokenRetry.Msg Msg)
| Error Http.Error
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg |> Debug.log "msg" of
RetryMsg msg ->
model.tokenHandler
|> TokenRetry.update msg
|> Tuple2.mapFst (\tokenHandler -> { model | tokenHandler = tokenHandler })
FetchIP ->
TokenRetry.Request fetchIP Error
|> TokenRetry.send model.tokenHandler
|> (,) model
GotIP ip ->
{ model | ip = ip } ! []
Error err ->
{ model | errors = err :: model.errors } ! []
-- VIEW
view : Model -> Html Msg
view model =
H.div []
[ H.button [ HE.onClick FetchIP ] [ H.text "send request" ]
, H.text model.ip
, errorList model.errors
]
errorList : List Http.Error -> Html msg
errorList errors =
errors
|> List.map (H.li [] << List.singleton << H.text << toString)
|> H.ol []
-- HTTP API
fetchIP : String -> Task Http.Error Msg
fetchIP token =
Http.url "http://ip-api.com/json" [ (,) "token" token ]
|> Http.get ("query" := Json.string)
|> Task.map (Debug.log "got IP" >> GotIP)
fetchToken : Cmd String
fetchToken =
Random.generate identity tokenGenerator
tokenGenerator : Generator String
tokenGenerator =
let
merge one other =
Random.andThen Random.bool
(\cond ->
if cond then
one
else
other
)
upLetter =
Random.int 65 90
|> Random.map Char.fromCode
digit =
Random.int 48 57
|> Random.map Char.fromCode
in
(upLetter `merge` digit)
|> Random.list 20
|> Random.map (List.foldl String.cons "")
module TokenRetry exposing (Model, Request, Msg, new, initCmd, update, send)
import Http
import Task exposing (Task)
import Time
new : Cmd String -> (Msg msg -> msg) -> Model msg
new fetchToken wrapMsg =
Model None [] (Cmd.map GotToken fetchToken) wrapMsg
initCmd : Model msg -> Cmd msg
initCmd model =
model.fetchToken |> Cmd.map model.wrapMsg
send : Model msg -> Request msg -> Cmd msg
send model request =
request
|> IssueRequest
|> model.wrapMsg
|> \msg ->
Time.now
|> Task.perform (\_ -> Debug.crash "never") (always msg)
-- MODEL
type alias Model msg =
{ token : Token
, requests : List (Request msg)
, fetchToken : Cmd (Msg msg)
, wrapMsg : Msg msg -> msg
}
type alias Request msg =
{ run : String -> Task Http.Error msg
, onError : Http.Error -> msg
}
type Token
= Good String
| Bad String
| None
-- UPDATE
type Msg msg
= GotToken String
| FetchToken
| IssueRequest (Request msg)
| Error (Request msg) msg
update : Msg msg -> Model msg -> ( Model msg, Cmd msg )
update msg model =
case msg of
GotToken token ->
model.requests
|> List.map (requestToCommand model token)
|> (!) { model | token = Good token, requests = [] }
FetchToken ->
model.fetchToken
|> Cmd.map model.wrapMsg
|> (,) model
IssueRequest request ->
model
|> issueRequest request
Error request msg ->
let
( model', cmds ) =
markTokenAsBad model
( model'', cmds' ) =
model' |> issueRequest request
in
model'' ! [ cmds, cmds' ]
issueRequest : Request msg -> Model msg -> ( Model msg, Cmd msg )
issueRequest request model =
case model.token of
Bad _ ->
{ model | requests = request :: model.requests } ! []
Good token ->
requestToCommand model token request
|> (,) model
None ->
model.fetchToken
|> Cmd.map model.wrapMsg
|> (,) { model | requests = request :: model.requests }
requestToCommand : Model msg -> String -> Request msg -> Cmd msg
requestToCommand model token ({ run, onError } as request) =
token
|> run
|> Task.perform (handleError model request onError) identity
handleError : Model msg -> Request msg -> (Http.Error -> msg) -> Http.Error -> msg
handleError model request onError err =
onError err
|> Error request
|> model.wrapMsg
markTokenAsBad : Model msg -> ( Model msg, Cmd msg )
markTokenAsBad model =
let
newModel =
case model.token of
Good token ->
{ model | token = Bad token }
_ ->
model
in
model.fetchToken
|> Cmd.map model.wrapMsg
|> (,) newModel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment