Giter Site home page Giter Site logo

elmish.bridge's People

Contributors

0x53a avatar alkasai avatar cotyar avatar dependabot[bot] avatar diegoaltb avatar lukaszkrzywizna avatar nhowka avatar olivercoad avatar zaid-ajaj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elmish.bridge's Issues

BroadcastServer doesn't work

I tried broadcasting message using BroadcastServer, but it is not being received on the server.
Looking at the code here

        default sh.BroadcastServer(msg: 'inner) =
        sh.SendClientIf (fun _ -> true) msg

shouldn't that be SendServerIf instead?

        default sh.BroadcastServer(msg: 'inner) =
        sh.SendServerIf (fun _ -> true) msg

I'm using Elmish.Bridge.Server version 6.0.0

Getting all the messages into the Shared isn't the best option for a big project

(More like a discussion topic.)

Getting all the messages into the Shared isn't the best option for a big project.
The reason is too tight dependency on ClientMsg-s server side which for a UI reach project turns in necessity of including all pure UI messages (e.g. button pressed on say login screen) into the Shared project and making them visible on server. With corresponding models.
All this creates a bit of mess in the separation of concerns. Especially as UIs tend to grow in size quite fast comparing to the communication layer.
Really server shouldn't know about (and shouldn't depend on) pure UI things and on the UI side extracting server communication into a separate layer would be a bit cleaner as well.

So the proposal is to have in Shared something like this:

module WsBridge =
// Messages processed on the server
type ServerMsg =
| Closed
| ConnectUser of AuthToken
| DisconnectUser
//Messages processed on the client
type BridgeMsg =
| BS of BridgeServerMsg
| BC of BridgeClientMsg
and BridgeServerMsg =
| ConnectUserOnServer of AuthToken
| DisconnectUserOnServer
and BridgeClientMsg =
| ErrorResponse of ServerError * Request: ServerMsg
| ConnectionLost
| ServerConnected
| UserConnected of AuthToken

And in Client remapped BridgeMsg to ClientMsg with a separate pair of init()/update() functions (where AppMsg is visible in the Client project only and holds whole complex UI logic):
type ClientMsg =
| BridgeMsg of WsBridge.BridgeMsg
| AppMsg of AppMsg

type BridgedMsg = Msg<WsBridge.ServerMsg, ClientMsg>

I've seen your discussion with @Zaid-Ajaj and do support you in emphasising that Remoting and Bridge can coexist within the same project. For at least a couple of reasons:

  • rpc flow is simply easier for the client-started communication
  • it maybe difficult to migrate existing rpc into the message-passing workflow overnight.
    Literally the approaches pursue different use cases and should easily coexist.

I've got a working implementation but it's based on previous version of Remoting (the CE one - should be an issue so) and I'm not fully happy with one small arch decision (clearly need your advise on this one).
Will clean up the code and share this bit in a gist within the next couple of days.

Cheers
Yuriy

Handling WebSocket Open Events

Hi @Nhowka,

Thank you for providing this great library to the community.

In a recent project I had to update the Client/Library.fs file to include a withWhenOpened function so that I could listen for WebSocket open events.

Is this a feature you think should be included in your library?
If so, would you like for me to submit a pull-request with this feature added?

Provide an example of Bridge.withSubscription usage

Could you please provide an example of Bridge.withSubscription?
As I understand, it accepts a function 'model -> Cmd<'a>, like this:

Bridge.withSubscription (fun model -> Cmd.none)

But I'm puzzled how to emit command via this API.

What I need is to emit messages to server Bridge, from other asynchronous things, like MBP or Akka actors. Is withSubscription is the right tool for this?

Would reply channels create memory leaks if they are dropped?

I am thinking about using them, but it occurred to me that if I drop them there is no way the garbage collector will know that since it would be on a different machine. I was wondering if you've made sure to implement the finalizers so that they close out the side on the other machine? The connection being closed should also cause a disposal as well.

Error in Saturn instructions?

In this section

open Elmish
open Elmish.Bridge

let server =
  Bridge.mkServer Shared.endpoint init update
  |> Bridge.run Giraffe.server

let webApp =    // <---- ??? not referenced
  choose [
    server // <---- referenced twice?
    route "/" >=> htmlFile "/pages/index.html" ]

let app =
  application {
    router server   // <---- ??? use_router?  server referenced twice?
    disable_diagnostics
    app_config Giraffe.useWebSockets
    url uri
  }

run app

Should that read instead use_router webApp ?

Object reference not set to an instance of an object

I receive the following error when I push a lot of messages in multiple threads. Any guidance what it could mean? It works fine for few background threads and then it dies

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Elmish.RingBuffer`1.Pop()
   at [email protected](msg msg)
   at [email protected](msg _arg1)
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, b result1, FSharpFunc`2 userCode) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 465
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104
--- End of stack trace from previous location ---
   at [email protected](ExceptionDispatchInfo edi) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 1078
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104
   at <StartupCode$FSharp-Core>[email protected](Object o) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 183
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()

