Skip to content
This repository has been archived by the owner on Apr 21, 2020. It is now read-only.

Commit

Permalink
Merge pull request #138 from Gizra/123-private-pusher-channel
Browse files Browse the repository at this point in the history
Private pusher channel
  • Loading branch information
amitaibu authored Jul 10, 2017
2 parents 976defc + 6c49099 commit 4070f09
Show file tree
Hide file tree
Showing 33 changed files with 734 additions and 113 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ For the backend, check the [server README.md](https://github.com/Gizra/drupal-el

For the frontend, check the [client README.md](https://github.com/Gizra/drupal-elm-starter/blob/master/client/README.md)

## Pusher
When the item will be updated (E.g. via the backend on
[/node/1/edit](http://localhost/drupal-elm-starter/server/www/node/1/edit)),
it will fire Pusher messages that will update the Elm application in real time.

Editing an item produces pusher messages on two different channels: `general`
and `private-general`. The private channel is only accessible by users with
`access private fields` permission, and currently exposes the item's "Private
note" field, which normal users can't access.

Log in to the Elm app for example with `admin` / `admin` to see also the item
private note field (on [/#/item/1](http://localhost:3000/#/item/1) for
example), and get notifications through the private channel.
Log in with a normal user (For exmaple `alice` / `alice`), to get notifications
through the public pusher channel.

## Credits

[Gizra](https://gizra.com)
Expand Down
5 changes: 5 additions & 0 deletions client/src/elm/App/Model.elm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import App.PageType exposing (Page(..))
import Config.Model
import Date exposing (Date)
import Pages.Login.Model exposing (emptyModel, Model)
import Pusher.Model
import RemoteData exposing (RemoteData(..), WebData)
import ItemManager.Model exposing (emptyModel, Model)
import Time exposing (Time)
Expand All @@ -21,6 +22,8 @@ type Msg
= HandleOfflineEvent (Result String Bool)
| Logout
| MsgItemManager ItemManager.Model.Msg
| MsgPusher Pusher.Model.Msg
| NoOp
| PageLogin Pages.Login.Model.Msg
| SetActivePage Page
| SetCurrentDate Date
Expand All @@ -36,6 +39,7 @@ type alias Model =
, offline : Bool
, pageLogin : Pages.Login.Model.Model
, pageItem : ItemManager.Model.Model
, pusher : Pusher.Model.Model
, sidebarOpen : Bool
, user : WebData User
}
Expand All @@ -61,6 +65,7 @@ emptyModel =
, offline = False
, pageLogin = Pages.Login.Model.emptyModel
, pageItem = ItemManager.Model.emptyModel
, pusher = Pusher.Model.emptyModel
, sidebarOpen = False
, user = NotAsked
}
2 changes: 1 addition & 1 deletion client/src/elm/App/PageType.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module App.PageType exposing (Page(..))

{-| Prevent circula dependency.
{-| Prevent circular dependency.
-}


Expand Down
6 changes: 5 additions & 1 deletion client/src/elm/App/Test.elm
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ getPageAsAuthenticated : Page -> Page
getPageAsAuthenticated page =
let
dummyUser =
{ id = 100, name = "Foo", avatarUrl = "https://example.com" }
{ id = 100
, name = "Foo"
, avatarUrl = "https://example.com"
, pusherChannel = "general"
}

model =
{ emptyModel | user = Success dummyUser }
Expand Down
88 changes: 69 additions & 19 deletions client/src/elm/App/Update.elm
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import App.PageType exposing (Page(..))
import Config
import Date
import Dict
import Http
import ItemManager.Model
import ItemManager.Update
import Json.Decode exposing (bool, decodeValue)
import Json.Encode exposing (Value)
import Pages.Login.Update
import Pusher.Model
import Pusher.Utils exposing (getClusterName)
import Pusher.Update
import RemoteData exposing (RemoteData(..), WebData)
import Task
import Time exposing (minute)
Expand All @@ -30,12 +29,7 @@ init flags =
Just config ->
let
defaultCmds =
[ pusherKey
( config.pusherKey.key
, getClusterName config.pusherKey.cluster
, Pusher.Model.eventNames
)
, Task.perform SetCurrentDate Date.now
[ Task.perform SetCurrentDate Date.now
]

( cmds, activePage_ ) =
Expand Down Expand Up @@ -87,13 +81,21 @@ update msg model =
model ! []

Logout ->
( { emptyModel
| accessToken = ""
, activePage = Login
, config = model.config
}
, accessTokenPort ""
)
let
( modelUpdated, pusherLogoutCmd ) =
update (MsgPusher Pusher.Model.Logout) model
in
( { emptyModel
| accessToken = ""
, activePage = Login
, config = model.config
, pusher = modelUpdated.pusher
}
, Cmd.batch
[ accessTokenPort ""
, pusherLogoutCmd
]
)

MsgItemManager subMsg ->
case model.user of
Expand Down Expand Up @@ -124,15 +126,31 @@ update msg model =
-- If we don't have a user, we have nothing to do.
model ! []

MsgPusher subMsg ->
let
( val, cmd ) =
Pusher.Update.update backendUrl subMsg model.pusher
in
( { model | pusher = val }
, Cmd.map MsgPusher cmd
)

NoOp ->
model ! []

PageLogin msg ->
let
( val, cmds, ( webDataUser, accessToken ) ) =
Pages.Login.Update.update backendUrl msg model.pageLogin

( pusherModelUpdated, pusherLoginCmd ) =
pusherLogin model webDataUser accessToken

modelUpdated =
{ model
| pageLogin = val
, accessToken = accessToken
, pusher = pusherModelUpdated
, user = webDataUser
}

Expand Down Expand Up @@ -166,6 +184,7 @@ update msg model =
[ Cmd.map PageLogin cmds
, accessTokenPort accessToken
, setActivePageCmds
, pusherLoginCmd
]
)

Expand Down Expand Up @@ -240,6 +259,7 @@ subscriptions model =
[ Sub.map MsgItemManager <| ItemManager.Update.subscriptions model.pageItem model.activePage
, Time.every minute Tick
, offline (decodeValue bool >> HandleOfflineEvent)
, Sub.map MsgPusher <| Pusher.Update.subscription
]


Expand All @@ -248,11 +268,41 @@ subscriptions model =
port accessTokenPort : String -> Cmd msg


{-| Send Pusher key and cluster to JS.
{-| Get a singal if internet connection is lost.
-}
port pusherKey : ( String, String, List String ) -> Cmd msg
port offline : (Value -> msg) -> Sub msg


{-| Get a singal if internet connection is lost.
{-| Login to pusher.
Either subscribes to the private channel, or to the general channel, according
to user.pusherChannel.
-}
port offline : (Value -> msg) -> Sub msg
pusherLogin : Model -> WebData User -> String -> ( Pusher.Model.Model, Cmd Msg )
pusherLogin model webDataUser accessToken =
let
-- Create the pusher login Msg, wrapped as a MsgPusher.
pusherLoginMsg pusherKey pusherChannel =
MsgPusher <|
Pusher.Model.Login
pusherKey
pusherChannel
(Pusher.Model.AccessToken accessToken)

-- Create a MsgPusher for login, or NoOp in case the user or the config
-- are missing.
msg =
RemoteData.toMaybe webDataUser
|> Maybe.map
(\user ->
RemoteData.toMaybe model.config
|> Maybe.map (\config -> pusherLoginMsg config.pusherKey user.pusherChannel)
|> Maybe.withDefault NoOp
)
|> Maybe.withDefault NoOp

( updatedModel, pusherLoginCmd ) =
update msg model
in
-- Return the pusher part of the model, as the pusher login action
-- shouldn't change other parts.
( updatedModel.pusher, pusherLoginCmd )
1 change: 1 addition & 0 deletions client/src/elm/Item/Decoder.elm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ decodeItem =
decode Item
|> required "label" string
|> optionalAt [ "image", "styles", "large" ] string "http://placehold.it/350x150"
|> optional "private_note" (nullable string) Nothing


decodeItemsDict : Decoder ItemsDict
Expand Down
1 change: 1 addition & 0 deletions client/src/elm/Item/Model.elm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type alias ItemId =
type alias Item =
{ name : String
, image : String
, privateNote : Maybe String
}


Expand Down
43 changes: 25 additions & 18 deletions client/src/elm/Pages/Item/View.elm
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,34 @@ import Html.Attributes exposing (..)
import Pages.Item.Model exposing (Msg(..))
import Item.Model exposing (ItemId, Item)
import User.Model exposing (User)
import Utils.Html exposing (divider, showMaybe)


view : Date -> User -> ItemId -> Item -> Html Msg
view currentDate currentUser itemId item =
div []
[ div
[ class "ui secondary pointing fluid menu" ]
[ h2
[ class "ui header" ]
[ text item.name ]
, div
[ class "right menu" ]
[ a
[ class "ui active item" ]
[ text "Overview" ]
let
privateNote =
showMaybe <|
Maybe.map
(\note -> div [ class "private-note" ] [ text note ])
item.privateNote
in
div []
[ div
[ class "ui secondary pointing fluid menu" ]
[ h2
[ class "ui header" ]
[ text item.name ]
, div
[ class "right menu" ]
[ a
[ class "ui active item" ]
[ text "Overview" ]
]
]
, div []
[ img [ src item.image, alt item.name ] []
]
, divider
, privateNote
]
, div []
[ img [ src item.image, alt item.name ] []
]
, div
[ class "ui divider" ]
[]
]
7 changes: 0 additions & 7 deletions client/src/elm/Pages/Login/Decoder.elm
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@ module Pages.Login.Decoder exposing (..)
import Base64 exposing (encode)
import Json.Decode as Decode
import Pages.Login.Model exposing (AccessToken)
import User.Decoder as UserDecoder exposing (decodeUser)
import User.Model exposing (User)


decodeUser : Decode.Decoder User
decodeUser =
Decode.at [ "data", "0" ] <| UserDecoder.decodeUser


decodeAccessToken : Decode.Decoder AccessToken
Expand Down
1 change: 1 addition & 0 deletions client/src/elm/Pages/Login/Update.elm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import User.Model exposing (..)
import Pages.Login.Model as Login exposing (..)
import Pages.Login.Decoder exposing (..)
import RemoteData exposing (RemoteData(..), WebData)
import User.Decoder exposing (decodeUser)
import Utils.WebData exposing (sendWithHandler)


Expand Down
53 changes: 53 additions & 0 deletions client/src/elm/Pusher/Model.elm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ module Pusher.Model exposing (..)
import Item.Model exposing (Item, ItemId)


type alias Model =
{ connectionStatus : ConnectionStatus
, errors : List PusherError
}


emptyModel : Model
emptyModel =
{ connectionStatus = Initialized
, errors = []
}


type Cluster
= ApSouthEast1
| EuWest1
Expand All @@ -25,8 +38,48 @@ type PusherEventData
= ItemUpdate Item


type AccessToken
= AccessToken String


type alias PusherChannel =
String


type alias PusherConfig =
{ key : String
, cluster : String
, authEndpoint : String
, channel : String
, eventNames : List String
}


type ConnectionStatus
= Initialized
| Connecting (Maybe Int)
| Connected
| Unavailable (Maybe Int)
| Failed
| Disconnected
| Other String


type alias PusherError =
{ code : Maybe Int
, message : Maybe String
}


{-| Return the event names that should be added via JS.
-}
eventNames : List String
eventNames =
[ "item__update" ]


type Msg
= HandleError PusherError
| HandleStateChange String
| Login PusherAppKey PusherChannel AccessToken
| Logout
1 change: 1 addition & 0 deletions client/src/elm/Pusher/Test.elm
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ decodeTest =
, data =
{ name = "new-item"
, image = "http://placehold.it/350x150"
, privateNote = Nothing
}
|> ItemUpdate
}
Expand Down
Loading

0 comments on commit 4070f09

Please sign in to comment.