I am trying to use purescript-thermite to build an application using websockets. The idea is that the application connects to some server using websockets and live-updates the HTML page. However, I cannot find a way how to wire it into the thermite workflow.
I have a spec that is made up of render and performAction. The render has access to the dispatch function. However, I need to start the websockets before rendering the element (I could probably put it e.g. into main), but upon arrival of a message I need to ideally dispatch an event to the component from the outside. What is the best way to do that?
The expectation is that you would render your component, getting a handle to the driver function, then set up your websocket connection and use the driver function to provide updates.
However, if you need the websocket connection to be set up first for some reason, then you will need to employ some trickery, probably involving a Ref to hold the driver function once setup is complete. This way, you'd need to make manually verify that you don't try to call the driver function before the Ref is updated.
A more advanced solution could be to wrap your websocket protocol in the form of a coroutine (see purescript-coroutines), and to represent any setup phase explicitly in the types:
type Connection =
Aff MyEffects { initialData :: InitialData
, updateStream :: Producer (Aff MyEffects) UpdateMessage
}
Here, InitialData represents the data you receive at setup time, which might be passed to the component, and UpdateMessage represents your incremental updates from the server, which would be passed to the driver function. You could then wire all of this up in main.
I am not sure if it is not the 'right' way, but it works. In order to access the websocket connection I had to put it into the State. To initialize it I put it into the componentWillMount function. So the initialization looks like this:
type State = {
conn :: Maybe Connection
}
main :: Eff (...) Unit
main = do
let rspec = T.createReactSpec spec initialState
let component = React.createClass $ rspec.spec {componentWillMount=mountInit rspec.dispatcher}
mountInit :: forall props eff.
(ReactThis props State -> Action ->
Eff (... | eff) Unit)
-> ReactThis props State
-> Eff (... | eff) Unit
mountInit dispatch this = do
let handlers = {
connected: log "Service connected"
, disconnected: log "Disconnected"
, handle: \msg -> dispatch this (WebsockMsg msg)
}
conn <- createConnection "ws://localhost:3000/websock/webclient" handlers
void $ transformState this (\s -> s{conn= Just conn})
Related
I want to make http requests from my elm program.
I use the openapi-generator https://eriktim.github.io/openapi-elm for the http requests:
https://github.com/eriktim/openapi-elm,
The only example I could find is this:
https://github.com/eriktim/openapi-elm/tree/master/example
There, a request has e.g. type Api.Request Api.Data.PlanetList and is converted with the send function: (Result Http.Error a -> msg) -> Request a -> Cmd msg.
The send function takes a function to convert the Request result to msg but but returns it wrapped in Cmd.
The update function has type
update : Msg -> Model -> ( Model, Cmd Msg )
So as long as the request is made in the update function and the result is put in the return value the framework will get msg out of Cmd.
Now I want to make requests in my program, but I'm using playground game as my main function (example) where the update function is update : Computer -> Model -> Model so the "trick" from the example project is not applicable. How can I still get the values from my request call then?
A Http request is a piece of data for the runtime to execute. If the Cmd is not passed to the runtime through the main update, the actual http call will never happen.
This is why you cannot have side-effects in simple programs (Playground and Browser.sandbox).
I'm not really sure what elm-playground is, but that's not the right starting point for a webapp such as you want to create, as it does not support Commands, such as Http requests.
You want to be using normal / standard Elm - see https://guide.elm-lang.org/install/elm.html and then you want to be building a Program based on Browser.document - https://guide.elm-lang.org/webapps/
Hope that gets you on your way
I am trying to make some conditional routes. The condition resolves on the serverside.
Route rule example:
| (dynamicRouteCT("#user" / long.caseClass[User]) ~> dynRender((page: User) => <.div("Hello, " + page.id.toString)))
.addCondition((page: User) => checkPermissions(page.id))(_ => Some(redirectToPage(Page403)(Redirect.Push)))
checkpermissions body:
def checkPermissions(id: Long) = CallbackTo.future{
/*Ajax.get(s"http://some.uri/?id=$id") map (res =>
* if (something) true
* else false
* )
*/
//the request before returns Future[XMLHttprequest] witch maps to Future[Boolean]
Future(false)
}
I got type missmatch here: (page: User) => checkPermissions(page.id)
Is it possible to perform ajax request inside conditional routes?
If we look at def addCondition(cond: Page => CallbackTo[Boolean])(condUnmet: Page => Option[Action[Page]]): Rule[Page] we can see that it requires a CallbackTo[Boolean]. Because of the nature of the JS env, there is now way to go from Future[A] to A. Although it's not a limitation from scalajs-react itself, it is an inherited reality that will affect your scalajs-react code; as this table in the doc shows, there's no way to go from a CallbackTo[Future[Boolean]] to a CallbackTo[Boolean].
This type-level restriction is actually a really good thing for user experience. The router is synchronous, it must determine how to render routes and route changes immediately. If it were allowed to be async and somehow supported Futures, then the user would experience noticable (and potentially huge) delays without any kind of visual feedback or means of interruption.
The "right way" to solve this problem is to use a model that covers the async state. This is what I would do:
Create an AsyncState[E, A] ADT with cases: Empty, AwaitingResponse, Loaded(value: A), Failed(error: E).(You can enrich these further if desired, eg. loadTime on Loaded, retry callback on Failed, timeStarted on AwaitingResponse, etc.)
Have an instance of AsyncState[Boolean] in your (local/client-side) state.
Optionally kick-off an async load on page startup.
Have the router pass its value to a component and/or check the value of this.(The router won't know the value because it's dynamic, use Callback in a for-comprehension to wire things up and satisfy the types.)
Depending on the value of AsyncState[Boolean], render something meaningful to the user. If it's AwaitingResponse, display a little spinner; if it's failed display an error and probably a retry button.
(It should also be noted that AsyncState[Boolean] shouldn't actually be Boolean as that's not very descriptive or true to the domain. It would probably be something more meaningful like AsyncState[UserAccess] or something like that.)
Hope that helps! Good luck!
In my project, I created UserRepositoryActor which create their own router with 10 UserRepositoryWorkerActor instances as routee, see hierarchy below:
As you see, if any error occur while fetching data from database, it will occur at worker.
Once I want to fetch user from database, I send message to UserRepositoryActor with this command:
val resultFuture = userRepository ? FindUserById(1)
and I set 10 seconds for timeout.
In case of network connection has problem, UserRepositoryWorkerActor immediately get ConnectionException from underlying database driver and then (what I think) router will restart current worker and send FindUserById(1) command to other worker that available and resultFuture will get AskTimeoutException after 10 seconds passed. Then some time later, once connection back to normal, UserRepositoryWorkerActor successfully fetch data from database and then try to send result back to the caller and found that resultFuture was timed out.
I want to propagate error from UserRepositoryWorkerActor up to the caller immediately after exception occur, so that will prevent resultFuture to wait for 10 seconds and stop UserRepositoryWorkerActor to try to fetch data again and again.
How can I do that?
By the way, if you have any suggestions to my current design, please suggest me. I'm very new to Akka.
Your assumption about Router resending the message is wrong. Router has already passed the message to routee and it doesnt have it any more.
As far as ConnectionException is concerned, you could wrap in a scala.util.Try and send response to sender(). Something like,
Try(SomeDAO.getSomeObjectById(id)) match {
case Success(s) => sender() ! s
case Failure(e) => sender() ! e
}
You design looks correct. Having a router allows you to distribute work and also to limit number of concurrent workers accessing the database.
Option 1
You can make your router watch its children and act accordingly when they are terminated. For example (taken from here):
import akka.routing.{ ActorRefRoutee, RoundRobinRoutingLogic, Router }
class Master extends Actor {
var router = {
val routees = Vector.fill(5) {
val r = context.actorOf(Props[Worker])
context watch r
ActorRefRoutee(r)
}
Router(RoundRobinRoutingLogic(), routees)
}
def receive = {
case w: Work =>
router.route(w, sender())
case Terminated(a) =>
router = router.removeRoutee(a)
val r = context.actorOf(Props[Worker])
context watch r
router = router.addRoutee(r)
}
}
In your case you can send some sort of a failed message from the repository actor to the client. Repository actor can maintain a map of worker ref to request id to know which request failed when worker terminates. It can also record the time between the start of the request and actor termination to decide whether it's worth retrying it with another worker.
Option 2
Simply catch all non-fatal exceptions in your worker actor and reply with appropriate success/failed messages. This is much simpler but you might still want to restart the worker to make sure it's in a good state.
p.s. Router will not restart failed workers, neither it will try to resend messages to them by default. You can take a look at supervisor strategy and Option 1 above on how to achieve that.
I have a ServiceWorker registered on my page and want to pass some data to it so it can be stored in an IndexedDB and used later for network requests (it's an access token).
Is the correct thing just to use network requests and catch them on the SW side using fetch, or is there something more clever?
Note for future readers wondering similar things to me:
Setting properties on the SW registration object, e.g. setting self.registration.foo to a function within the service worker and doing the following in the page:
navigator.serviceWorker.getRegistration().then(function(reg) { reg.foo; })
Results in TypeError: reg.foo is not a function. I presume this is something to do with the lifecycle of a ServiceWorker meaning you can't modify it and expect those modification to be accessible in the future, so any interface with a SW likely has to be postMessage style, so perhaps just using fetch is the best way to go...?
So it turns out that you can't actually call a method within a SW from your app (due to lifecycle issues), so you have to use a postMessage API to pass serialized JSON messages around (so no passing callbacks etc).
You can send a message to the controlling SW with the following app code:
navigator.serviceWorker.controller.postMessage({'hello': 'world'})
Combined with the following in the SW code:
self.addEventListener('message', function (evt) {
console.log('postMessage received', evt.data);
})
Which results in the following in my SW's console:
postMessage received Object {hello: "world"}
So by passing in a message (JS object) which indicates the function and arguments I want to call my event listener can receive it and call the right function in the SW. To return a result to the app code you will need to also pass a port of a MessageChannel in to the SW and then respond via postMessage, for example in the app you'd create and send over a MessageChannel with the data:
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
console.log(event.data);
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
and then you can respond via it in your Service Worker within the message handler:
evt.ports[0].postMessage({'hello': 'world'});
To pass data to your service worker, the above mentioned is a good way. But in case, if someone is still having a hard time implementing that, there is an other hack around for that,
1 - append your data to get parameter while you load service-worker (for eg., from sw.js -> sw.js?a=x&b=y&c=z)
2- Now in service worker, fetch those data using self.self.location.search.
Note, this will be beneficial only if the data you pass do not change for a particular client very often, other wise it will keep changing the loading url of service worker for that particular client and every time the client reloads or revisits, new service worker is installed.
I'm looking for the proper way to use play's Enumerator (play.api.libs.iteratee.Enumerator[A]) in my code, i have a stream of object of type "InfoBlock" and i want to redirect it to a websocket.What i actually do is:
The data structure holding the blocks
private lazy val buf:mutable.Queue[InfoBlock] = new mutable.SynchronizedQueue[InfoBlock]
The callback to be used in the Enumerator
def getCallback: Future[Option[InfoBlock]] = Future{
if (!buf.isEmpty)
Some(buf.dequeue)
else
None}
Block are produced by another thread and added to the queue using:
buf += new InfoBlock(...)
Then in the controller i want to set up a websocket to stream that data,doing:
def stream = WebSocket.using[String]{ request =>
val in = Iteratee.consume[String]()
val enu:Enumerator[InfoBlock] = Enumerator.fromCallback1(
isFirst => extractor.getCallback
)
val out:Enumerator[String] = enu &> Enumeratee.map(blk => blk.author+" -> "+blk.msg)
(in,out)}
It works but with a big problem, when a connection is open it sends a bunch of blocks (=~ 50) and stops, if i open a new websocket then i get another bunch of blocks but no more.I tried to set some property to the js object WebSocket in particular i tried setting
websocket.binaryType = "arraybuffer"
because i thought using "blob" may be the cause but i was wrong the problem must be server side and i have no clue..
From the Enumerator ScalaDocs on Enumerator.fromCallback, describing the retriever function:
The input function. Returns a future eventually redeemed with Some value if there is input to pass, or a future eventually redeemed with None if the end of the stream has been reached.
This means that the enumerator will start by pulling everything off the queue. When it is empty, the callback will return a None. The enumerator sees this as the end of the stream, and closes sends a Done state downstream. It won't be looking for any more data
Rather than using a mutable queue for message passing, try and push the Enumerator/Iteratee paradigm into your worker. Create an Enumerator that outputs instances of the what you're creating, and have the iteratee pull from that instead. You can stick some enumerates in the middle to do some transforms if you need to.