Collection was modified; enumeration operation may not execute

Given I am posting a message to the Hub with a type of List<'T> from different threads. It looks like I'm getting the following error even if I made sure that the source object that I'm trying to send is not getting modified.

Stack trace shows something related to Array.Parallel? I'm not entirely sure if it's due to the library?

|> Array.Parallel.iter (fun (d,m) -> d m)

System.AggregateException: One or more errors occurred. (Collection was modified; enumeration operation may not execute.)
 ---> System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Fable.Remoting.Json.FableJsonConverter.WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConvertable(JsonWriter writer, JsonConverter converter, Object value, JsonContract contract, JsonContainerContract collectionContract, JsonProperty containerProperty)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value, Type type, JsonSerializer jsonSerializer)
   at <StartupCode$Elmish-Bridge-Server>[email protected](client x)
   at Microsoft.FSharp.Collections.ArrayModule.Parallel.Iterate@1418.Invoke(Int32 i) in D:\a\_work\1\s\src\fsharp\FSharp.Core\array.fs:line 1418
   at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0`1.<ForWorker>b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion)
--- End of stack trace from previous location ---
   at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0`1.<ForWorker>b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion)
   at System.Threading.Tasks.TaskReplicator.Replica.Execute()
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.TaskReplicator.Run[TState](ReplicatableUserAction`1 action, ParallelOptions options, Boolean stopOnFirstFailure)
   at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 localInit, Action`1 localFinally)
