@stonyx/sockets 0.1.1-alpha.0 → 0.1.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,174 +0,0 @@
1
- # Handlers
2
-
3
- ## Overview
4
-
5
- Handlers are the primary extension point for consumers. Each handler is a class that extends `Handler` and defines a `server()` method, a `client()` method, or both. Handler files live in the handler directory (default: `./socket-handlers`).
6
-
7
- ## Base Class
8
-
9
- ```javascript
10
- // src/handler.js
11
- export default class Handler {
12
- static skipAuth = false;
13
- }
14
- ```
15
-
16
- That's it — 3 lines. The base class exists to provide the `skipAuth` default and a common prototype for `instanceof` checks.
17
-
18
- ## Defining a Handler
19
-
20
- ### Server-only handler
21
-
22
- ```javascript
23
- import { Handler } from '@stonyx/sockets';
24
-
25
- export default class ValidateGameHandler extends Handler {
26
- server(data, client) {
27
- // data = whatever the client sent in the 'data' field
28
- // client = the WebSocket client object (with .id, .ip, .meta, .send())
29
-
30
- // Return a value to send it back as the response
31
- return { valid: true };
32
-
33
- // Return undefined/null to send no response
34
- }
35
- }
36
- ```
37
-
38
- ### Client-only handler
39
-
40
- ```javascript
41
- import { Handler } from '@stonyx/sockets';
42
-
43
- export default class ScanGamesHandler extends Handler {
44
- client(response) {
45
- // response = whatever the server sent back
46
- // this.client = reference to the SocketClient instance
47
-
48
- this.client.app.scanGames(response);
49
- }
50
- }
51
- ```
52
-
53
- ### Dual handler (both sides)
54
-
55
- ```javascript
56
- import { Handler } from '@stonyx/sockets';
57
-
58
- export default class EchoHandler extends Handler {
59
- server(data) {
60
- return data; // echo back
61
- }
62
-
63
- client(response) {
64
- console.log('Got echo:', response);
65
- }
66
- }
67
- ```
68
-
69
- ## Auth Handler (Required)
70
-
71
- The `auth` handler is special — `SocketServer.init()` throws if it doesn't find a handler named `auth` with a `server()` method.
72
-
73
- ```javascript
74
- import { Handler } from '@stonyx/sockets';
75
- import config from 'stonyx/config';
76
-
77
- export default class AuthHandler extends Handler {
78
- static skipAuth = true; // MUST be true — auth runs before authentication
79
-
80
- server(data, client) {
81
- if (data.authKey !== config.sockets.authKey) return client.close();
82
-
83
- // Register client in the server's client map
84
- this._serverRef.clientMap.set(client.id, client);
85
-
86
- // Optionally set app-level metadata
87
- client.meta = { role: 'worker' };
88
-
89
- // Returning a truthy value triggers the framework to:
90
- // 1. Set client.__authenticated = true
91
- // 2. Generate and send a per-session encryption key (if encryption enabled)
92
- // 3. Send the response back to the client
93
- return 'success';
94
- }
95
-
96
- client(response) {
97
- // this.client = the SocketClient instance
98
- if (response !== 'success') this.client.promise.reject(response);
99
- this.client.promise.resolve();
100
- }
101
- }
102
- ```
103
-
104
- ### Auth enforcement rules
105
-
106
- - If a message arrives from an unauthenticated client:
107
- - And the handler is the `auth` handler → allowed
108
- - And the handler has `static skipAuth = true` → allowed
109
- - Otherwise → rejected, connection closed
110
- - The framework sets `client.__authenticated = true` when the auth handler returns a truthy value
111
- - If the auth handler returns `undefined`/`null`/falsy, auth fails (no response sent)
112
-
113
- ## Handler Discovery
114
-
115
- Both `SocketServer` and `SocketClient` call `forEachFileImport` on the handler directory:
116
-
117
- ```javascript
118
- await forEachFileImport(handlerDir, (HandlerClass, { name }) => {
119
- const instance = new HandlerClass();
120
- if (typeof instance.server === 'function') {
121
- instance._serverRef = this;
122
- this.handlers[name] = instance;
123
- }
124
- }, { ignoreAccessFailure: true });
125
- ```
126
-
127
- - **Filename → handler name:** kebab-case to camelCase (`validate-game.js` → `validateGame`)
128
- - **Exception:** `auth.js` stays as `auth`
129
- - **ignoreAccessFailure:** If the handler directory doesn't exist, no error is thrown
130
-
131
- ## Handler Context
132
-
133
- ### Inside `server()` hooks
134
-
135
- - `this._serverRef` — the `SocketServer` instance
136
- - First argument `data` — the parsed `data` field from the message
137
- - Second argument `client` — the WebSocket client object
138
-
139
- ### Inside `client()` hooks
140
-
141
- - `this.client` — the `SocketClient` instance (set via `.call()` binding)
142
- - First argument `response` — the parsed `response` field from the message
143
-
144
- ## Wire Protocol
145
-
146
- All messages are JSON objects with a `request` field:
147
-
148
- ```javascript
149
- // Client → Server (outgoing request)
150
- { request: 'handlerName', data: { ... } }
151
-
152
- // Server → Client (response from handler return value)
153
- { request: 'handlerName', response: { ... } }
154
-
155
- // Server → Client (explicit send via sendTo/broadcast)
156
- { request: 'handlerName', data: { ... } }
157
- ```
158
-
159
- ## Built-in Handlers
160
-
161
- These are handled by the framework — consumers do NOT define handlers for them:
162
-
163
- ### heartBeat
164
-
165
- - **Server:** Receives `heartBeat` request → responds with `{ request: 'heartBeat', response: true }`
166
- - **Client:** Receives `heartBeat` response → schedules next heartbeat via `setTimeout`
167
- - **Lifecycle:** Automatically started after successful auth; interval configured via `heartBeatInterval`
168
-
169
- ### auth (framework-level handling)
170
-
171
- While consumers define the auth handler logic, the framework wraps it with:
172
- - Auto-setting `client.__authenticated = true` on truthy return
173
- - Auto-generating and transmitting per-session encryption keys
174
- - Auto-starting the heartbeat cycle on the client side
package/.claude/index.md DELETED
@@ -1,36 +0,0 @@
1
- # @stonyx/sockets — Agent Documentation Index
2
-
3
- Comprehensive reference for AI agents working on the `@stonyx/sockets` package. Start here, then drill into specific docs as needed.
4
-
5
- ## Quick Orientation
6
-
7
- `@stonyx/sockets` is a Stonyx framework module providing WebSocket server/client with handler auto-discovery, auth enforcement, AES-256-GCM encryption, and built-in heartbeat. It follows the same conventions as `@stonyx/rest-server` and `stonyx-orm`.
8
-
9
- ## Documentation
10
-
11
- - [architecture.md](./architecture.md) — Module structure, singleton pattern, Stonyx integration, handler discovery lifecycle
12
- - [handlers.md](./handlers.md) — Handler class API, server/client hooks, auth flow, skipAuth, wire protocol
13
- - [encryption.md](./encryption.md) — AES-256-GCM encryption, key derivation, handshake flow, session keys
14
- - [configuration.md](./configuration.md) — All config options, env vars, defaults, how config loads via Stonyx
15
- - [testing.md](./testing.md) — Test structure, running tests, sample handlers, writing new tests
16
- - [api-reference.md](./api-reference.md) — Complete method/property reference for SocketServer, SocketClient, Handler
17
-
18
- ## Key Files
19
-
20
- | File | Purpose |
21
- |------|---------|
22
- | `src/main.js` | Entry point — `Sockets` default class (Stonyx auto-init) + barrel exports |
23
- | `src/server.js` | `SocketServer` — singleton, handler discovery, auth gate, message dispatch |
24
- | `src/client.js` | `SocketClient` — singleton, handler discovery, connect/auth/heartbeat |
25
- | `src/handler.js` | `Handler` base class (3 lines — just `skipAuth` flag) |
26
- | `src/encryption.js` | AES-256-GCM encrypt/decrypt, key derivation, session key generation |
27
- | `config/environment.js` | Default config with env var overrides |
28
-
29
- ## Conventions
30
-
31
- - **Singleton pattern:** `if (Class.instance) return Class.instance;` in constructor
32
- - **Stonyx module keywords:** `stonyx-async` + `stonyx-module` in package.json
33
- - **Config namespace:** `config.sockets` (camelCase of package name minus `@stonyx/`)
34
- - **Logging:** `log.socket()` via `logColor: 'white'` + `logMethod: 'socket'` in config
35
- - **Handler discovery:** `forEachFileImport` from `@stonyx/utils/file`, kebab-to-camelCase naming
36
- - **Test runner:** `stonyx test` (not plain `qunit`) — bootstraps Stonyx before running tests
@@ -1,135 +0,0 @@
1
- # Testing
2
-
3
- ## Running Tests
4
-
5
- ```bash
6
- # From the stonyx-sockets directory
7
- npx stonyx test
8
-
9
- # Or via pnpm
10
- pnpm test
11
- ```
12
-
13
- **Important:** Use `stonyx test`, not plain `qunit`. The Stonyx test runner bootstraps the framework (config, logging, module init) before running QUnit. Without it, `stonyx/config` and `log.socket()` won't be available.
14
-
15
- ## Test Structure
16
-
17
- ```
18
- test/
19
- ├── config/
20
- │ └── environment.js # Test-specific config overrides
21
- ├── sample/
22
- │ └── socket-handlers/
23
- │ ├── auth.js # Sample auth handler (server + client hooks)
24
- │ └── echo.js # Simple echo handler (both hooks)
25
- ├── unit/
26
- │ ├── handler-test.js # Base Handler class tests
27
- │ ├── encryption-test.js # AES-256-GCM encrypt/decrypt tests
28
- │ ├── server-test.js # SocketServer unit tests (no network)
29
- │ └── client-test.js # SocketClient unit tests (no network)
30
- └── integration/
31
- └── socket-test.js # Full server+client round-trip tests
32
- ```
33
-
34
- ## Test Config
35
-
36
- ```javascript
37
- // test/config/environment.js
38
- export default {
39
- sockets: {
40
- handlerDir: './test/sample/socket-handlers',
41
- heartBeatInterval: 60000, // Long interval so timers don't fire during tests
42
- encryption: 'false', // Disabled for test simplicity
43
- }
44
- }
45
- ```
46
-
47
- ## Sample Handlers
48
-
49
- ### auth.js
50
-
51
- Validates `authKey` against config, registers client in `clientMap`, resolves the connection promise. Has `static skipAuth = true`.
52
-
53
- ### echo.js
54
-
55
- Server returns whatever data it receives. Client stores the response on `client._lastEchoResponse` for test assertions.
56
-
57
- ## Writing Unit Tests
58
-
59
- Unit tests do NOT start a WebSocket server. They test class behavior directly:
60
-
61
- ```javascript
62
- import QUnit from 'qunit';
63
- import SocketServer from '../../src/server.js';
64
-
65
- const { module, test } = QUnit;
66
-
67
- module('[Unit] SocketServer', function (hooks) {
68
- hooks.afterEach(function () {
69
- const server = SocketServer.instance;
70
- if (server) server.reset();
71
- });
72
-
73
- test('Singleton pattern', function (assert) {
74
- const s1 = new SocketServer();
75
- const s2 = new SocketServer();
76
- assert.strictEqual(s1, s2);
77
- s1.reset();
78
- });
79
- });
80
- ```
81
-
82
- Key patterns:
83
- - Always call `reset()` in `afterEach` to clear the singleton
84
- - Use `sinon` for stubs/spies when needed
85
- - Restore sinon in `afterEach` with `sinon.restore()`
86
-
87
- ## Writing Integration Tests
88
-
89
- Integration tests start a real server and client:
90
-
91
- ```javascript
92
- import QUnit from 'qunit';
93
- import SocketServer from '../../src/server.js';
94
- import SocketClient from '../../src/client.js';
95
- import { setupIntegrationTests } from 'stonyx/test-helpers';
96
-
97
- const { module, test } = QUnit;
98
-
99
- module('[Integration] Sockets', function (hooks) {
100
- setupIntegrationTests(hooks); // Waits for Stonyx.ready
101
-
102
- hooks.afterEach(function () {
103
- const client = SocketClient.instance;
104
- const server = SocketServer.instance;
105
- if (client) client.reset();
106
- if (server) server.reset();
107
- });
108
-
109
- test('Round-trip', async function (assert) {
110
- const server = new SocketServer();
111
- await server.init();
112
-
113
- const client = new SocketClient();
114
- await client.init();
115
-
116
- client.send({ request: 'echo', data: { msg: 'hello' } });
117
- await new Promise(resolve => setTimeout(resolve, 200));
118
-
119
- assert.deepEqual(client._lastEchoResponse, { msg: 'hello' });
120
- });
121
- });
122
- ```
123
-
124
- Key patterns:
125
- - `setupIntegrationTests(hooks)` — adds a `hooks.before` that `await Stonyx.ready`
126
- - Always clean up in `afterEach` — `reset()` terminates connections and clears state
127
- - Use `setTimeout` + `await` for async message assertions (messages are async)
128
- - For multiple clients: null out `SocketClient.instance` between creations, track extras for cleanup
129
-
130
- ## Common Gotchas
131
-
132
- - **Process hangs after tests:** Usually caused by un-cleared heartbeat timers or unclosed WebSocket servers. Ensure `reset()` is called for all instances.
133
- - **`log.socket is not a function`:** Running `qunit` directly instead of `stonyx test`. The Stonyx bootstrap is required.
134
- - **`moduleClass is not a constructor`:** The `src/main.js` default export must be a class (not just named exports). The `Sockets` class serves as the Stonyx auto-init entry point.
135
- - **Port conflicts:** Integration tests use port 2667 by default. If tests run in parallel with other services, override `SOCKET_PORT`.
@@ -1,16 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- pull_request:
5
- branches: [dev, main]
6
-
7
- concurrency:
8
- group: ci-${{ github.head_ref || github.ref }}
9
- cancel-in-progress: true
10
-
11
- permissions:
12
- contents: read
13
-
14
- jobs:
15
- test:
16
- uses: abofs/stonyx-workflows/.github/workflows/ci.yml@main
@@ -1,51 +0,0 @@
1
- name: Publish to NPM
2
-
3
- on:
4
- repository_dispatch:
5
- types: [cascade-publish]
6
- workflow_dispatch:
7
- inputs:
8
- version-type:
9
- description: 'Version type'
10
- required: true
11
- type: choice
12
- options:
13
- - patch
14
- - minor
15
- - major
16
- custom-version:
17
- description: 'Custom version (optional, overrides version-type)'
18
- required: false
19
- type: string
20
- pull_request:
21
- types: [opened, synchronize, reopened]
22
- branches: [main]
23
- push:
24
- branches: [main]
25
-
26
- concurrency:
27
- group: ${{ github.event_name == 'repository_dispatch' && 'cascade-update' || format('publish-{0}', github.ref) }}
28
- cancel-in-progress: false
29
-
30
- permissions:
31
- contents: write
32
- id-token: write
33
- pull-requests: write
34
-
35
- jobs:
36
- publish:
37
- if: "!contains(github.event.head_commit.message, '[skip ci]')"
38
- uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
39
- with:
40
- version-type: ${{ github.event.inputs.version-type }}
41
- custom-version: ${{ github.event.inputs.custom-version }}
42
- cascade-source: ${{ github.event.client_payload.source_package || '' }}
43
- secrets: inherit
44
-
45
- cascade:
46
- needs: publish
47
- uses: abofs/stonyx-workflows/.github/workflows/cascade.yml@main
48
- with:
49
- package-name: ${{ needs.publish.outputs.package-name }}
50
- published-version: ${{ needs.publish.outputs.published-version }}
51
- secrets: inherit
package/.npmignore DELETED
@@ -1,4 +0,0 @@
1
- test/
2
- .nvmrc
3
- .git/
4
- .gitignore