@@ -1,28 +1,22 @@
1−module Ur.Run exposing (element, document, application, Program, Model, Msg)
2−
3−{-| This module contains functions to run your application with Urbit integration.
4−
5−@docs element, document, application, Program, Model, Msg
6−
7−-}
1+module Ur.Run exposing (Model, Msg, application)
82
93import Browser exposing (Document, UrlRequest)
104import Browser.Navigation as Nav
115import Dict exposing (Dict)
126import Either exposing (Either(..))
13−import Html exposing (Html)
7+import Html
148import Json.Decode as JD
159import Task
1610import Time
11+import Ur exposing (Agent, Path)
1712import Ur.Cmd
1813import Ur.Cmd.Internal
1914import Ur.Constructor as C
2015import Ur.Deconstructor as D
21−import Ur.NounDiff exposing (Patch, deconstructPatch)
16+import Ur.Phonemic exposing (Ship)
2217import Ur.Requests exposing (..)
2318import Ur.Sub
2419import Ur.Sub.Internal
25−import Ur.Types exposing (..)
2620import Ur.Uw
2721import Url exposing (Url)
2822
@@ -31,9 +25,8 @@ type alias SubDict msg =
3125 Dict
3226 -- (ship, agent, path)
3327 ( String, String, List String )
34− { deconstructor : D.Deconstructor msg
28+ { deconstructor : D.Deconstructor (msg -> msg) msg
3529 , number : Int
36− , sink : Bool
3730 }
3831
3932
@@ -41,8 +34,6 @@ type alias Flags =
4134 { uid : String }
4235
4336
44−{-| An Urbit wrapper around your application model.
45−-}
4637type alias Model app msg =
4738 { subscriptions : SubDict msg
4839 , subscriptionIntMapping : Dict Int ( String, String, List String )
@@ -51,12 +42,9 @@ type alias Model app msg =
5142 , eventId : Int
5243 , flags : Flags
5344 , requestsToRetry : List UrbitRequest
54− , sinks : Dict Int Noun
5545 }
5646
5747
58−{-| An Urbit wrapper around your application messages.
59−-}
6048type Msg msg
6149 = AppMsg msg
6250 | EventSourceMsg JD.Value
@@ -67,61 +55,6 @@ type Msg msg
6755 | RetryRequests
6856
6957
70−{-| A wrapper around your application `Program`.
71−-}
72−type alias Program model msg =
73− Platform.Program Flags (Model model msg) (Msg msg)
74−
75−
76−{-| The same as `Browser.element` but with urbit stuff added around it.
77−-}
78−element :
79− { init : ( model, Ur.Cmd.Cmd msg )
80− , view : model -> Html msg
81− , update : msg -> model -> ( model, Ur.Cmd.Cmd msg )
82− , subscriptions : model -> Sub msg
83− , urbitSubscriptions : model -> Ur.Sub.Sub msg
84− , createEventSource : String -> Cmd (Msg msg)
85− , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
86− , urbitUrl : model -> String
87− }
88− -> Program model msg
89−element inp =
90− Browser.element
91− { init = init inp inp.init
92− , view = \model -> inp.view model.app |> Html.map AppMsg
93− , update = update inp
94− , subscriptions = subscriptions inp
95− }
96−
97−
98−{-| The same as `Browser.element` but with urbit stuff added around it.
99−-}
100−document :
101− { init : ( model, Ur.Cmd.Cmd msg )
102− , view : model -> Document msg
103− , update : msg -> model -> ( model, Ur.Cmd.Cmd msg )
104− , subscriptions : model -> Sub msg
105− , urbitSubscriptions : model -> Ur.Sub.Sub msg
106− , createEventSource : String -> Cmd (Msg msg)
107− , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
108− , urbitUrl : model -> String
109− }
110− -> Program model msg
111−document inp =
112− Browser.document
113− { init = init inp inp.init
114− , view =
115− \model ->
116− inp.view model.app
117− |> (\{ body, title } -> { title = title, body = body |> List.map (Html.map AppMsg) })
118− , update = update inp
119− , subscriptions = subscriptions inp
120− }
121−
122−
123−{-| The same as `Browser.element` but with urbit stuff added around it.
124−-}
12558application :
12659 { init : Url -> Nav.Key -> ( model, Ur.Cmd.Cmd msg )
12760 , view : model -> Document msg
@@ -134,20 +67,66 @@ application :
13467 , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
13568 , urbitUrl : model -> String
13669 }
137− -> Program model msg
70+ -> Program Flags (Model model msg) (Msg msg)
13871application inp =
13972 let
140− { view, onUrlRequest, onUrlChange } =
73+ { init, view, onUrlRequest, onUrlChange, onEventSourceMsg } =
14174 inp
14275 in
14376 Browser.application
144− { init = \flags url key -> init inp (inp.init url key) flags
77+ { init =
78+ \flags u key ->
79+ let
80+ ( app, appCmds ) =
81+ init u key
82+
83+ { subscriptions, eventId, subscriptionRequests, subscriptionIntMapping } =
84+ processUrSubs
85+ 0
86+ Dict.empty
87+ (inp.urbitSubscriptions app |> (\(Ur.Sub.Internal.Sub x) -> x))
88+
89+ ( eventId_, cmds, urReqs ) =
90+ processCmd eventId appCmds
91+
92+ url =
93+ inp.urbitUrl app ++ "/~/channel/" ++ flags.uid
94+ in
95+ ( { subscriptions = subscriptions
96+ , subscriptionIntMapping = subscriptionIntMapping
97+ , app = app
98+ , connected = False
99+ , eventId = eventId_
100+ , flags = flags
101+ , requestsToRetry = []
102+ }
103+ , [ cmds
104+ , pureCmd NeedsActivation
105+ , send
106+ { requests = urReqs ++ subscriptionRequests
107+ , url = url
108+ , error = Noop
109+ , success = Noop
110+ }
111+ ]
112+ |> Cmd.batch
113+ )
145114 , view =
146115 \model ->
147116 view model.app
148117 |> (\{ body, title } -> { title = title, body = body |> List.map (Html.map AppMsg) })
149118 , update = update inp
150− , subscriptions = subscriptions inp
119+ , subscriptions =
120+ \model ->
121+ Sub.batch
122+ [ inp.subscriptions model.app |> Sub.map AppMsg
123+ , onEventSourceMsg EventSourceMsg
124+ , if List.isEmpty model.requestsToRetry then
125+ Sub.none
126+
127+ else
128+ Time.every 1000 (always RetryRequests)
129+ ]
151130 , onUrlRequest = \req -> onUrlRequest req |> AppMsg
152131 , onUrlChange = \url -> onUrlChange url |> AppMsg
153132 }
@@ -174,20 +153,20 @@ update inp msg model =
174153 ( appModel, appCmds ) =
175154 inp.update msg_ model.app
176155
177− subsResult =
156+ { subscriptions, eventId, subscriptionRequests, subscriptionIntMapping } =
178157 processUrSubs
179158 model.eventId
180159 model.subscriptions
181− (inp.urbitSubscriptions appModel |> (\(Ur.Sub.Internal.Sub x) -> x))
160+ (inp.urbitSubscriptions model.app |> (\(Ur.Sub.Internal.Sub x) -> x))
182161
183162 ( eventId_, cmds, urReqs ) =
184− processCmd subsResult.eventId appCmds
163+ processCmd eventId appCmds
185164 in
186165 ( { model
187166 | app = appModel
188167 , eventId = eventId_
189− , subscriptions = subsResult.subscriptions
190− , subscriptionIntMapping = model.subscriptionIntMapping |> Dict.union subsResult.subscriptionIntMapping
168+ , subscriptions = subscriptions
169+ , subscriptionIntMapping = model.subscriptionIntMapping |> Dict.union subscriptionIntMapping
191170 }
192171 , Cmd.batch
193172 [ cmds
@@ -198,9 +177,9 @@ update inp msg model =
198177 , requests = urReqs
199178 }
200179 , send
201− { requests = subsResult.subscriptionRequests
180+ { requests = subscriptionRequests
202181 , url = url
203− , error = subsResult.subscriptionRequests |> List.map (\( _, x ) -> x) |> FailedRequest
182+ , error = subscriptionRequests |> List.map (\( _, x ) -> x) |> FailedRequest
204183 , success = Noop
205184 }
206185 ]
@@ -211,10 +190,12 @@ update inp msg model =
211190 Ok string ->
212191 case
213192 D.runBytes
214− (D.cell D.int (D.cell D.cord D.tar))
193+ (D.cell D.int (D.cell D.cord D.tar)
194+ |> D.map (\a b c -> ( a, b, c ))
195+ )
215196 (Ur.Uw.decode string)
216197 of
217− Just ( messageId, ( messageType, rest ) ) ->
198+ Just ( messageId, messageType, rest ) ->
218199 let
219200 ( eventId, ackReqs ) =
220201 tag model.eventId [ Ack messageId ]
@@ -244,51 +225,14 @@ update inp msg model =
244225 Dict.get messageId model.subscriptionIntMapping
245226 |> Maybe.andThen (\key -> Dict.get key model.subscriptions)
246227 of
247− Just { deconstructor, sink } ->
248− if sink then
249− case D.run (D.cell D.ignore (D.cell D.ignore deconstructSink) |> D.map (\( (), ( (), s ) ) -> s)) rest of
250− Just (Flush noun) ->
251− ( { model | sinks = Dict.insert messageId noun model.sinks }
252− , case D.run deconstructor noun of
253− Just subMsg ->
254− pureCmd (AppMsg subMsg)
255−
256− -- Got garbage
257− Nothing ->
258− Cmd.none
259− )
260−
261− Just (Drain patch) ->
262− case Dict.get messageId model.sinks of
263− Just oldNoun ->
264− let
265− newNoun =
266− Ur.NounDiff.apply patch oldNoun
267− in
268− ( { model | sinks = Dict.insert messageId newNoun model.sinks }
269− , case D.run deconstructor newNoun of
270− Just subMsg ->
271− pureCmd (AppMsg subMsg)
272−
273− Nothing ->
274− Cmd.none
275− )
276−
277− Nothing ->
278− ( model, Cmd.none )
279−
280− -- Got garbage
281− Nothing ->
282− ( model, Cmd.none )
283−
284− else
285− case D.run (D.cell D.ignore (D.cell D.ignore deconstructor) |> D.map (\( (), ( (), m ) ) -> m)) rest of
286− Just subMsg ->
287− ( model_, pureCmd (AppMsg subMsg) )
288−
289− -- Got gargbage
290− Nothing ->
291− ( model_, ackCmd )
228+ Just { deconstructor } ->
229+ case D.run (D.cell D.ignore (D.cell D.ignore deconstructor)) rest of
230+ Just subMsg ->
231+ ( model_, pureCmd (AppMsg subMsg) )
232+
233+ -- Got gargbage
234+ Nothing ->
235+ ( model_, ackCmd )
292236
293237 -- Got a fact for a subscription we do not hold
294238 Nothing ->
@@ -313,11 +257,11 @@ update inp msg model =
313257 NeedsActivation ->
314258 let
315259 ( eventId, reqs ) =
316− [ Poke { ship = "", agent = "hood", mark = "helm-hi", noun = C.cord "Opening airlock!" } ]
260+ [ Poke { ship = "~zod", agent = "hood", mark = "helm-hi", noun = C.cord "Opening airlock!" } ]
317261 |> tag model.eventId
318262 in
319263 ( { model | eventId = eventId }
320− , send { url = url, requests = reqs, success = OpenConnection, error = Noop }
264+ , send { url = url, requests = reqs, success = OpenConnection, error = NeedsActivation }
321265 )
322266
323267 Noop ->
@@ -344,19 +288,6 @@ update inp msg model =
344288 )
345289
346290
347−type Sink
348− = Flush Noun
349− | Drain Patch
350−
351−
352−deconstructSink : D.Deconstructor Sink
353−deconstructSink =
354− D.oneOf
355− [ D.cell (D.const D.cord "flush") D.tar |> D.map (\( (), n ) -> Flush n)
356− , D.cell (D.const D.cord "drain") (D.lazy deconstructPatch) |> D.map (\( (), patch ) -> Drain patch)
357− ]
358−
359−
360291processCmd : EventId -> Ur.Cmd.Cmd msg -> ( EventId, Cmd (Msg msg), List ( EventId, UrbitRequest ) )
361292processCmd eventId urCmds =
362293 let
@@ -386,10 +317,10 @@ pureCmd msg =
386317
387318processUrSubs :
388319 EventId
389− -> Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
390− -> Dict ( Ship, Agent, Path ) { deconstructor : d, sink : Bool }
320+ -> Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId }
321+ -> Dict ( Ship, Agent, Path ) d
391322 ->
392− { subscriptions : Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
323+ { subscriptions : Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId }
393324 , eventId : EventId
394325 , subscriptionRequests : List ( EventId, UrbitRequest )
395326 , subscriptionIntMapping : Dict EventId ( Ship, Agent, Path )
@@ -398,7 +329,7 @@ processUrSubs eventId existingSubscriptions urbitSubs_ =
398329 let
399330 urbitSubs =
400331 urbitSubs_
401− |> Dict.map (\_ { deconstructor, sink } -> { deconstructor = deconstructor, sink = sink })
332+ |> Dict.map (\_ deconstructor -> { deconstructor = deconstructor })
402333
403334 ( eventId_, newSubscriptionActions ) =
404335 Dict.diff urbitSubs existingSubscriptions
@@ -421,11 +352,10 @@ processUrSubs eventId existingSubscriptions urbitSubs_ =
421352 newSubscriptions =
422353 Dict.merge
423354 (\_ _ x -> x)
424− (\key number { deconstructor, sink } ->
355+ (\key number { deconstructor } ->
425356 Dict.insert key
426357 { deconstructor = deconstructor
427358 , number = number
428− , sink = sink
429359 }
430360 )
431361 (\_ _ x -> x)
@@ -443,68 +373,3 @@ processUrSubs eventId existingSubscriptions urbitSubs_ =
443373 , subscriptionRequests =
444374 removedSubscriptionActions ++ (newSubscriptionActions |> List.map (\( id, ( req, _ ) ) -> ( id, req )))
445375 }
446−
447−
448−init :
449− { a | urbitSubscriptions : model -> Ur.Sub.Sub msg, urbitUrl : model -> String }
450− -> ( model, Ur.Cmd.Cmd msg )
451− -> Flags
452− ->
453− ( Model model msg
454− , Cmd (Msg msg)
455− )
456−init inp ( app, appCmds ) flags =
457− let
458− subsResult =
459− processUrSubs
460− 0
461− Dict.empty
462− (inp.urbitSubscriptions app |> (\(Ur.Sub.Internal.Sub x) -> x))
463−
464− ( eventId_, cmds, urReqs ) =
465− processCmd subsResult.eventId appCmds
466−
467− url =
468− inp.urbitUrl app ++ "/~/channel/" ++ flags.uid
469− in
470− ( { subscriptions = subsResult.subscriptions
471− , sinks = Dict.empty
472− , subscriptionIntMapping = subsResult.subscriptionIntMapping
473− , app = app
474− , connected = False
475− , eventId = eventId_
476− , flags = flags
477− , requestsToRetry = []
478− }
479− , [ cmds
480− , pureCmd NeedsActivation
481− , send
482− { requests = urReqs ++ subsResult.subscriptionRequests
483− , url = url
484− , error = Noop
485− , success = Noop
486− }
487− ]
488− |> Cmd.batch
489− )
490−
491−
492−subscriptions :
493− { a | subscriptions : b -> Sub msg, onEventSourceMsg : (JD.Value -> Msg c) -> Sub (Msg msg) }
494− -> { d | app : b, requestsToRetry : List e, connected : Bool }
495− -> Sub (Msg msg)
496−subscriptions inp model =
497− Sub.batch
498− [ inp.subscriptions model.app |> Sub.map AppMsg
499− , inp.onEventSourceMsg EventSourceMsg
500− , if List.isEmpty model.requestsToRetry then
501− Sub.none
502−
503− else
504− Time.every 1000 (always RetryRequests)
505− , if not model.connected then
506− Time.every 10000 (always NeedsActivation)
507−
508− else
509− Sub.none
510− ]