--- End of stack trace from previous location ---
   at System.Threading.Tasks.Parallel.ThrowSingleCancellationExceptionOrOtherException(ICollection exceptions, CancellationToken cancelToken, Exception otherException)
   at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 localInit, Action`1 localFinally)
   at System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, Action`1 body)
   at Microsoft.FSharp.Collections.ArrayModule.Parallel.Iterate[T](FSharpFunc`2 action, T[] array) in D:\a\_work\1\s\src\fsharp\FSharp.Core\array.fs:line 1417
   at <StartupCode$Elmish-Bridge-Server>[email protected](Unit unitVar)
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 447
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104
--- End of stack trace from previous location ---
   at [email protected](ExceptionDispatchInfo edi) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 1078
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104
   at <StartupCode$FSharp-Core>[email protected](Object o) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 183
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()

[Suggestion] Let the user handle the message mapping through subscriptions

Having the notion of the "top-level" messages is very confusing and to register a specific union case instead of the type of the DU is a bit weird.

Before talking about implementation details, lets look at things are now, here is my application specific message type on the client side:

type AppMsg =
  // The messages coming from children
  | PostsMsg of Posts.Types.Msg
  | AdminMsg of Admin.Types.Msg
  // the app messages 
  | LoadBlogInfo
  | BlogInfoLoaded of Result<BlogInfo, string> 
  | BlogInfoLoadFailed of error:string
  | UrlUpdated of Page
  | NavigateTo of Page
  // specialized message coming from server via Elmish.Bridge
  | ServerMsg of RemoteServerMsg
  | DoNothing 

where RemoteServerMsg is defines as

// Message from the server, telling the client to reload posts
type RemoteServerMsg = 
    | ReloadPosts 

By registering the ServerMsg as a mapping in the bridge client, bridge now has the responsibility of internally creating an AppMsg of case ServerMsg and sending it to the dispatch loop

My suggestion is as follows: let the user himself do the dispatch and mapping of messages whenever a message is coming from the server using a subscription, it would look this:

let remoteMsgSub = 
    Bridge.subscribe<RemoteServerMsg> (fun remoteServerMsg dispatch -> 
          // the user creates the AppMsg, not the library
          let appMsg = AppMsg.ServerMsg remoteServerMsg 
          dispatch appMsg 
    )

This way, I could have more control and if I wanted, I could remove the ServerMsg of RemoteServerMsg from the server entirely and dispatch some other message in my app that is specific to that subscription.

The same would happen on the server, the user subscribes to client messages and maps them himself.

What do you think of this? What challenges do you expect?

Endpoint location is not relative

When relative endpoint location is specified in Program.withBridge call, it is not relative during the runtime.

Consider the scenario, when app is developed locally as standalone - i.e. all routes are root-based, but deployed to IIS as a virtual app under some folder.

Here
the endpoint location with the existing path info is being replaced with the one passed with the bridge configuration.

Maybe it would be better to do this instead?
url.pathname <- url.pathname + this.path

How to handle connections to different websocket servers?

Program.mkProgram Index.init Index.update Index.view
|> Program.withSubscription (fun model ->
    let body msg msg_closed dispatch =
        let config =
            Bridge.endpoint $"{Url.learn_server}/{socket_endpoint}"
            |> Bridge.withWhenDown msg_closed
            |> Bridge.withMapping Index.FromServer

        Bridge.asSubscription config dispatch

        Bridge.Send // how do we use this?

        config :> IDisposable
    [
        for KeyValue(name,pl) in model.cfr_players do
            if pl.training_iterations_left > 0 then
                let key = [ string name; "train"; string pl.training_iterations_left ]
                key, body (FromClient (MsgClientToServer.Train (pl.training_iterations_left, pl.training_model)))
                        (Index.ConnectionClosed(name,true))
            if pl.testing_iterations_left > 0 then
                let key = [ string name; "test"; string pl.testing_iterations_left ]
                key, body (FromClient (MsgClientToServer.Test (pl.testing_iterations_left, pl.testing_model)))
                        (Index.ConnectionClosed(name,false))
    ]
    )
|> Program.withBridgeConfig (
        Bridge.endpoint socket_endpoint
        |> Bridge.withMapping Index.FromServer
        )
|> Program.withReactSynchronous "app"
#if DEBUG
|> Program.withDebugger
#endif
|> Program.run

It is really good that Elmish.Bridge has a way of closing and reacting to connections closings, but I want to be able to interact with multiple ws servers. I do not understand how Bridge.Send could be configured to send to a specific server.

Is that possible?

Update to Fable.Browser.Url 1.3.2

Hello @Nhowka.

in one of the latest version of Fable.Browser.Url we fixed the URL.Create.

The problem is that even if the new signature is in theory compatible with the previous one F#/.NET is not able to pick it when used in already published packages.

image

fable-compiler/fable-browser#116

Can you please update Elmish.Bridge.Clilent to Fable.Browser.Url version 1.3.2? In theory, you just need to update the dependency and have nothing to change in your code.

ValLinkagePartialKe(set_onclose) error building without Fable

Building the client to .NET (i.e. not with Fable) I get an error:

The module/namespace 'Types' from compilation unit 'Browser.WebSocket' did not contain the val 'ValLinkagePartialKe(set_onclose)'

|> Program.withBridgeConfig ( Bridge.endpoint BridgeShared.endpoint |> Bridge.withMapping (ProjMgmt.Index.ServerMsg >> Users.Secure.InnerMsg) )

I can workaround with an #if FABLE_COMPILER guard. I like to compile without Fable to get intellisense working in Visual Studio. Once I do the proper build it works fine.

Server fails to deserialize client's message if it includes List<'T>

It appears that the new Json.serialize serializes F#'s Lists in a way that is incompatible with what the server expects.

Remote message: ["Shared.Types.UpstreamMsg", "[\"UpstreamAppMsg\",[\"HwMsg\",[\"SwitchRelays\",{\"head\":[\"Pump1\",\"On\"],\"tail\":{\"head\":null}}]]]"]
[17:29:11 INF] Request finished HTTP/1.1 GET http://192.168.0.61:5000/appSock - - - 101 - - 1173352.2601ms
[17:29:12 INF] Request starting HTTP/1.1 GET http://192.168.0.61:5000/appSock - -

The other problem (besides messages not getting across) is that client simply gets disconnected and nobody reports any error, which makes it difficult to debug.

Question: extend Elmish.Bridge to RPC with a ReplyChannel abstraction

If this is outside the scope of your Vision of Elmish.Bride, please feel free to just close this ;)


I like the elmish concept in general, and I already use and like Elmish.Bridge for pushing data from server to client.

It is really good for one-way, message based communication.

But often you also need a RPC style, request-response flow, and that is a little bit cumbersome with just fire-and-forget messages.


I haven't thought too deeply about it, but what would you think about adding RPC capabilities by emulating some Actor models with a ReplyChannel abstraction?

The proposed API use would look very similar to the MailboxProcessor:

You have a ReplyChannel class/interface.

On the serverside on the hub, you have AskClient / AskClientIf, similar to BroadcastClient / SendClientIf.

On the client side you have Bridge.Ask, complimentary to Bridge.Send.


IMO the Msg and update parts of elmish are already very similar to an actor, this would just extend this capability.

ECONNREFUSED errors

I am getting
[HPM] Error occurred while trying to proxy request /socket/init from localhost:8080 to http://localhost:8085 (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors)
[HPM] Error occurred while trying to proxy request /socket from localhost:8080 to http://localhost:8085 (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors)
[HPM] Error occurred while trying to proxy request /socket from localhost:8080 to http://localhost:8085 (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors)
[HPM] Error occurred while trying to proxy request /socket from localhost:8080 to http://localhost:8085 (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors)

indefinitely and elmish bridge does not work.
To reproduce:
1-) Create a new SAFE application with Elmish Bridge
2-) Update all packages via dotnet paket update (or just update Saturn, which in turn updates FSharp.Control.WebSockets and Microsoft.AspNetCore.WebSockets)
3-) Run the app.

Sending messages to Elmish update on the server locally

On my server there is logic that works in parallel with the websocket Elmsih update. I found a method that allows you to get models of connected users:
connections.GetModels()
However, I can't find a method to send a message to the Elmish update
The following method is not suitable due to the fact that the message sent to it can also be sent from the client
connections.BroadcastServer (Inner Test)
The client has the following option
Bridge.Send (Inner Test)

Is it possible to resolve Bridge configuration at runtime?

A client app startup code looks like this:

Program.mkProgram init update render
|> Program.withBridgeConfig (Bridge.endpoint "./socket" |> Bridge.withUrlMode Append)
|> Program.withReactSynchronous "elmish-app"
|> Program.run

And for more advanced configuration I can use Calculated URL mode with a function that returns a url for socket communication.

But in both cases the url is resolved upfront. What if the configuration comes from an app settings JSON? Then the app typically introduces app settings load messages (e.g. LoadAppSettings and AppSettingsLoaded) and loads the settings asynchronously. The regular operations are deferred until app settings are loaded and the configuration is resolved.

It would be nice to be able to fit Bridge configuratoin into the same workflow i.e. defer setting up Bridge config URL resolution until the application settings are loaded. But it doesn't seem to be possible using withBridgeConfig. Or am I overlooking something?

Error in instructions for Saturn

In this section

open Elmish
open Elmish.Bridge

let server =
  Bridge.mkServer Shared.endpoint init update
  |> Bridge.run Giraffe.server

let webApp =    // <---- ??? not referenced
  choose [
    server // <---- referenced twice?
    route "/" >=> htmlFile "/pages/index.html" ]

let app =
  application {
    router server   // <---- ??? use_router?  server referenced twice?
    disable_diagnostics
    app_config Giraffe.useWebSockets
    url uri
  }

run app

Should that read instead use_router webApp ?

Doesn't work with Giraffe 6, .NET 6

Trying to upgrade to Giraffe 6. Getting an error because it doesn't include Ply.

server: info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
server:       Request starting HTTP/1.1 GET http://localhost:8080/socket/clientbridge - -
server: fail: Giraffe.Middleware.GiraffeErrorHandlerMiddleware[0]
server:       An unhandled exception has occurred while executing the request.
server:       System.IO.FileNotFoundException: Could not load file or assembly 'Ply, Version=0.3.1.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
server:       
server:       File name: 'Ply, Version=0.3.1.0, Culture=neutral, PublicKeyToken=null'
server:          at [email protected](FSharpFunc`2 next, HttpContext ctx)
server:          at [email protected]()
server:          at [email protected]()
server:          at [email protected]()
server:          at [email protected]()
server:          at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
server:          at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
server:          at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
server:          at [email protected]()
server: info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
server:       Request finished HTTP/1.1 GET http://localhost:8080/socket/clientbridge - - - 500 140 application/json;+charset=utf-8 1.3749ms

