Socketelx (socketelx v0.4.3)

Copy Markdown View Source

Embed a Pusher-protocol-compatible websocket server in your own application's supervision tree:

children = [
  {Socketelx,
   name: MyApp.Socketelx,
   port: 4001,
   app_key: "my-key",
   app_secret: "my-secret",
   app_id: "my-app-id"}
]

Every option can be omitted; it falls back to the SOCKETELX_APP_KEY / SOCKETELX_APP_SECRET / SOCKETELX_APP_ID / PORT / SOCKETELX_WEBHOOK_URL / SOCKETELX_ENABLE_CLIENT_MESSAGES / SOCKETELX_REDIS_URL environment variables (the same ones the standalone Docker release reads), so this also works with zero config for the common case of one instance per app.

Running several servers

Add redis_url: "redis://host:6379" (or set SOCKETELX_REDIS_URL) on every node and they behave as one: a client connected to node A receives events triggered through node B, presence is shared, and member_added/channel_occupied-style events fire once cluster-wide. Every node must use the same app_id/app_key/app_secret.

Redis is optional — without :redis_url an instance is fully in-memory and never opens a Redis connection.

Several apps in one server

Pass :apps (or set SOCKETELX_APPS to a JSON array) to serve several Pusher apps from one port. Each app has its own id, key and secret, and is completely isolated: channels, presence, webhooks, limits and metrics are never shared between apps.

{Socketelx,
 name: MyApp.Socketelx,
 port: 6001,
 apps: [
   [id: "chat", key: "chat-key", secret: "chat-secret",
    allowed_origins: ["https://chat.example.com"]],
   [id: "admin", key: "admin-key", secret: "admin-secret",
    webhook_url: "https://admin.example.com/hooks"]
 ]}

Everything else you pass (limits, Redis, cluster settings, ...) applies to every app unless an app sets it itself. When calling trigger/5 or authorize_channel/4 on a multi-app server, say which app with app: "chat".

Several instances in one app

Running more than one named instance in the same app (e.g. two different Pusher app_ids on two different ports) works too — just give each a unique :name. Each instance gets its own channel registry and presence state; they never see each other's connections or channels.

Summary

Functions

Signs a user in for pusher-js's pusher.signin(). Call it from the endpoint pusher-js is configured to use as userAuthentication.endpoint, after you have identified the user, and reply with the returned map as JSON.

Signs a client's request to join a private or presence channel and returns the map your auth endpoint should reply with as JSON.

Stops this node from accepting new connections and tells every currently-connected client to reconnect (a clean pusher:error + close frame instead of the socket just dying). With several nodes behind a load balancer, clients reconnect to a node that is still up.

Starts one instance (its supervisor, registry, backend processes and HTTP/WebSocket listener).

Sends an event to every subscriber of channel (on every node, with Redis) without going through the HTTP API. This is how an embedding app publishes.

Functions

authenticate_user(name, socket_id, user, opts \\ [])

@spec authenticate_user(atom(), String.t(), map(), keyword()) :: map()

Signs a user in for pusher-js's pusher.signin(). Call it from the endpoint pusher-js is configured to use as userAuthentication.endpoint, after you have identified the user, and reply with the returned map as JSON.

user must have an :id (a string or integer). Once signed in, events triggered on the channel "#server-to-user-<id>" reach every connection of that user.

Socketelx.authenticate_user(MyApp.Socketelx, socket_id, %{id: "42", name: "Ada"})
#=> %{auth: "app-key:...", user_data: "{\"id\":\"42\",\"name\":\"Ada\"}"}

authorize_channel(name, socket_id, channel, opts \\ [])

@spec authorize_channel(atom(), String.t(), String.t(), keyword()) :: map()

Signs a client's request to join a private or presence channel and returns the map your auth endpoint should reply with as JSON.

Call it from the endpoint pusher-js is configured to use (channelAuthorization.endpoint) after you have decided the user may join. For a presence channel pass user_id and optionally user_info. On a multi-app server pass app: "<app id>".

Examples

Socketelx.authorize_channel(MyApp.Socketelx, socket_id, "private-room-1")
#=> %{auth: "app-key:9c1f..."}

Socketelx.authorize_channel(MyApp.Socketelx, socket_id, "presence-room-1",
  user_id: "42", user_info: %{name: "Ada"})
#=> %{auth: "app-key:...", channel_data: "{\"user_id\":\"42\",...}"}

drain(name \\ Socketelx)

Stops this node from accepting new connections and tells every currently-connected client to reconnect (a clean pusher:error + close frame instead of the socket just dying). With several nodes behind a load balancer, clients reconnect to a node that is still up.

Call this from your own app's shutdown path if you embed Socketelx; the standalone release calls it automatically on SIGTERM.

start_link(opts \\ [])

Starts one instance (its supervisor, registry, backend processes and HTTP/WebSocket listener).

Usually you don't call this directly; add {Socketelx, opts} to your supervision tree instead. See Socketelx.Config for every option.

trigger(name, channel, event, data, opts \\ [])

@spec trigger(atom(), String.t(), String.t(), term(), keyword()) :: :ok

Sends an event to every subscriber of channel (on every node, with Redis) without going through the HTTP API. This is how an embedding app publishes.

data is sent as a JSON string, as Pusher does: pass a string to send it untouched, or any term Jason can encode.

Options

  • :socket_id: skip this connection (the sender, usually)
  • :app: the app id, needed only when the server serves several apps

Example

Socketelx.trigger(MyApp.Socketelx, "chat", "message", %{text: "hi"})