项目作者: syallop

项目描述 :
Chatkit bindings for Pusher-Platform-Haskell
高级语言: Haskell
项目地址: git://github.com/syallop/chatkit-haskell.git
创建时间: 2019-06-12T21:56:31Z
项目社区:https://github.com/syallop/chatkit-haskell

开源协议:MIT License

下载


Chatkit-Haskell

Chatkit is a hosted chat API that allows you to add one-to-one and group chat to your app, along with typing indicators, file attachments and storage, user online presence and a flexible permissions system.

This respository provides Haskell bindings to the Chatkit API that can be consumed by the pusher-platform-haskell client. It is not a server or client SDK (but could be used to build one).

Note: This package is unofficial and alpha quality.

Continue reading this document to learn how to install this dependency,
connect to an instance, make requests,
establish subscriptions and handle errors. Or
consult documentation.

Contents:

Examples

There are self-contained examples under /Examples which can be built with
stack build from within this repository.

The Server example shows how to create access tokens, users, rooms and send
messages from the server.

Installing

  1. Start a project

If you have an Existing application, skip to step 2 otherwise start a new
Haskell project. Stack is
known to work but you should be able to use other tools such as cabal.

With stack:

  1. mkdir MyApp && cd MyApp && stack init
  1. Add chatkit-haskell as a dependency to MyApp.Cabal file:

    1. ...
    2. build-depends: base >= 4.12
    3. , chatkit-haskell
    4. ...
  2. Tell your build tool where to find this dependency

chatkit-haskell is not currently on Hackage or in a stack resolver.

If using stack add this repository as a source to your stack.yaml:

  1. resolver: lts-14.20
  2. packages:
  3. - '.'
  4. extra-deps:
  5. - git: https://github.com/syallop/Chatkit-Haskell.git
  6. commit: master
  7. - git: https://github.com/syallop/Pusher-Platform-Haskell.git
  8. commit: master

If using cabal directly you may install this repository globally:

  1. git clone https://github.com/syallop/Chatkit-Haskell.git && cd Chatkit-Haskell && cabal install

Connecting to an instance

To interact with Chatkit you will need your instance and key. Obtain credentials
or create a free instance in the dashboard.

The “Instance Locator” displayed in the dashboard takes the form VERSION:CLUSTER:INSTANCEID.
The “Secret Key” takes the form KEYID:KEYSECRET.

Note:

  • The keySecret is the private key used to sign requests. It should NOT be
    shared with untrusted clients.
  • The instanceID identifies your instance and can be shared
  • The keyID identifies the secret key you will use to authorize requests and
    can be shared.

Create an environment that points at a single instance of Chatkit:

  1. import Pusher
  2. instanceID = "my-instance-id"
  3. keyID = "my-key-id"
  4. keySecret = error "Only supply key secret to trusted servers"
  5. clusterName = US1
  6. host = PusherPlatform
  7. main :: IO ()
  8. main = do
  9. Just env <- mkPusherEnv instanceID keyID clusterName host []
  10. ...

Use this environment to issue requests and establish subscriptions by using
runPusher like:

  1. main :: IO
  2. main = do
  3. Just env <- mkPusherEnv instanceID keyID clusterName host []
  4. result <- runPusher env pusherActions
  5. case result of
  6. PusherSuccess ()
  7. -> putStrLn "Successfully executed actions"
  8. PusherErrorResponse errResp
  9. -> putStrLn $ "Got error response from api: " <> show errResp
  10. PusherFailure errMsg
  11. -> fail $ show errMsg
  12. pusherActions :: Pusher ()
  13. pusherActions = do
  14. pusherIO $ putStrLn "Hello world"
  15. -- More Pusher actions can be chained here. Failures will shortcircuit.

AccessTokens

Most requests will require an AccessToken to be supplied for authorization. These are created
by signing a JWT with your SecretKey.

SecretKeys must never be given to untrusted clients as it would allow them to
authorize any request.

See the Authentication Flow docs for more
details.