Workaround is to explicitly add nuget Ply as a dependency.

Not compatible with Thoth.Json 3-beta

Thoth.Json 3 is currently in beta.

When I tried to use Thoth.Json 3.0.0-beta-002 with Elmish.Bridge.Client 2.0.1, I got this error:

ERROR in ./.fable/Elmish.Bridge.Client.2.0.1/Library.fs
Module Error (from ./node_modules/fable-loader/index.js):
c:/Projekte/ebos/ebosYC/.fable/Elmish.Bridge.Client.2.0.1/Library.fs(106,18): (106,97) error FABLE: Cannot get type info of generic parameter Server, please inline or inject a type resolver
 @ ./src/Client/App.fs 19:0-471 134:14-71 134:72-111 134:141-179 136:3-41 136:46-53 138:4-39
 @ ./src/Client/Client.fsproj
 @ multi ./src/Client/Client.fsproj ./src/Client/site.css

ERROR in ./.fable/Elmish.Bridge.Client.2.0.1/Library.fs
Module Error (from ./node_modules/fable-loader/index.js):
c:/Projekte/ebos/ebosYC/.fable/Elmish.Bridge.Client.2.0.1/Library.fs(97,23): (97,102) error FABLE: Cannot get type info of generic parameter Server, please inline or inject a type resolver
 @ ./src/Client/App.fs 19:0-471 134:14-71 134:72-111 134:141-179 136:3-41 136:46-53 138:4-39
 @ ./src/Client/Client.fsproj
 @ multi ./src/Client/Client.fsproj ./src/Client/site.css

