Skip to content

Socket handlers

The SDKs ship a dispatcher so you write one callback per event instead of switching on input.action. The dispatcher also exposes the live socket metadata on the context (for example ctx.ws.action, ctx.ws.connectionId, ctx.ws.requestId, ctx.ws.topic).

Node.js

js
const { modusellSocket } = require('./modusell_sdk.js');

exports.handler = modusellSocket({
  async connect(ctx) {
    return ctx.ws.accept({ subscribe: ['events'], messages: [{ welcome: true }] });
  },
  async message(ctx, input) {
    await ctx.ws.publish('events', { type: 'message', data: input });
    return { echo: input };
  },
  async subscribe(ctx, input) {
    return input.topic === 'events' ? ctx.ws.allow() : ctx.ws.deny('Unknown topic');
  },
  async close(ctx) {
    return {};
  },
});

PHP

php
<?php

return modusell_socket([
    'connect' => function ($context) {
        return $context->ws->accept(['subscribe' => ['events'], 'messages' => [['welcome' => true]]]);
    },
    'message' => function ($context, $input) {
        $context->ws->publish('events', ['type' => 'message', 'data' => $input]);
        return ['echo' => $input];
    },
    'subscribe' => function ($context, $input) {
        return ($input['topic'] ?? '') === 'events' ? $context->ws->allow() : $context->ws->deny('Unknown topic');
    },
    'close' => function ($context) {
        return [];
    },
]);

Python

python
from modusell_sdk import modusell_socket


def connect(ctx, input):
    return ctx.ws.accept(subscribe=["events"], messages=[{"welcome": True}])


def message(ctx, input):
    ctx.ws.publish("events", {"type": "message", "data": input})
    return {"echo": input}


def subscribe(ctx, input):
    return ctx.ws.allow() if input.get("topic") == "events" else ctx.ws.deny("Unknown topic")


def close(ctx, input):
    return {}


handler = modusell_socket(
    connect=connect,
    message=message,
    subscribe=subscribe,
    close=close,
)

Rust

rust
use modusell_sdk::prelude::*;

fn handler(ctx: Context, input: Value) -> FunctionResult {
    match WsAction::from_input(&input) {
        Some(WsAction::Connect) => Ok(accept(
            vec!["events".to_string()],
            vec![json!({ "welcome": true })],
        )),
        Some(WsAction::Message) => {
            let _ = ctx.ws.publish("events", json!({ "type": "message", "data": input.clone() }));
            Ok(json!({ "echo": input }))
        }
        Some(WsAction::Subscribe) => {
            if input.get("topic").and_then(Value::as_str) == Some("events") {
                Ok(allow())
            } else {
                Ok(deny("Unknown topic"))
            }
        }
        Some(WsAction::Close) | None => Ok(json!({})),
    }
}

Rust also offers a builder that routes actions to boxed callbacks: Socket::new().on_connect(...).on_message(...).dispatch(ctx, input).

Publishing

publish(topic, data) requires the ws_publish capability and a topic matching ws_topics (empty denies, * allows everything, prefix:* matches a prefix). The call returns the durable event id. Handlers that never publish don't need ws_publish; leave the capability off.

Start a new function from New → WebSocket endpoint to get the matching starter for each language, then test it from the Socket console in the function's inspector.