@abot-ai/socket 0.1.0 → 0.2.0
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.
- package/CHANGELOG.md +38 -0
- package/README.md +27 -14
- package/docs/INSTALLATION.md +13 -2
- package/docs/MIGRATION.md +38 -2
- package/docs/PROTOCOL.md +111 -18
- package/docs/SECURITY.md +18 -0
- package/docs/SELF-HOSTING.md +9 -2
- package/package.json +2 -1
- package/src/auth/auth-database.mjs +6 -3
- package/src/cli/client-capabilities.mjs +12 -2
- package/src/protocol/approval-protocol.mjs +39 -0
- package/src/protocol/protocol.mjs +11 -1
- package/src/protocol/spark-chat-protocol.mjs +51 -0
- package/src/relay/relay-approvals.mjs +86 -0
- package/src/relay/relay-correlations.mjs +8 -3
- package/src/relay/relay-request-adoption.mjs +97 -0
- package/src/relay/relay-requests.mjs +69 -13
- package/src/relay/relay-session-mutations.mjs +39 -5
- package/src/relay/relay.mjs +25 -5
- package/src/relay/request-ownership-schema.mjs +18 -0
- package/src/relay/request-ownership-store.mjs +115 -0
- package/src/transport/http-api.mjs +3 -2
- package/src/transport/server.mjs +13 -3
|
@@ -2,7 +2,7 @@ import { bearerToken, jsonResponse, readJson } from './transport-limits.mjs';
|
|
|
2
2
|
import { clientCapabilities } from '../cli/client-capabilities.mjs';
|
|
3
3
|
import { handleNotificationHttp } from '../notifications/notification-http.mjs';
|
|
4
4
|
|
|
5
|
-
export async function handleHttpApi(req, res, { auth, runtimeGrants, relay, sourceId, notifications }) {
|
|
5
|
+
export async function handleHttpApi(req, res, { auth, runtimeGrants, relay, sourceId, notifications, ownership }) {
|
|
6
6
|
const path = req.url;
|
|
7
7
|
if (req.method === 'GET' && path === '/health') return jsonResponse(res, 200, { ok: true, version: 2 });
|
|
8
8
|
if (req.method === 'POST' && path === '/v2/pairing/redeem') {
|
|
@@ -24,7 +24,8 @@ export async function handleHttpApi(req, res, { auth, runtimeGrants, relay, sour
|
|
|
24
24
|
if (req.method === 'GET' && path === '/v2/runtimes') {
|
|
25
25
|
const snapshots = relay.runtimeSnapshot(runtimeGrants.map((grant) => grant.runtimeId));
|
|
26
26
|
return jsonResponse(res, 200, { runtimes: runtimeGrants.map(({ runtimeId, allowedModes }, index) => ({
|
|
27
|
-
...snapshots[index], runtimeId, allowedModes,
|
|
27
|
+
...snapshots[index], runtimeId, allowedModes,
|
|
28
|
+
capabilities: clientCapabilities({ ownership, runtime: snapshots[index] }),
|
|
28
29
|
})) });
|
|
29
30
|
}
|
|
30
31
|
return jsonResponse(res, 404, { error: 'not_found' });
|
package/src/transport/server.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { createServer as createHttpsServer } from 'node:https';
|
|
|
3
3
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
4
4
|
import { AuthService } from '../auth/auth.mjs';
|
|
5
5
|
import { Relay } from '../relay/relay.mjs';
|
|
6
|
+
import { RequestOwnershipStore } from '../relay/request-ownership-store.mjs';
|
|
6
7
|
import { handleHttpApi } from './http-api.mjs';
|
|
7
8
|
import { WindowLimiter, sameAuthority, jsonResponse, bearerToken, trustedClientAddress } from './transport-limits.mjs';
|
|
8
9
|
import { resolveRuntimeCredential, validateRuntimeGrants } from '../auth/runtime-credentials.mjs';
|
|
@@ -22,9 +23,11 @@ export function createSocketServer({ stateDirectory, runtimeGrants: grants,
|
|
|
22
23
|
const runtimeGrants = validateRuntimeGrants(grants);
|
|
23
24
|
const database = new AuthDatabase(stateDirectory);
|
|
24
25
|
let auth;
|
|
26
|
+
let ownership;
|
|
25
27
|
let notifications;
|
|
26
28
|
try {
|
|
27
29
|
auth = new AuthService({ database, now });
|
|
30
|
+
ownership = new RequestOwnershipStore({ database, now });
|
|
28
31
|
notifications = new NotificationService({ database, now,
|
|
29
32
|
delivery: notificationDelivery ?? createNotificationDelivery(notificationConfig?.delivery ?? { type: 'disabled' }) });
|
|
30
33
|
} catch (error) {
|
|
@@ -51,7 +54,14 @@ export function createSocketServer({ stateDirectory, runtimeGrants: grants,
|
|
|
51
54
|
peer.send(encoded, (error) => { if (error) peer.terminate(); });
|
|
52
55
|
return true;
|
|
53
56
|
};
|
|
54
|
-
const relay = new Relay({ now, send, close,
|
|
57
|
+
const relay = new Relay({ now, send, close, ownership,
|
|
58
|
+
isClientAuthorized: client => {
|
|
59
|
+
const context = peers.get(client.peer);
|
|
60
|
+
if (context?.role !== 'client') return false;
|
|
61
|
+
const principal = auth.authenticate(context.token);
|
|
62
|
+
return principal?.subject === client.subject && principal.sessionId === client.sessionId;
|
|
63
|
+
},
|
|
64
|
+
observeRequest: (identity, body) => notifications.observe(identity, body) });
|
|
55
65
|
const authorityAccepted = (req) => sameAuthority(req, origin) &&
|
|
56
66
|
(Boolean(req.socket.encrypted) || req.headers['x-forwarded-proto'] === 'https');
|
|
57
67
|
const handler = async (req, res) => {
|
|
@@ -60,7 +70,7 @@ export function createSocketServer({ stateDirectory, runtimeGrants: grants,
|
|
|
60
70
|
const sourceId = trustedClientAddress(req);
|
|
61
71
|
if (!sourceId) return jsonResponse(res, 403, { error: 'untrusted_proxy' });
|
|
62
72
|
if (!requests.allow(sourceId)) return jsonResponse(res, 429, { error: 'rate_limited' });
|
|
63
|
-
await handleHttpApi(req, res, { auth, runtimeGrants, relay, sourceId, notifications });
|
|
73
|
+
await handleHttpApi(req, res, { auth, runtimeGrants, relay, sourceId, notifications, ownership });
|
|
64
74
|
} catch (error) {
|
|
65
75
|
const status = [400, 401, 403, 404, 409, 413, 415, 429, 503].includes(error.status) ? error.status : 500;
|
|
66
76
|
const code = status === 500 ? 'internal_error' :
|
|
@@ -86,7 +96,7 @@ export function createSocketServer({ stateDirectory, runtimeGrants: grants,
|
|
|
86
96
|
allowedRuntimeIds: runtimeGrants.map((grant) => grant.runtimeId) });
|
|
87
97
|
if (admitted === false) { close(peer, 1013, 'Capacity exceeded'); return; }
|
|
88
98
|
send(peer, { type: 'authenticated', sessionId: principal.sessionId, expiresAt: principal.expiresAt,
|
|
89
|
-
capabilities: clientCapabilities() });
|
|
99
|
+
capabilities: clientCapabilities({ ownership }) });
|
|
90
100
|
};
|
|
91
101
|
|
|
92
102
|
server.on('upgrade', (req, socket, head) => {
|