DotnetClient reply channel type not compatible with exn

Trying to use a IReplyChannel<bool> on the DotnetClient, but when using Bridge.AskServer it has a compilation error:

The type 'bool' is not compatible with the type 'exn'

module TestReplyChannels =
    type Msg =
        | Foo of string * IReplyChannel<bool>

    let _ = Bridge.AskServer (fun rc -> Foo("bar", rc))

Does AskServer somehow have an implicit requirement that 'T inherits Exception?

Single shared state

Hi,

This is not really an issue report, but rather a personal question, so I'd like to excuse myself in advance for polluting this repository's issue tracker.

I've ported an existing app to Fable2 and in the process I had to find a new solution for Fable.Websockets.Elmish, so Elmish.Bridge seemed like a good solution.

It was only after much of the port was already completed, that I've found out that there's a slight change in the architecture...

My app has only one single "Elmish" state that is being shared and manipulated by all clients together, whereas Elmish.Bridge keeps a separate state for each client connection.

Now I'm trying to find an efficient way of recreating my original functionality (single state per app, rahter than one state per connection). Technically what I need to do is probably a single global mailbox processor into which all client's update calls get diverted.

My question is that Elmish.Bridge already has a Mailbox procesor for each client connection so I'm wondering if there is a simple way to use or somehow plug into the existing code rather than wrapping another mailbox processor around existing ones.

I'll appreciate any help or hints on how to best approach this situation
(I'm not yet familiar with the internals of Elmish.Bridge -- if possible I'd rather plug into existing code rather than forking the whole repository).

Q: SignalR for automatic fallback?

Hi @Nhowka,

I have a question on fallbacks. Is there a plan to use / upgrade to https://github.com/aspnet/SignalR so that Elmish.Bridge can take advantage of automatic fallbacks? According to https://docs.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-2.1, using Microsoft.AspNetCore.WebSockets requires the developer (aka either you or the app developer) to implement fallback to alternative transport protocols.

Would love to hear your thoughts on this.

Thank you

What should this project be called?

This project was born from the knowledge I got from working on Fable.Remoting. It's one of many amazing projects from @Zaid-Ajaj that I recommend if you don't know about it yet.

Now, it's time to leave the nest; the names are too similar and that will cause confusion.

On one hand, Fable.Remoting is a general use library. You can call functions over the wire and when done on the recommended guidelines is stateless. This project is designed to work on an Elmish environment. It's stateful and changes that state by exchanging messages between client and server (or server-server and client-client, but only the client-server travels through the magic channel WebSocket.

So. It's a client-server tool for communication with Elmish. Only Elmish. That is an important bit. The Remoting part? Well, I'll be sad to let it go, but we can live with another name. I even brought some options ready, but I'm not a creative person on naming things.

