Crowdhailer
I am developing a library that unusually has a significant client part. In summary GenBrowser aims to give clients/browser an identifier that can be used to send messages to/from/between clients. In essence a pid.
For example
# browser 1
const { address, mailbox, send } = await GenBrowser.start('http://gen_browser.dev')
console.log(address)
# server
iex> {:ok, client} = GenBrowser.decode_address(address)
iex> GenBrowser.send(client, %{text: "From the server"})
# browser 2
send(address, {text: 'From browser 2'})
# browser 1
var message1 = await mailbox.receive({timeout: 2000})
message1.text
# From the server
var message2 = await mailbox.receive({timeout: 2000})
message2.text
# From browser 2
The security model relies on signing addresses that are sent out of the server, that is why the signed address needs decoding on the server.
I am not very up to date on the front end world and so want some advice on how to proceed with this project.
Ideally I want to keep the whole thing in one project and use as much of the Elixir ecosystem as possible. However that might be limiting.
Advice on the JavaScript API
There are two options for working with messages received.
mailbox.receive()that takes an optional timeout an returns a promise that completes on the next messagemailbox.setHandler(messageCallback, closeCallback)The first callback is called whenever a message is received, the second when the mailbox has been closed permanently.
These names api’s come from their erlang world equivalent receive and handle_*. They look reasonably sensible in the JavaScript world but could probably be more idiomatic
Npm publishing
The project has a JavaScript build step and the code is always to be used in a client.
- Would you expect this to be available on npm as well as a CDN
- If so should only the source (or only the bundle) be published to npm
JS Documentation
ExDoc has spoiled me for ease of setting up documentation. In these cases I am probably just looking for the most standard/simple way of doing things
- What is the recommended way to document a JavaScript library?
- Is there a way to integrate this documentation into the hex documentation?
Redux as Actors
With my (limited) knowledge of redux I think that redux and GenServers look quite similar.
Hence the name of this project.
Do you think this is a helpful analogy when describing processes to JavaScript developers.
Within a single process or store there is a single state tree.
I often say that sending a message is like dispatching on a remote store.
Is there any better way of explaining things? Should I just say actor model and not confuse the issue with mentioning redux
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #elixirconf-us
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
LostKobrakai
You could take a look at how phoenix handles their js dependency.
As for the API:
I’d expect a callback driven interface. Promises are better for async results for a single action/task.
peerreynders
Given the nature of a
mailboxas a potential source of an infinite stream of messages (events) a more contemporary interface like Observable seems more appropriate (active implementations RxJS, xstream, Bacon.js, Kefir) - Learning Observable By Building Observable.While I realize that
awaitis a popular construct to make asynchronous code look more sequential, I believe that this is ultimately barking up the wrong tree given the mercilessly single-threaded nature of JavaScript runtimes.On the BEAM strictly sequential code makes sense given that you can have millions of tiny processes for concurrency. Pretending on a single threaded platform that sequential flow of control programming is sufficient for more complex interactions will in my opinion ultimately run into a brick wall.
The real answer is to start writing code that composes event streams and let the underlying platform schedule what gets processed when.
Can you please expand on this line of thinking - i.e. what has lead you to this conclusion?
namelos
Redux is more like Agent since most of the effectful operations are strongly discouraged.
Most of the effects are handled with redux-saga or thunk. Redux-saga is a little bit like a worker, but there’s no mailbox.
The key difference is Redux simplified everything by dispatching the event to every possible listener, so there’s no such thing like pid at all. And it’s acceptable because in front-end we usually don’t have too much data.
And also all redux operation happens in a synchronous manner so there’s no need for a mailbox.
Crowdhailer
Yeah so far I have used both. something listening for pings using the callback approach to send pongs.
But on the other side after sending a single ping the promise makes sense because it is waiting for a single pong
I though that this could all be built on top of a callback interface
I thought when using
awaitit was essentially syntactic sugar for the same behaviour as a promise. i.e. other callbacks etc would work as normalpeerreynders
For efficient streaming you need three callback functions (RxJS 6).
next(value:T) => voiderror(error:any) => voidcomplete() => void(i.e. when the other end chooses to close the stream for a non-error reason).It is but (apart that I find
async/awaita bit of a tarpit with regards to refactoring) once you start using Observables, Promises come across as one-shot streams (when they resolve they deliver a single value and complete) and theasync/awaitsyntactic sugar becomes a “cul-de-sac” with nowhere to go when you need to move to streams.So the first thing I had to do was to strip all the
async/awaitout of the end user code before I could even think of introducing Observables/Streams.Crowdhailer
That can be done. I’m most interested in what would be a good foundational API, because I know there are 100 different flavours of how to JavaScripts and there is no reason for this project to favour one over the other.
Thanks for the Gist that’s awesome. Rather a lot to lead a README with but I think I could certainly update the actual examples.
peerreynders
Well, that wasn’t really my intention. Those were simply the files that I changed to stage the next phase - introducing RxJS 6. So the next set of changed files are here:
gen-browser Pinger/Ponger refactor part 2 - enter RxJS 6 · GitHub
Connect.jsis the module that I cobbled together in an attempt to wrap the current client API. It’s still rather simplistic given that only one single attempt is made to start a client.Thinking out aloud here for an improved version:
It’s in the nature of Observables that once an error occurs the Observable is done and junk. So if the client interface ever experiences an error it makes sense to trash it and start over. So it makes sense to model:
switchMapoperator can then be used transform that “stream of message streams” into a single “stream of messages”. In essence there should be a “stream of messages” that is entirely oblivious to failures of the client interfaces as long as fresh ones can be acquired.Some points:
There doesn’t currently seem to be a way to “close” (i.e. discard) the client interface. Closing the mailbox seems to simply call the registered close handler - meanwhile the underlying
EventSourcedoesn’t seem to be closed.There currently don’t seem to be any opportunities for errors after the client interface has started. That makes me wonder whether there are places where errors are being thrown and not being converted to error values - i.e. ultimately it’s important that there is an error handler and that all errors are channeled towards it. Then there is the classification of the errors. Do all errors mean that a new client interface needs to be acquired or are there other less severe errors?
The
reasonforPromise.rejectshould be anError, not just a string.MDN Promise.reject - Description:
i.e.
should be
etc.
Understood, my approach was to use one of the more sophisticated methods to see if there are any glaring shortcomings. At this point I’m wondering how difficult it would be to deal with the EventSource directly.
The base API would need to accept an error handler (there needs to be some notion of the desirable action after any particular error) and there needs to be a way to cleanly close/dispose of the interface regardless of errors.
It might be an idea to make a Promise based API a separate, optional
npmpackage (which uses the base callback API). That way it should be easier to bypass any unnecessary functionality.Crowdhailer
Not that difficult, it has a callback based API. So there is probably only limited value in using my layer that probably just obscures the underlying interface.
This is good point it is missing. So far my usecase has only been to discard the client when the browser page is closed, however I think it is something that is worth adding.
mailbox.closeis really only to be used by the client code when it looses connection.I only consider it an error when a reconnection fails, loosing a connection is just a natural occurance.
The setHander function does take two callbacks the second one being called on close
peerreynders
So here is what I came up with (eliminating the need for
mailbox.js,promiseTimeout.jsandclient.js):gen-browser Pinger/Ponger refactor part 3 - RxJS 6 on Top of EventSource · GitHub
In hindsight it would have been useful to have something like this:
for a no-nonsense piece of code clearly illuminating all aspects of the interface from the browser point of view.
I’m not sure that providing a definitive JS library is the way to go - a demonstration one (or two) sure. For maximum flexibility it is necessary to lay bare all the options made available via
fetchandEventSource.I don’t think you’re in the market to maintain an ultra-flexible (read ultra-complex and bloated) JS library that will accommodate all the numerous edge cases for connection options that people may want.
peerreynders
Just thinking out aloud:
The first message to the
EventSourceis “special”. Given that the EventSource is opened with a URL theopenevent is the first response but that is standardized. So the first message is the first opportunity to return client specific details.One thing I’m wondering is whether the hard requirement for a “first special message” may make it more difficult to use a more generic library built on top of EventSource with this server protocol.
An alternate means could be:
Place a regular fetch to
/mailbox. The response contains URLs forThen create the EventSource with the provided URL (and the first message doesn’t have to be special). A timeout against the
openevent can by used to ensure that the connection is established.Tradeoff: The additional fetch before creating the EventSource. But there is the added bonus that there only is one root URL
/mailbox- the URLs for sending, logging and creating theEventSourceare completely under the server’s control.