remove demo app md
This commit is contained in:
@@ -1,267 +0,0 @@
|
||||
# Demo App Transport Migration
|
||||
|
||||
This guide describes the client-facing protocol for the route-agnostic transport
|
||||
architecture.
|
||||
|
||||
## Connection Model
|
||||
|
||||
HTTP, SSE, and WebSocket do not authenticate at the transport layer. Do not send
|
||||
request-auth headers such as `X-PublicKey`, `X-Signature`, or `X-Timestamp`
|
||||
for normal route dispatch.
|
||||
|
||||
Authentication is scoped to each resource operation. Every written resource
|
||||
carries its own `publicKey`, `timestamp`, and `signature`.
|
||||
|
||||
All route input belongs in the request body. Paths are exact route names; there
|
||||
are no path parameters, query parameters, or alternate HTTP methods.
|
||||
|
||||
## Request Size Limit
|
||||
|
||||
The server accepts at most 1048576 encoded bytes per HTTP request body or
|
||||
complete WebSocket message by default. Deployments can change this with
|
||||
`SERVER_MAX_REQUEST_BODY_BYTES`. The count is over the Extended JSON wire text,
|
||||
so hexadecimal `Uint8Array` values use approximately twice their decoded byte
|
||||
length.
|
||||
|
||||
HTTP limits use Hono's built-in `bodyLimit` middleware and return `413`.
|
||||
WebSocket messages do not pass through HTTP middleware; Hono's Node adapter uses
|
||||
`ws`, whose native `maxPayload` option rejects oversized frames before the
|
||||
application message handler. Response bodies are not limited.
|
||||
|
||||
## HTTP Requests
|
||||
|
||||
All application HTTP routes use POST.
|
||||
|
||||
Read:
|
||||
|
||||
```http
|
||||
POST /data/get
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"resourceId": ["resource-a", "resource-b"]
|
||||
}
|
||||
```
|
||||
|
||||
Write:
|
||||
|
||||
```http
|
||||
POST /data/write
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"id": "resource-a",
|
||||
"publicKey": "...hex...",
|
||||
"timestamp": 1730000000000,
|
||||
"signature": "...der-hex...",
|
||||
"value": "<Uint8Array: deadbeef>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Write response:
|
||||
|
||||
```json
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"id": "resource-a",
|
||||
"publicKey": "...hex...",
|
||||
"blob": "<Uint8Array: deadbeef>",
|
||||
"timestamp": 1730000001000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If any item has invalid authorization, the whole batch fails.
|
||||
|
||||
## SSE Subscriptions
|
||||
|
||||
The client explicitly requests SSE using `Accept: text/event-stream`:
|
||||
|
||||
```http
|
||||
POST /data/subscribe
|
||||
Accept: text/event-stream
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"resourceId": ["resource-a"]
|
||||
}
|
||||
```
|
||||
|
||||
A subscription without this Accept header receives a `406` JSON error.
|
||||
|
||||
The server keeps the SSE request open while its resource topics remain
|
||||
subscribed. Subscribing sends no current state; it only enables future
|
||||
publications. Request current state separately through `/data/get` when needed.
|
||||
|
||||
SSE publication:
|
||||
|
||||
```text
|
||||
id: 1730000001000
|
||||
event: instance-changed
|
||||
data: {"resourceId":"resource-a"}
|
||||
```
|
||||
|
||||
After SSE begins, failures arrive as error events and the server closes that SSE
|
||||
connection:
|
||||
|
||||
```text
|
||||
event: error
|
||||
data: {"statusCode":500,"error":"Internal Server Error"}
|
||||
```
|
||||
|
||||
To change an SSE subscription, abort the existing request and open a new
|
||||
`/data/subscribe` request with the complete desired `resourceId` list.
|
||||
|
||||
## WebSocket
|
||||
|
||||
Connect without a connection-auth message:
|
||||
|
||||
```ts
|
||||
const ws = new WebSocket("ws://host:port/ws");
|
||||
```
|
||||
|
||||
Every client message uses this strict envelope. Messages may execute
|
||||
concurrently; the server binds `id` and `body` to each individual dispatch so
|
||||
responses remain correctly correlated even when they finish out of order:
|
||||
|
||||
```ts
|
||||
type WsRequest = {
|
||||
id?: string;
|
||||
path: string;
|
||||
body?: unknown;
|
||||
};
|
||||
```
|
||||
|
||||
Old fields such as `event`, `type`, `url`, `method`, `headers`,
|
||||
`params`, and `data` are rejected.
|
||||
|
||||
Read:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "read-1",
|
||||
"path": "/data/get",
|
||||
"body": {
|
||||
"resourceId": ["resource-a", "resource-b"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Write:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "write-1",
|
||||
"path": "/data/write",
|
||||
"body": {
|
||||
"resources": [
|
||||
{
|
||||
"id": "resource-a",
|
||||
"publicKey": "...hex...",
|
||||
"timestamp": 1730000000000,
|
||||
"signature": "...der-hex...",
|
||||
"value": "<Uint8Array: deadbeef>"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Subscribe:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "sub-1",
|
||||
"path": "/data/subscribe",
|
||||
"body": {
|
||||
"resourceId": ["resource-a"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The subscribe request sends no immediate message. Its dispatch remains active
|
||||
until those topics are removed or the connection closes. Request current state
|
||||
separately through `/data/get` when needed.
|
||||
|
||||
The client should consolidate its local subscribers and send only the topic set
|
||||
needed by the shared WebSocket.
|
||||
|
||||
Unsubscribe selected resources without closing the socket:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "unsub-1",
|
||||
"path": "/data/unsubscribe",
|
||||
"body": {
|
||||
"resourceId": ["resource-a"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Unsubscribe is idempotent. The response is a normal correlated response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "unsub-1",
|
||||
"type": "response",
|
||||
"statusCode": 200,
|
||||
"body": {}
|
||||
}
|
||||
```
|
||||
|
||||
Normal response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "write-1",
|
||||
"type": "response",
|
||||
"statusCode": 200,
|
||||
"body": {}
|
||||
}
|
||||
```
|
||||
|
||||
Error response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "write-1",
|
||||
"type": "error",
|
||||
"statusCode": 400,
|
||||
"error": "Validation Error",
|
||||
"details": []
|
||||
}
|
||||
```
|
||||
|
||||
Message-level errors do not close the WebSocket. The client decides whether to
|
||||
retry, alter its subscription, or reconnect.
|
||||
|
||||
Published events retain the shared stream shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "server-event-id",
|
||||
"type": "instance-changed",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
## Signing
|
||||
|
||||
For each written resource, sign this canonical payload:
|
||||
|
||||
```ts
|
||||
`${timestamp}${resourceId}${canonicalBody(value)}`;
|
||||
```
|
||||
|
||||
`value` is the `Uint8Array` payload. `canonicalBody(value)` uses the same
|
||||
Extended JSON rules as the server:
|
||||
|
||||
```json
|
||||
"<Uint8Array: deadbeef>"
|
||||
```
|
||||
Reference in New Issue
Block a user