How about Elmish.Link? Or maybe Elmish.Wire? The project creates a way for messages to go on their way, traveling to change a state on a distant realm. (Or not that far, most action it got was on localhost.)

Enough of story time! Bring me your best names!

Integration with Elmish.Streams on the server side

I have a scenario where I am using the Elmish.Bridge architecture on the frontend with Elmish.Streams. However this breaks the isomorphism, since Elmish.Bridge on the server side uses Cmds by default, while Elmish.Streams does not use Cmds.

You may know, Elmish.Streams modifies the update function, where instead of generating and returning Cmds there. There is a new function stream that is subscribed to Program.mkSimple and what would be done by update with Cmds, is instead done in stream, decoupled from update.

What would be a simple way to use Elmish.Bridge, modified to use Elmish.Streams on the server side?

Elmish.Bridge.Client 3.1.1 not compatible with Fable.SimpleJson 3.6.0+?

Using fable-compiler 3.4.10, all's fine with Fable.SimpleJson 3.5.0 - but with 3.6.0 or 3.7.0, I get a couple of build errors:

  • .../.fable/Elmish.Bridge.Client.3.1.1/Library.fs(196,27): (196,33) error FABLE: Cannot get type info of generic parameter Msg, please inline or inject a type resolver
  • .../.fable/Elmish.Bridge.Client.3.1.1/Library.fs(96,42): (96,74) error FSHARP: This expression was expected to have type 'string' but here has type ''a * 'b'

(Not a major problem: I can just stick with Fable.SimpleJson 3.5.0 for now. But thought it might be worth mentioning...)

WebSocket is invalid State

I get the following when I reload the page with websockets with Chrome.

Unhandled exception. System.AggregateException: One or more errors occurred. (The WebSocket is in an invalid state ('Closed') for this operation. Valid states are: 'Open, CloseReceived')
 ---> System.Net.WebSockets.WebSocketException (0x80004005): The WebSocket is in an invalid state ('Closed') for this operation. Valid states are: 'Open, CloseReceived'
   at System.Net.WebSockets.WebSocketValidate.ThrowIfInvalidState(WebSocketState currentState, Boolean isDisposed, WebSocketState[] validStates)
   at System.Net.WebSockets.ManagedWebSocket.SendAsync(ReadOnlyMemory`1 buffer, WebSocketMessageType messageType, WebSocketMessageFlags messageFlags, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at [email protected](ExceptionDispatchInfo edi) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 1078
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104
   at <StartupCode$FSharp-Core>[email protected](Object o) in D:\a\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 183
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()
[16:52:00 INF] HTTP GET /socket responded 101 in 255.8078 ms

Is this just a simple check before the library calls SendAsync to the WebSocket if it's in valid state?

Use with Javascript client

Is it possible to use this directly in javascript? Probably there should be a way to generate an api documentations for use with other clients?

V4 subscription additions

As a consequence of how Program.withSubscription is defined:

let withSubscription (subscribe : 'model -> Sub<'msg>) (program: Program<'arg, 'model, 'msg, 'view>) =
    { program with subscribe = subscribe }

The current implementation of Program.withBridge and Program.withBridgeConfig will overwrite any existing subscriptions. Alternatively, if there are subscriptions added after the bridge configuration, the Elmish.Bridge subscriptions will be overwritten and their events will never be received.

So I'm suggesting the following additional functions to be added (bikeshedding welcome):

let inline withBridgeAndSubs
  endpoint
  (subs : 'model -> (string list * (('msg -> unit) -> IDisposable)) list)
  (program : Program<_, 'model, 'msg, _>) =

   program
   |> Program.withSubscription
        (fun model ->
            subs model
          @ [["Elmish";"Bridge"], fun dispatch -> let config = Bridge.endpoint(endpoint) in config.Attach dispatch; config ])

let inline withBridgeConfigAndSubs
  (subs : 'model -> (string list * (('msg -> unit) -> IDisposable)) list)
  (config:BridgeConfig<_,_>)
  (program : Program<_, 'model, 'msg, _>) =

  program
  |> Program.withSubscription
       (fun model ->
            subs model
          @ ["Elmish"::"Bridge"::(config.name |> Option.map List.singleton |> Option.defaultValue []), fun dispatch -> config.Attach dispatch; config])

So that the new style of Elmish subscriptions can play nice with Elmish.Bridge

Version mismatches with recent saturn & giraffe

It seems that everything is moving ahead (including new versions of Saturn, Giraffe and Fable 3) and people are updating their software so things are starting to break.

Many recent starter projects, like https://github.com/Zaid-Ajaj/SAFE.Simplified will break dependencies that come with current version of Elmish.Bridge, because other packages are pulling in a more recent version of Giraffe:

Unhandled exception. System.IO.FileLoadException:
Could not load file or assembly 'Giraffe, Version=3.6.0.0, Culture=neutral, PublicKeyToken=null'.
The located assembly's manifest definition does not match the assembly reference. (0x80131040)
File name: 'Giraffe, Version=3.6.0.0, Culture=neutral, PublicKeyToken=null'
   at Elmish.Bridge.Giraffe.server(String endpoint, FSharpFunc`2 inboxCreator)
   at <StartupCode$Server>.$Program.main@()

Some people have already made necessary code fixes: https://github.com/Nhowka/Elmish.Bridge/network

Is there any chance of spinning a new version on Nuget?

Fable.SimpleJson stringify error in Bridge.Sender

Cmd.bridgeSend appears to trigger a runtime error:

image

I have published a repo that makes bug reproducible at: https://github.com/pkese/Elmish.Bridge.Test

To run:

  Shell tab ./Elmish.Bridge.Test
 -------------------------------------- --------------------------------------
  > cd server                            > cd client
  > dotnet restore                       > npm install
  > dotnet run                           > ./run.sh

While processing Welcome message it fails to serialize Cmd.bridgeSend UpstreamMsg.GetState

Websocket connection fails in IE11 / Edge

Apologies for even mentioning IE11 ...

We don't have a strong requirement for IE11, but it would be really nice to be able to still support it.

  1. I had to polyfill URL with url-polyfill
  2. The websocket ctor throws because for some reason there is a hash at the end:

grafik

If I set a breakpoint, remove the # then continue, it works.


Do you have any idea where the hash comes from? Maybe it is an issue with url-polyfill, I have no clue.

What do you think about just adding a TrimEnd('#')?

EDIT: Same behavior with Edge (EdgeHTML 16.16299)

Update to Elmish v4

Elmish v4 has some changes with subscription, my fix locally looks like below

[<RequireQualifiedAccess>]
module Program =
    /// Apply the `Bridge` to be used with the program.
    /// Preferably use it before any other operation that can change the type of the message passed to the `Program`.
    let inline withBridge endpoint (program : Program<_, _, _, _>) =
        program |> Program.withSubscription (fun _ -> [["withBridge"],Bridge.endpoint(endpoint).Attach])

    /// Apply the `Bridge` to be used with the program.
    /// Preferably use it before any other operation that can change the type of the message passed to the `Program`.
    let inline withBridgeConfig (config:BridgeConfig<_,_>) (program : Program<_, _, _, _>) =
        program |> Program.withSubscription (fun _ -> ( [ ["withBridgeConfig"], config.Attach ]))

And return disposable from attach

image

Order of broadcast messages

When using several BroadcastClient(msg) in sequence, it seems that the client does not receive the messages in the same order. Is this intended? Is there a way to preserve the order?

DotnetClient stops getting server messages after using AskServer

After solving #40 and going about using AskServer with IReplyChannel, it took me a while to realise that using it seems to break other messages.

Using Bridge.AskServer with a reply channel works fine the first time, but only once, and after using it all other server messages stop being received by the client. The server can still receive messages sent by the client, though.

Also, usually if I stop the server, the client will print debug logs every second with the Bridge.withWhenDown message. But after using a reply channel at least once, if I then stop the server, the client does not even get those connection down messages.

[Question] shorten form of `Bridge.AskServer`

Hi,

I'm testing RPC (Bridge.Client 6.0.2) functionality along with Bridge.AskServer function. To make it more comfortable to use, I thought about creating a helper like this:

let askServer f arg =
    Bridge.AskServer(fun rc -> f(arg, rc))

// ... usage in an app

type ToServer =
        | GetTodos of unit * IReplyChannel<Todo list>

type Msg =
    | GotTodos of Todo list
    | GotTodosError of exn
    
let cmd = Cmd.OfAsync.either
                  (Bridge.askServer GetTodos)
                  ()
                  GotTodos GotTodosError

However, during runtime I receive an error:
TypeError: Cannot read properties of null (reading 'fullname') (log attached)
error.log

Would you happen to have any idea what is the reason for this? Can it be fixed somehow?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.