A library that provides components to manage the lifecycle of a WebSocket connection.
The example directory has a fully functioning application.
Check out via-auth for authentication strategies.
Check out via-schema for schema validation strategies.
From a repl, run the following commands
user> (dev)
dev> (go)
Wait a bit, then browse to https://localhost:3449.
Shadow-cljs will automatically push cljs changes to the browser.
There are several features included in via, with their most common use cases outlined below.
To start using via, you must initialize an endpoint locally. This endpoint can then be used to establish a connection with an endpoint at a remote peer.
You can specify a list of peers to connect to on startup in the integrant config for the endpoint:
{:via/endpoint {:peers #{"wss://localhost:3449/via"}}}
Alternatively, you can connect to and disconnect from a remote endpoint manually:
(def local-endpoint (via/first-endpoint))
(def remote-peer-id (via/connect local-endpoint "wss://localhost:3449/via"))
(via/disconnect local-endpoint remote-peer-id)
via relies on the use of signum to express the ways in which communication can occur between peers. The most common action you will take when using via is to dispatch to or invoke remote event handlers, and subscribe to remote subscriptions. Both the event handlers and subscriptions are registered using signum as normal. You must export any events and subs you would like to be accessible remotely (this is described in the next section).
signum has the concept of effect handlers built in. Effect handlers allow you to chain together event handlers. via provides some useful effect handlers out of the box to simplify common actions, including sending a reply from an invoked event handler, and disconnecting a peer.
The following is an example of how to use the :via/reply effect handler to send a response from an invoked event handler:
(ns via.example.events
(:require [signum.events :as se]))
(se/reg-event
:api.example/echo
(fn [_ [_ value]]
{:via/reply {:body value
:status 200}}))
Subscription handlers are used in via to subscribe remotely to values that change over time (i.e. a signal in signum language).
Example:
(reg-sub
:api.example/add
(fn [[_ num1 num2]]
(+ num1 num2)))
By default, event handlers and subscriptions registered through signum are not accessible remotely. In order to make them accessible, you must export them. This can either be done in the endpoint integrant config, or directly with a function call.
You can export events and subs individually:
{:via/endpoint {:exports {:events #{:example.peer/recv}
:subs #{:api.example/add}}}}
Or you can export all events and subs from a namespace:
{:via/endpoint
{:exports {:namespaces #{:via.example/events
:via.example/subs}}}}
There are 3 main ways via provides to communicate with a remote peer. You can subscribe to a remote signum subscription. You can dispatch an event to a remote signum event handler. Or you can invoke a remote signum event handler.
A subscription can be used to subscribe to a value that changes over time.
(ns via.example.views
(:require [signum.hooks :refer [use-signal]]
[via.core :refer [subscribe dispatch invoke]]
[utilis.js :as j]))
;; to be called in the body of a React component
(let [sum (use-signal (subscribe [:api.example/add 5 10]))]
(println sum) ;; => 15
)
Invoke is used to have a remote peer handle an event, where a reply (:via/reply) is expected in response.
(-> (invoke [:api.example/echo :bar])
(j/call :then (fn [{:keys [body]}]
(println body) ;; => :bar
))
(j/call :catch (fn [error] (js/console.error error))))
Dispatch is identical to invoke, except no reply is expected or will be sent. It can be used for "fire and forget" semantics.
(dispatch [:api.example/echo :foo])
Interceptors can be used from signum as normal. These interceptors can be used to accomplish various tasks, but a common use case is for authorization. Interceptors in signum are implemented here.
(defn authorized?
[{:keys [coeffects]}]
(let [{:keys [request]} coeffects]
true ;; add authorization logic here
))
(def authorize
(signum.interceptors/->interceptor
:id ::auth
:before (fn [context]
(if (not (authorized? context))
(assoc context
:queue [sfx/interceptor] ; Stop any further execution
:effects {:via/disconnect nil} ;; disconnect this peer
)
context))))
The authorize interceptor can be added to an event handler or sub as needed.
In certain cases, you may have multiple peers connected (e.g. in a client/server relationship). In these cases, selecting a specific peer to send to is useful.
This can be done as follows:
;; ensure :example.peer/recv is a registered event handler, and is exported at the peer.
(via.endpoint/send (via.endpoint/first-endpoint) peer-id [:example.peer/recv :foo])
There are a few ways to access a peer-id.
From an event handler:
(se/reg-event
:example.peer/recv
(fn [{:keys [request]} _]
(println :peer-id (:peer-id request))
))
From the context passed into a signum interceptor:
(-> context
:coeffects
:request
:peer-id)
List all connected peers:
(via.endpoint/connected-peers)
via provides several effect handlers and event handlers out of the box that you can use. Look for reg-fx and reg-event in via.endpoint for those that have been implemented.
In order to share per-session data between two peers, you can use via's session-context capability. As an example, you might use session context to share a session token between two peers (this example is implemented in via-auth). The relevant effect handlers are :via.session-context/replace and :via.session-context/merge. They have corresponding functions that can be called directly as needed.
As a convenience, you can add tags to a peer. This can allow you to easily send messages to peers that all have the 'foo' tag as an example. Tags are local to the peer on which they were added. They are not shared remotely. The relevant effects handlers are :via.tags/add, :via.tags/remove, and :via.tags/replace. You can use the send-to-tag function to send a message to all peers with a given tag.
via Event SystemTo listen for certain events that happen internally to via, you can register event listeners in the integrant config, or directly using via.endpoint/add-event-listener. In the integrant config, you can register a listener for individual events, or with a :default catch-all listener.
Example:
{:via/endpoint {:event-listeners {:default (fn [event] (println :via-event event))
:via.endpoint.peer/connected (fn [event] (println :connected event))
:via.endpoint.peer/disconnected (fn [event] (println :disconnected event))}}}
Copyright © 2015 7theta
Distributed under the MIT License.
Can you improve this documentation? These fine people already did:
Tom, Achint Sandhu, Joshua DeGagné & Tom GoldsmithEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |