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 http://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 #{"ws://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 "ws://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.
It can be useful in some cases to send traffic on a socket to ensure both sides know the connection is still alive. via
provides this capability if needed. You can configure an endpoint
to send a heartbeat at a set interval by configuring the following options when initializing the endpoint
:
{:via/endpoint {:heartbeat-interval 30000 ;; ms in between heartbeats
:heartbeat-fail-threshold 1 ;; how many failed heartbeats in a row before disconnecting
:heartbeat-timeout 10000 ;; ms to wait before timing out (resulting in heartbeat failure)
:heartbeat-enabled true}}
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:
Achint Sandhu, Tom, Joshua DeGagné & Tom GoldsmithEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close