If you are in an untrusted client context you cannot securely generate
AccessTokens. You should communicate with a trusted server context who is
responsible for deciding whether to grant you an appropriate AccessToken.

Quick start development

You may enable the “Test Token Provider” in the dashboard. Request
AccessTokens like:

  1. import Chatkit.Service.InsecureTokenProvider
  2. getTokenInsecurelyFor :: UserId -> Pusher AccessToken
  3. getTokenInsecurelyFor userId = do
  4. response <- pusherRequest (CreateToken userId) Nothing
  5. pure $ _accessToken response

Warning: It is important to disable this option for a production Chatkit instance as the
endpoint is unauthenticated meaning everybody has admin access to your instance.

Still-pretty-quick-start server-side development

If you are in a trusted server context, you may generate AccessTokens:

  1. import Data.Map.Strict
  2. import Data.Text
  3. import Data.Time.Clock.POSIX
  4. import Test.RandomStrings
  5. import qualified Data.Aeson as JSON
  6. import qualified Data.Map as Map
  7. import Pusher.Client.Token
  8. pusherActions :: Pusher ()
  9. pusherActions = do
  10. pusherIO $ putStrLn "Hello world"
  11. -- More Pusher actions can be chained here. Failures will shortcircuit.
  12. -- Create an access token for alice
  13. aliceUserID <- pusherIO $ generateUserID "alice"
  14. accessToken <- pusherIO $ createAccessTokenAtTrustedServer aliceUserID
  15. pure ()
  16. createAccessTokenAtTrustedServer :: Text -> IO (Maybe AccessToken)
  17. createAccessTokenAtTrustedServer forUser = do
  18. issuedAt <- getPOSIXTime
  19. let expiresAt = issuedAt + 60*60
  20. let subject = forUser
  21. let claims = Map.fromList [("su", JSON.Bool True)]
  22. pure $ mkAccessToken instanceID keyID keySecret issuedAt expiresAt subject claims
  23. generateUserID :: Text -> IO Text
  24. generateUserID name = do
  25. suffix <- randomWord randomASCII 10
  26. pure $ name <> "-" <> pack suffix

Warning: This token has no restrictions on permissions across the entire
instance.

Using

All API calls documented in the API docs have definitions under similarly named modules.

Feature API docs Chatkit-Haskell Module
Core (Rooms, Users, Messages) https://pusher.com/docs/chatkit/reference/latest Chatkit.Service.Core
Roles & Permissions (Roles, User Roles, Permissions) https://pusher.com/docs/chatkit/reference/roles-and-permissions Chatkit.Service.RolesAndPermissions
Scheduler (Asynchronous delete Users/ Rooms) https://pusher.com/docs/chatkit/reference/scheduler Chatkit.Service.Scheduler
Presence (Setting and subscribing to online state) https://pusher.com/docs/chatkit/reference/presence Chatkit.Service.Presence
Read Cursors (Setting, getting and subscribing to read cursors) https://pusher.com/docs/chatkit/reference/cursors Chatkit.Service.Cursors
Files (Upload, get and delete Files) https://pusher.com/docs/chatkit/reference/files-api Chatkit.Service.Files

Requests

Requests and Responses are first-class data structures that define:

  • How to talk to the Chatkit api
  • How to serialise and deserialise request and response bodies
  • How to interpolate query parameters

Requests can be fed to a pusherRequest function to produce a matching
response type in the Pusher context.

For example the create user
endpoint is found under Chatkit.Service.Core. It’s request type is defined
like:

  1. -- | POST /users
  2. data CreateUser = CreateUser
  3. { _userID :: UserID
  4. , _userName :: UserName
  5. , _avatarURL :: Maybe URL
  6. , _customData :: Maybe CustomData
  7. }

It’s response type looks like:

  1. -- | Response to CreateUser
  2. data CreateUserResponse = CreateUserResponse
  3. { _user :: User
  4. }

This can be passed to pusherRequest to return a CreateUserResponse.

Subscriptions

Subscriptions are primarily used for client-side development and deliver events
as they occur in real time. Examples include subscribing to:

  • Messages sent to a room
  • Changes in read cursors in a room
  • Changes in users online state

The Subscription format is documented in the api docs however this library intends to abstract most of the details.

Each subscription defined in the api-docs has a corresponding Subscribe*
data-structure which defines how to open the subscription as well as a *Event
data-structure which defines the type of events the subscription may return.

E.G. To print all new messages sent to a room:

  1. printRoomMessages :: RoomId -> Chatkit ()
  2. printRoomMessages roomID = do
  3. roomSubscription <- pusherSubscription (SubscribeRoom roomID Nothing) (Just token)
  4. printRoomMessages' roomSubscription
  5. where
  6. printRoomMessages' :: Subscription RoomEvent -> Pusher ()
  7. printRoomMessages' roomSubscription = do
  8. ev <- pusherReadEvent roomSubscription timeout
  9. putStrLn $ case ev of
  10. Right (NewMessage _timestamp message)
  11. -> "Received message: " <> show message
  12. _ -> ""
  13. printRoomMessages' roomSubscription

If a client stops caring for a subscription, they should call close on it.

Errors

Executing the Pusher type with runPusher returns a PusherResult.
Asides from success, the result may contain two classes of errors -
PusherFailures and PusherErrorResponses.

ErrorResponses

PusherErrorResponses indicate an ‘expected’ error returned by the Chatkit
API. The contained ErrorResponse will have a status code, a description of
the error and a link to documentation.

The type looks like:

  1. data ErrorResponse = ErrorResponse
  2. { -- ^ The status code mirrors HTTP. I.E. 4xx indicates the client made a bad
  3. -- request. 5xx indicates the server encountered some internal error.
  4. _errorStatusCode :: Int
  5. , _errorResponseBody :: ErrorResponseBody
  6. }
  7. -- | An Error 'successfully' returned from the API.
  8. -- The status code mirrors http - 4xx indicates the client made a bad request,
  9. -- 5xx indicates the server etc.
  10. data ErrorResponseBody = ErrorResponseBody
  11. {
  12. -- ^ A unique string identifying the specific type of error.
  13. _errorType :: Text
  14. -- ^ A longer description of the meaning of the error.
  15. , _errorDescription :: Maybe Text
  16. -- ^ A link to further documentation on the error.
  17. , _errorURI :: Text
  18. -- ^ Key-value pairs that are specific to the error type and may provide more
  19. -- detail as to what caused the error.
  20. , _errorAttributes :: Maybe (Map Text Value)
  21. }

The status codes and presence of a Retry-After header indicate whether a
request can be retried.

E.G.

  • 2xx could indicate a subscription has unexpected closed ‘successfully’ from
    the servers point of view. You may want to re-establish.
  • 429 with Retry-After header indicates you have hit a ratelimit and should
    retry after the specified period.
  • 4xx indicates the request is malformed in some way and likely cannot be
    retried without modifying some property indicated by the error type.
  • 5xx indicates the server is having an internal problem. A Retry-After header
    should be present and indicate how soon you should retry the request.

Failures

PusherFailures indicate a logic error in the libraries implementation or it’s
dependencies which should be reported. You may be able to retry these requests
but it is more likely the library is in an invalid state and should be
completely re-initialised.

For example, this could be caused by:

  • An internal error in the underlying HTTP2 client
  • A double close on a Subscription

Short-circuiting

By default, both types of error short-circuit a Pusher computation. I.E. in a
do block, if one chatkit request fails, it’s error will be immediately
returned and any remaining requests will not be issued. If you do NOT want this
behavior, pusherTry can be used to explicitly handle ErrorResponses.

Documentation

A local copy of the reference docs can be built and opened with:

  1. stack haddock --open

Note: This will build documentation for every transitive dependency and may take a long time on first run.

Developing this library

Build with:

  1. stack build

Supplied suitable envars, a test-suite can be ran against a Chatkit instance:

  1. stack test