alexbruf/elm-clerk/2.0.0

Clerk

Clerk authentication for Elm, bridged through ClerkJS by the companion npm package @viewengine/elm-clerk.

This package holds the typed state, the decoders and the command API. The two ports live in your own application, because the Elm package registry does not allow a package to declare them. Copy Ports.elm from the elm-clerk repository into your app: it declares clerkOut : Value -> Cmd msg and clerkIn : (Value -> msg) -> Sub msg, and it never changes.

Then hand both of them to this package as a Ports record:

clerkPorts : Clerk.Ports Msg
clerkPorts =
    { toJs = Ports.clerkOut, fromJs = Ports.clerkIn }

State

type State
= Loading
| SignedOut
| SignedIn
{ session : Session
, user : User
, organization : Maybe Organization
}

Everything the app knows about the current visitor.

Loading is the only state before the shim reports the first stateChanged, so every app must render something for it.

type alias Msg =
Msg

An opaque message produced by subscriptions. Wrap it in one of your own constructors and hand it back to update.

type alias Ports msg =
{ toJs : Value -> Cmd msg
, fromJs : (Value -> msg) -> Sub msg
}

The two ports the shim talks over. They are passed to every function in this module, so this package declares none of its own.

type Event
= StateChanged State
| TokenReceived
{ requestId : String
, token : String
}
| TokenFailed
{ requestId : String
, reason : String
}
| Error String

What just happened, returned by update so the app can react to it. Ignore the events you do not care about.

Error carries a human readable description of anything that went wrong, including an unknown message tag and a message this package could not decode. It never crashes the app.

Wiring

The state before the shim has said anything, which is Loading.

If the shim hands you the current state as a flag, decode that with stateDecoder instead so the first render is correct.

update : Ports msg -> Msg -> State -> ( State, Cmd msg, Maybe Event )

Fold an incoming message into the state.

A stateChanged message replaces the state and reports Just (StateChanged newState); every other message leaves the state alone and only reports an event.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ClerkMsg clerkMsg ->
            let
                ( clerk, cmd, event ) =
                    Clerk.update clerkPorts clerkMsg model.clerk
            in
            ( { model | clerk = clerk }, cmd )
subscriptions : Ports msg -> (Msg -> msg) -> Sub msg

Listen for messages from the shim.

subscriptions : Model -> Sub Msg
subscriptions _ =
    Clerk.subscriptions clerkPorts ClerkMsg

Commands

signOut : Ports msg -> Cmd msg

Sign the current user out, ending the session in every tab.

openSignIn : Ports msg -> Cmd msg

Open Clerk's hosted sign-in modal.

openSignUp : Ports msg -> Cmd msg

Open Clerk's hosted sign-up modal.

openUserProfile : Ports msg -> Cmd msg

Open Clerk's hosted user profile modal.

mountSignIn : Ports msg -> String -> Cmd msg

Mount Clerk's sign-in component into the element with the given id.

The element must already be in the document, so send this from a command after the view that contains it has rendered.

mountUserButton : Ports msg -> String -> Cmd msg

Mount Clerk's user button into the element with the given id.

unmount : Ports msg -> String -> Cmd msg

Unmount whatever Clerk component was mounted on the element with the given id.

Ports msg
-> { requestId : String
, template : Maybe String
}
-> Cmd msg

Ask for a session token, optionally minted from a JWT template.

You choose the requestId and get it back on the matching TokenReceived or TokenFailed event, so several requests can be in flight at once.

Clerk.requestToken clerkPorts
    { requestId = "backend-call-1", template = Just "backend" }
setActiveOrganization : Ports msg -> String -> Cmd msg

Make the organization with the given id the active one. The shim reports the result as a stateChanged event.

Flags

Decode the state JSON the shim sends, which is also what it passes as the initial flag:

{ "status": "loading" }


{ "status": "signedOut" }


{ "status": "signedIn"
, "session": Session
, "user": User
, "organization": Organization | null
}

Use it on your flags so the very first render already knows who is signed in:

Decode.decodeValue Clerk.stateDecoder flags
    |> Result.withDefault Clerk.init