Skip to content
Back to skills

Socket

ASecurity

How to use @owlmeans/socket — the transport-agnostic Connection model shared by client-socket and server-socket, its message types (call/request/event/auth/system), the type guards, and the socket error classes. Auto-invoked when importing socket types, message constants or the connection model.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 22, 2026
businesstypescriptgoreact

Works with

  • terminal
  • cli

Security analysis

A100/100

Scanned October 6, 2026

npx -y skills add owlmeans/common --skill socket --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Socket?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Socket
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/owlmeans-socket/badge)](https://www.skillsdirectory.com/skills/owlmeans-socket)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: socket
description: How to use @owlmeans/socket — the transport-agnostic Connection model shared by client-socket and server-socket, its message types (call/request/event/auth/system), the type guards, and the socket error classes. Auto-invoked when importing socket types, message constants or the connection model.
user-invocable: false
---

# @owlmeans/socket

**Layer:** Core
**Install:** `"@owlmeans/socket": "^0.1.18-rc.41"` in `dependencies`

Contracts and one implementation-free connection model. It knows nothing about WebSockets: the
browser side is `@owlmeans/client-socket`, the Fastify side `@owlmeans/server-socket`, and each
supplies the members the model leaves abstract — `send`, `close`, `authenticate` and `prepare` —
that make it concrete. Both halves of an application therefore speak the same frames.

## Key Exports

| Export | Description |
|--------|-------------|
| `createBasicConnection()` | The connection model — everything below the wire. A carrier assigns `send` / `close` / `authenticate` / `prepare` and feeds bytes to `receive` |
| `Connection` | What a handler is handed: the messaging verbs, `stage`, `getListeners` |
| `Message<T>` | The frame — `{ type, payload, id?, sender?, recipient?, dt?, rawData? }` |
| `CallMessage<T>` / `EventMessage<T>` / `AuthMessage<T>` | The three frames that add a field: `method` + `timeout`, `event`, `stage` |
| `MessageType` | `Call` `Result` `Error` `Request` `Response` `Event` `Message` `Auth` `System` |
| `socketMessageHelper` — `.isMessage` / `.isEventMessage` / `.isCallMessage` / `.isAuthMessage` | Type guards — `socketMessageHelper.isMessage(msg, true)` excludes system frames, `.isEventMessage(msg, true)` keeps only them |
| `ConnectionListener` / `CallHendler` / `RequestHandler` / `CallResolver` | The callback shapes |
| `SocketSystemEvent` | The `event` values a `MessageType.System` frame carries — see below |
| `SOCKET_HEARTBEAT_TIMEOUT_CODE` | `4000` — the close code `client-socket`'s carrier uses when it force-closes a socket that has gone silent |
| `SocketError` and subclasses | `SocketInitializationError`, `SocketConnectionError`, `SocketUnauthorized`, `SocketUnsupported`, `SocketTimeout`, `SocketMessageError`, `SocketMessageMalformed` — all registered with `ResilientError` |
| `CALL_TIMEOUT` | 60 000 ms, the fallback when neither the message nor `connection.defaultCallTimeout` says |

## The four ways to say something

Pick by who is expected to answer and how often — they are separate registries, and a handler
bound to one never sees the others.

| Verb | Answered by | Shape |
|---|---|---|
| `notify(event, payload)` | `observe(event, handler)` | Fire-and-forget, fanned out to every observer of that event name |
| `call(method, ...args)` | `perform(method, handler)` | One RPC, resolved with the handler's return value or rejected with its error |
| `request(payload, observer?)` | `acknowledge(handler)`, answered with `reply(id, payload)` | An open question — acknowledgers run in turn until one takes it |
| `enqueue(payload, id?)` | `consume(filter?)` | A mailbox the far side drains on its own schedule; `enqueued()` is its depth |

```typescript
import { MessageType } from '@owlmeans/socket'
import type { Connection, EventMessage } from '@owlmeans/socket'

connection.observe<Progress>('job-event', async message => render(message.payload))
await connection.notify('job-event', { id, progress: 0.5 })

connection.perform<Report, [string]>('report', async id => await build(id))
const report = await connection.call<Report, [string]>('report', id)
```

A `call` carries an id and a timeout, and the model arms the timer on both sides: the caller
rejects with `SocketTimeout` when the answer does not arrive, and the performer stops sending one
once it has elapsed. `timeout: 0` disables it. A performer that throws is answered with a
`MessageType.Error` frame carrying the marshalled error, so the caller's `call` rejects with the
original class rather than with a string.

## Frames a listener sees

`listen(listener)` receives EVERY inbound frame after the model has routed it — the escape hatch
for what the verbs above do not cover. It also receives the frames a carrier synthesises, which is
how a handler learns the connection is gone:

```typescript
connection.listen(async message => {
  if (typeof message !== 'object') {
    return
  }
  const msg = message as EventMessage<void>
  if (msg.type === MessageType.System && msg.event === 'close') {
    await cleanUp()
  }
})
```

Both carriers emit exactly that frame — `MessageType.System`, `event: 'close'`, payload
`{ code }` — when the socket closes for good. Nothing else reports a TERMINAL disconnect, so any
subscription a handler opened is released there.

**`SocketSystemEvent`** is the full vocabulary a `MessageType.System` frame's `event` can carry —
`client-socket`'s reconnecting carrier is what emits the other four:

| Event | Meaning |
|---|---|
| `close` | The connection is gone for good — see above |
| `disconnected` | The socket dropped and a retry IS scheduled (client-socket only) — `{ code }` |
| `reconnecting` | Before each retry attempt (client-socket only) — `{ attempt, delay }` |
| `reconnected` | A retry succeeded, same `Connection` model (client-socket only) — `{ attempts }` |
| `lost` | The retry budget elapsed with no success (client-socket only). NOT followed by `close`: the connection stays revivable until its owner closes it |

`close` is the only one of the five a plain carrier with no retry logic (like `server-socket`, or
`client-socket` itself with `reconnect: false`) will ever emit. A listener that must react to a
reconnecting carrier giving up listens for `lost` — a `close` there comes only from its owner's
own `close()` or a terminal server code.

## What the model expects of a carrier

- `receive(raw)` takes the raw string. It only parses text that starts with `{` or `[`; anything
  else is dropped without reaching a listener. The carriers' own heartbeat IS JSON
  (`{ type: 'ping' }`), so it is parsed: it matches no `MessageType` and routes nowhere, yet it
  still reaches every `listen` listener — a listener has to recognise the frames it wants.
- A frame with no `type` is read as `MessageType.Message` and a frame with no `payload` is treated
  as its own payload, so a plain JSON body from a foreign client still arrives as a message.
- `prepare(message, isRequest?)` is the carrier's hook for stamping a frame — timestamps,
  `sender` / `recipient`. It runs on every outbound frame and on every inbound one.
- `send`, `close` and `authenticate` throw `SyntaxError` until a carrier assigns them, so one that
  forgets a member fails loudly rather than dropping frames. `prepare` is the exception: it is
  optional on the interface and simply absent until assigned, and the model calls it defensively —
  a carrier that omits it stamps nothing.

## Authentication

`auth(stage, payload)` sends an `AuthMessage` and waits. The far side's `authenticate` answers with
the next stage and its payload, or throws — a rejection travels back as an `AuthMessage` with a
null stage and is rebuilt by the initiator. `connection.stage` holds the current
`AuthenticationStage` throughout. Only the server carrier implements a real sequence; see the
`server-socket` skill.

Raw inbound data is parsed and structurally checked before dispatch. A server connection may accept
only authentication frames until its carrier marks it authenticated; an unknown call returns a
typed socket error, and listener failures are isolated and logged so an EventEmitter never receives
an unhandled rejected promise. Keep sensitive actions behind the carrier's authentication stage.

## Depends On

- `@owlmeans/error` — `ResilientError`, which every socket error registers with
- `@owlmeans/auth` — `AuthenticationStage`, the vocabulary the auth frames carry
- `@owlmeans/basic-ids` — `idHelper.uuid` for call and request ids

## Related

- `client-socket` — the browser carrier and `useWs`
- `server-socket` — the Fastify carrier, guard enforcement and `connection(protocol, callback)`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…