Liking cljdoc? Tell your friends :D

com.7theta/via

Current Version GitHub license Dependencies Status

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.

Running example application

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.

Usage

There are several features included in via, with their most common use cases outlined below.

Endpoints

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.

Integrant

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"}}}

Manage Lifecycle Directly

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)

Events and Subs

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).

Events

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}}))

Subs

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)))

Export Events and Subs

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}}}}

Dispatch / Invoke / Subscribe

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

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.

Send Directly to Peer

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)

Effect Handlers / Event Handlers

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.

Heartbeat

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}}

Session Context

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.

Tags

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.

Internal via Event System

To 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 and License

Copyright © 2015 7theta

Distributed under the MIT License.

Can you improve this documentation? These fine people already did:
Achint Sandhu, Tom, Joshua DeGagné & Tom Goldsmith
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close