@@ -38,6 +38,35 @@ import UnliftIO.Async (race)
3838import UnliftIO.Concurrent (threadDelay )
3939import UnliftIO.Exception (catch , try )
4040
41+ {- | A handler takes a websocket connection and the current model and then watches
42+ for websocket messages. It must return a new route to watch (after that, the
43+ returned route's HTML will be sent back to the client).
44+
45+ Note that this is usually a long-running thread that waits for the client's
46+ messages. But you can also use it to implement custom server actions, by handling
47+ the incoming websocket messages or other IO events in any way you like.
48+
49+ Also note that whenever the model is updated, the handler action will be
50+ stopped and then restarted with the new model as argument.
51+ -}
52+ type EmaWsHandler r = WS. Connection -> RouteModel r -> LoggingT IO Text
53+
54+ data EmaServerOptions r = EmaServerOptions
55+ { emaServerShim :: LByteString
56+ , emaServerWsHandler :: EmaWsHandler r
57+ }
58+
59+ defaultEmaWsHandler :: forall r . EmaWsHandler r
60+ defaultEmaWsHandler conn _model = do
61+ msg :: Text <- liftIO $ WS. receiveData conn
62+ log LevelDebug $ " <~~ " <> show msg
63+ pure msg
64+ where
65+ log lvl (t :: Text ) = logWithoutLoc " ema.ws" lvl t
66+
67+ defaultEmaServerOptions :: forall r . EmaServerOptions r
68+ defaultEmaServerOptions = EmaServerOptions wsClientJS (defaultEmaWsHandler @ r )
69+
4170runServerWithWebSocketHotReload ::
4271 forall r m .
4372 ( Show r
@@ -48,11 +77,12 @@ runServerWithWebSocketHotReload ::
4877 , IsRoute r
4978 , EmaStaticSite r
5079 ) =>
80+ EmaServerOptions r ->
5181 Host ->
5282 Maybe Port ->
5383 LVar (RouteModel r ) ->
5484 m ()
55- runServerWithWebSocketHotReload host mport model = do
85+ runServerWithWebSocketHotReload opts host mport model = do
5686 logger <- askLoggerIO
5787 let runM = flip runLoggingT logger
5888 settings =
@@ -90,10 +120,7 @@ runServerWithWebSocketHotReload host mport model = do
90120 let log lvl (s :: Text ) =
91121 logWithoutLoc (toText @ String $ printf " ema.ws.%.2d" subId) lvl s
92122 log LevelInfo " Connected"
93- let askClientForRoute = do
94- msg :: Text <- liftIO $ WS. receiveData conn
95- log LevelDebug $ " <~~ " <> show msg
96- pure msg
123+ let wsHandler = emaServerWsHandler opts conn
97124 sendRouteHtmlToClient path s = do
98125 decodeUrlRoute s path & \ case
99126 Left err -> do
@@ -113,10 +140,11 @@ runServerWithWebSocketHotReload host mport model = do
113140 liftIO $ WS. sendTextData conn $ " REDIRECT " <> toText (review (fromPrism_ $ enc s) r)
114141 log LevelDebug $ " ~~> " <> show r
115142 -- @mWatchingRoute@ is the route currently being watched.
116- loop mWatchingRoute =
143+ loop mWatchingRoute = do
117144 -- Listen *until* either we get a new value, or the client requests
118145 -- to switch to a new route.
119- race (LVar. listenNext model subId) askClientForRoute >>= \ case
146+ currentModel <- LVar. get model
147+ race (LVar. listenNext model subId) (wsHandler currentModel) >>= \ case
120148 Left newModel -> do
121149 -- The page the user is currently viewing has changed. Send
122150 -- the new HTML to them.
@@ -130,7 +158,7 @@ runServerWithWebSocketHotReload host mport model = do
130158 sendRouteHtmlToClient mNextRoute =<< LVar. get model
131159 loop mNextRoute
132160 -- Wait for the client to send the first request with the initial route.
133- mInitialRoute <- askClientForRoute
161+ mInitialRoute <- wsHandler =<< LVar. get model
134162 try (loop mInitialRoute) >>= \ case
135163 Right () -> pass
136164 Left (connExc :: ConnectionException ) -> do
@@ -150,18 +178,18 @@ runServerWithWebSocketHotReload host mport model = do
150178 case mr of
151179 Left err -> do
152180 logErrorNS " App" $ badRouteEncodingMsg err
153- let s = emaErrorHtmlResponse (badRouteEncodingMsg err) <> wsClientJS
181+ let s = emaErrorHtmlResponse (badRouteEncodingMsg err) <> emaServerShim opts
154182 liftIO $ f $ Wai. responseLBS H. status500 [(H. hContentType, " text/html" )] s
155183 Right Nothing -> do
156- let s = emaErrorHtmlResponse decodeRouteNothingMsg <> wsClientJS
184+ let s = emaErrorHtmlResponse decodeRouteNothingMsg <> emaServerShim opts
157185 liftIO $ f $ Wai. responseLBS H. status404 [(H. hContentType, " text/html" )] s
158186 Right (Just r) -> do
159187 renderCatchingErrors val r >>= \ case
160188 AssetStatic staticPath -> do
161189 let mimeType = Static. getMimeType staticPath
162190 liftIO $ f $ Wai. responseFile H. status200 [(H. hContentType, mimeType)] staticPath Nothing
163191 AssetGenerated Html html -> do
164- let s = html <> toLazy wsClientHtml <> wsClientJS
192+ let s = html <> toLazy wsClientHtml <> emaServerShim opts
165193 liftIO $ f $ Wai. responseLBS H. status200 [(H. hContentType, " text/html" )] s
166194 AssetGenerated Other s -> do
167195 let mimeType = Static. getMimeType $ review (fromPrism_ $ enc val) r
0 commit comments