@lovrozagar/honey 0.1.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/README.md +51 -0
- package/docs/api-ref/go.md +399 -0
- package/docs/api-ref/python.md +399 -0
- package/docs/api-ref/rust.md +399 -0
- package/docs/api-ref/typescript.md +399 -0
- package/docs/invalidate-spec.md +303 -0
- package/docs/sdk.md +722 -0
- package/package.json +120 -0
- package/src/accepts.ts +78 -0
- package/src/body-limit.ts +100 -0
- package/src/build/index.ts +174 -0
- package/src/cli-go/auth.go +15 -0
- package/src/cli-go/config.go +165 -0
- package/src/cli-go/errors.go +57 -0
- package/src/cli-go/multipart.go +55 -0
- package/src/cli-go/output.go +109 -0
- package/src/cli-go/root.go +30 -0
- package/src/cli-go/stream.go +88 -0
- package/src/cli-go/version.go +5 -0
- package/src/cli.ts +204 -0
- package/src/client/error.ts +27 -0
- package/src/client/http.ts +502 -0
- package/src/client/index.ts +127 -0
- package/src/client/sdk.ts +265 -0
- package/src/client/sse.ts +102 -0
- package/src/client/types.ts +211 -0
- package/src/client/ws.ts +115 -0
- package/src/client-go/errors.go +207 -0
- package/src/client-go/invalidation.go +373 -0
- package/src/client-go/realtime.go +317 -0
- package/src/client-go/result.go +20 -0
- package/src/client-go/runtime.go +413 -0
- package/src/client-go/sse.go +149 -0
- package/src/client-go/transport.go +452 -0
- package/src/client-go/ws.go +104 -0
- package/src/client-mcp/README.md +10 -0
- package/src/client-mcp/server.ts +94 -0
- package/src/client-mcp/types.ts +19 -0
- package/src/client-python/_errors.py +115 -0
- package/src/client-python/_invalidation.py +578 -0
- package/src/client-python/_realtime.py +156 -0
- package/src/client-python/_runtime.py +469 -0
- package/src/client-python/_sse.py +99 -0
- package/src/client-python/_transport.py +335 -0
- package/src/client-python/_ws.py +131 -0
- package/src/client-python/py.typed +0 -0
- package/src/client-rust/errors.rs +200 -0
- package/src/client-rust/invalidation.rs +441 -0
- package/src/client-rust/invalidation_sync.rs +306 -0
- package/src/client-rust/realtime.rs +695 -0
- package/src/client-rust/result.rs +22 -0
- package/src/client-rust/runtime.rs +547 -0
- package/src/client-rust/runtime_sync.rs +452 -0
- package/src/client-rust/sse.rs +167 -0
- package/src/client-rust/ws.rs +143 -0
- package/src/cloudflare/realtime.ts +236 -0
- package/src/codegen-go-cli.ts +1180 -0
- package/src/codegen-go.ts +1010 -0
- package/src/codegen-hash.ts +8 -0
- package/src/codegen-ir.ts +517 -0
- package/src/codegen-loaders.ts +53 -0
- package/src/codegen-mcp.ts +364 -0
- package/src/codegen-python.ts +1028 -0
- package/src/codegen-rust.ts +1652 -0
- package/src/codegen-sanitize.ts +67 -0
- package/src/codegen-schema-naming.ts +146 -0
- package/src/codegen.ts +5015 -0
- package/src/context.ts +210 -0
- package/src/cookie-sign.ts +58 -0
- package/src/cookie.ts +89 -0
- package/src/cors.ts +96 -0
- package/src/crypto.ts +19 -0
- package/src/csrf.ts +54 -0
- package/src/curl-logger.ts +235 -0
- package/src/detect-runtime.ts +19 -0
- package/src/error.ts +52 -0
- package/src/errors.ts +87 -0
- package/src/etag.ts +74 -0
- package/src/feature-load.ts +23 -0
- package/src/go-type-emitter.ts +404 -0
- package/src/honey-response.ts +354 -0
- package/src/i18n-slot.ts +20 -0
- package/src/i18n.ts +234 -0
- package/src/index.ts +3843 -0
- package/src/init.ts +149 -0
- package/src/input.ts +8 -0
- package/src/ip-restrict.ts +192 -0
- package/src/logger.ts +149 -0
- package/src/middleware.ts +199 -0
- package/src/node-request.ts +240 -0
- package/src/node.ts +212 -0
- package/src/openapi/register.ts +19 -0
- package/src/openapi/scalar.ts +33 -0
- package/src/openapi/spec-factory.ts +46 -0
- package/src/openapi/spec.ts +31 -0
- package/src/openapi/swagger.ts +34 -0
- package/src/plugin.ts +771 -0
- package/src/powered-by.ts +17 -0
- package/src/pretty-json.ts +39 -0
- package/src/proxy.ts +155 -0
- package/src/python-type-emitter.ts +144 -0
- package/src/realtime/buffer.ts +101 -0
- package/src/realtime/bus.ts +93 -0
- package/src/realtime/longpoll.ts +148 -0
- package/src/realtime/protocol.ts +128 -0
- package/src/realtime/route.ts +104 -0
- package/src/realtime/runtime.ts +430 -0
- package/src/request-id.ts +23 -0
- package/src/request-to-curl.ts +34 -0
- package/src/response.ts +414 -0
- package/src/rust-type-emitter.ts +412 -0
- package/src/secure-headers.ts +69 -0
- package/src/serve-register.ts +8 -0
- package/src/serve-slot.ts +20 -0
- package/src/serve.ts +141 -0
- package/src/server-timing.ts +66 -0
- package/src/static.ts +60 -0
- package/src/telemetry/otel.ts +139 -0
- package/src/testing.ts +114 -0
- package/src/timeout.ts +36 -0
- package/src/tree.ts +464 -0
- package/src/ts-type-emitter.ts +106 -0
- package/src/type-emitter.ts +524 -0
- package/src/type-extractor.ts +749 -0
- package/src/types.ts +638 -0
- package/src/validation.ts +300 -0
- package/src/ws/bun.ts +89 -0
- package/src/ws/cloudflare.ts +133 -0
- package/src/ws/deno.ts +109 -0
- package/src/ws/node.ts +161 -0
- package/src/yaml.ts +80 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* Code generated by honey. DO NOT EDIT. */
|
|
2
|
+
|
|
3
|
+
use crate::errors::Error;
|
|
4
|
+
use serde::Serialize;
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
7
|
+
use std::sync::Mutex;
|
|
8
|
+
use tokio_tungstenite::tungstenite::Message;
|
|
9
|
+
use tokio_tungstenite::WebSocketStream;
|
|
10
|
+
|
|
11
|
+
/// WS_STATUS_NORMAL_CLOSURE is the normal WebSocket close code (1000).
|
|
12
|
+
pub const WS_STATUS_NORMAL_CLOSURE: u16 = 1000;
|
|
13
|
+
|
|
14
|
+
/// WS_STATUS_GOING_AWAY is the going-away WebSocket close code (1001).
|
|
15
|
+
pub const WS_STATUS_GOING_AWAY: u16 = 1001;
|
|
16
|
+
|
|
17
|
+
/// WS_STATUS_PROTOCOL_ERROR is the protocol error WebSocket close code (1002).
|
|
18
|
+
pub const WS_STATUS_PROTOCOL_ERROR: u16 = 1002;
|
|
19
|
+
|
|
20
|
+
type WsStream = WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
|
|
21
|
+
|
|
22
|
+
type WsHandler = Box<dyn Fn(&[u8]) + Send + Sync>;
|
|
23
|
+
type HandlerMap = Mutex<HashMap<String, Vec<WsHandler>>>;
|
|
24
|
+
|
|
25
|
+
/// TypedWebSocket wraps a tokio-tungstenite connection with a buffered pre-OPEN
|
|
26
|
+
/// send queue (flushed once on first read via AtomicBool) and an event fanout
|
|
27
|
+
/// system that mirrors the TS client's .on() escape hatch.
|
|
28
|
+
pub struct TypedWebSocket {
|
|
29
|
+
conn: WsStream,
|
|
30
|
+
send_buffer: Option<Vec<u8>>,
|
|
31
|
+
once: AtomicBool,
|
|
32
|
+
handlers: HandlerMap,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl TypedWebSocket {
|
|
36
|
+
/// new constructs a TypedWebSocket from an already-dialled stream.
|
|
37
|
+
pub fn new(conn: WsStream) -> Self {
|
|
38
|
+
TypedWebSocket {
|
|
39
|
+
conn,
|
|
40
|
+
send_buffer: None,
|
|
41
|
+
once: AtomicBool::new(false),
|
|
42
|
+
handlers: Mutex::new(HashMap::new()),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// send serialises data to JSON and writes it to the WebSocket connection.
|
|
47
|
+
/// Accepts any serde_json::Value or JSON-serializable payload pre-encoded as Value.
|
|
48
|
+
pub async fn send(&mut self, data: &serde_json::Value) -> Result<(), Error> {
|
|
49
|
+
use futures_util::SinkExt;
|
|
50
|
+
let encoded = serde_json::to_vec(data).map_err(Error::Json)?;
|
|
51
|
+
self.conn
|
|
52
|
+
.send(Message::Text(String::from_utf8_lossy(&encoded).into_owned()))
|
|
53
|
+
.await
|
|
54
|
+
.map_err(|e| Error::Other(e.to_string()))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// send_typed serialises any Serialize value to JSON and writes it to the WebSocket connection.
|
|
58
|
+
pub async fn send_typed<T: Serialize + ?Sized>(&mut self, data: &T) -> Result<(), Error> {
|
|
59
|
+
use futures_util::SinkExt;
|
|
60
|
+
let encoded = serde_json::to_vec(data).map_err(Error::Json)?;
|
|
61
|
+
self.conn
|
|
62
|
+
.send(Message::Text(String::from_utf8_lossy(&encoded).into_owned()))
|
|
63
|
+
.await
|
|
64
|
+
.map_err(|e| Error::Other(e.to_string()))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// close sends a WebSocket close frame with the given status code and reason.
|
|
68
|
+
pub async fn close(&mut self, code: u16, reason: &str) -> Result<(), Error> {
|
|
69
|
+
use futures_util::SinkExt;
|
|
70
|
+
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
|
71
|
+
use tokio_tungstenite::tungstenite::protocol::CloseFrame;
|
|
72
|
+
let frame = CloseFrame {
|
|
73
|
+
code: CloseCode::from(code),
|
|
74
|
+
reason: reason.to_string().into(),
|
|
75
|
+
};
|
|
76
|
+
self.conn
|
|
77
|
+
.send(Message::Close(Some(frame)))
|
|
78
|
+
.await
|
|
79
|
+
.map_err(|e| Error::Other(e.to_string()))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// read reads the next message from the connection.
|
|
83
|
+
/// On the first call, any pre-OPEN buffered send is flushed via AtomicBool compare_exchange.
|
|
84
|
+
pub async fn read(&mut self) -> Result<Vec<u8>, Error> {
|
|
85
|
+
use futures_util::{SinkExt, StreamExt};
|
|
86
|
+
|
|
87
|
+
/* flush send_buffer exactly once on first read — mirrors Go sync.Once */
|
|
88
|
+
if self
|
|
89
|
+
.once
|
|
90
|
+
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
|
|
91
|
+
.is_ok()
|
|
92
|
+
{
|
|
93
|
+
if let Some(buf) = self.send_buffer.take() {
|
|
94
|
+
let _ = self
|
|
95
|
+
.conn
|
|
96
|
+
.send(Message::Text(String::from_utf8_lossy(&buf).into_owned()))
|
|
97
|
+
.await;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
loop {
|
|
102
|
+
match self.conn.next().await {
|
|
103
|
+
None => return Err(Error::Closed { code: 1006, reason: String::new() }),
|
|
104
|
+
Some(Err(e)) => return Err(Error::Other(e.to_string())),
|
|
105
|
+
Some(Ok(msg)) => {
|
|
106
|
+
let data = match msg {
|
|
107
|
+
Message::Text(t) => t.into_bytes(),
|
|
108
|
+
Message::Binary(b) => b,
|
|
109
|
+
Message::Close(frame) => {
|
|
110
|
+
let (code, reason) = match frame {
|
|
111
|
+
Some(f) => (u16::from(f.code), f.reason.into_owned()),
|
|
112
|
+
None => (1005, String::new()),
|
|
113
|
+
};
|
|
114
|
+
return Err(Error::Closed { code, reason })
|
|
115
|
+
}
|
|
116
|
+
_ => continue,
|
|
117
|
+
};
|
|
118
|
+
self.fanout("message", &data);
|
|
119
|
+
return Ok(data);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// on registers an event handler for a named event.
|
|
126
|
+
/// Supported events: "message", "close", "error", "open".
|
|
127
|
+
pub fn on(&mut self, event: &str, handler: WsHandler) {
|
|
128
|
+
let mut handlers = self.handlers.lock().unwrap();
|
|
129
|
+
handlers
|
|
130
|
+
.entry(event.to_string())
|
|
131
|
+
.or_default()
|
|
132
|
+
.push(handler);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn fanout(&self, event: &str, data: &[u8]) {
|
|
136
|
+
let handlers = self.handlers.lock().unwrap();
|
|
137
|
+
if let Some(hs) = handlers.get(event) {
|
|
138
|
+
for h in hs {
|
|
139
|
+
h(data);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createBus } from "../realtime/bus.ts"
|
|
2
|
+
import { ReconnectBuffer } from "../realtime/buffer.ts"
|
|
3
|
+
import { decodeClientFrame, encodeServerFrame } from "../realtime/protocol.ts"
|
|
4
|
+
import type { ServerFrame } from "../realtime/protocol.ts"
|
|
5
|
+
import { createConnContext } from "../realtime/route.ts"
|
|
6
|
+
import type { ConnContext } from "../realtime/route.ts"
|
|
7
|
+
|
|
8
|
+
export type DurableObjectContext = {
|
|
9
|
+
id: { toString(): string }
|
|
10
|
+
storage: {
|
|
11
|
+
getAlarm(): Promise<number | null>
|
|
12
|
+
setAlarm(time: number): Promise<void>
|
|
13
|
+
deleteAlarm(): Promise<void>
|
|
14
|
+
}
|
|
15
|
+
acceptWebSocket(ws: WebSocket): void
|
|
16
|
+
getWebSockets(): WebSocket[]
|
|
17
|
+
getTags(ws: WebSocket): string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type RealtimeHandlerConfig<TEnv = unknown> = {
|
|
21
|
+
ctx: DurableObjectContext
|
|
22
|
+
env: TEnv
|
|
23
|
+
onAuth?: (req: Request) => string | null | Promise<string | null>
|
|
24
|
+
reconnectBuffer?: number
|
|
25
|
+
routes?: Record<string, (c: unknown, conn: ConnContext) => void | Promise<void>>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type RealtimeHandlers = {
|
|
29
|
+
fetch(req: Request): Promise<Response>
|
|
30
|
+
message(ws: WebSocket, msg: ArrayBuffer | string): void | Promise<void>
|
|
31
|
+
close(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise<void>
|
|
32
|
+
error(ws: WebSocket, err: unknown): void | Promise<void>
|
|
33
|
+
alarm(): void | Promise<void>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type Attachment = {
|
|
37
|
+
connId: string
|
|
38
|
+
reconnectToken: string
|
|
39
|
+
userId: string | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_BUFFER_SIZE = 100
|
|
43
|
+
const BUFFER_TTL_MS = 30_000
|
|
44
|
+
|
|
45
|
+
export function createRealtimeHandlers<TEnv = unknown>(
|
|
46
|
+
config: RealtimeHandlerConfig<TEnv>,
|
|
47
|
+
): RealtimeHandlers {
|
|
48
|
+
const bufferSize = config.reconnectBuffer ?? DEFAULT_BUFFER_SIZE
|
|
49
|
+
const buffer = new ReconnectBuffer({ size: bufferSize })
|
|
50
|
+
const bus = createBus()
|
|
51
|
+
|
|
52
|
+
/* Track registered tokens so we can lazily register on hibernation wake */
|
|
53
|
+
const knownTokens = new Set<string>()
|
|
54
|
+
|
|
55
|
+
function ensureToken(token: string): void {
|
|
56
|
+
if (knownTokens.has(token)) return
|
|
57
|
+
/* Token was created before hibernation — re-register with buffer */
|
|
58
|
+
buffer.create(token)
|
|
59
|
+
knownTokens.add(token)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getAttachment(ws: WebSocket): Attachment | null {
|
|
63
|
+
const raw = (ws as unknown as { deserializeAttachment(): string | null }).deserializeAttachment()
|
|
64
|
+
if (!raw) return null
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(raw) as Attachment
|
|
67
|
+
} catch {
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sendFrame(ws: WebSocket, frame: ServerFrame): void {
|
|
73
|
+
(ws as unknown as { send(data: string): void }).send(encodeServerFrame(frame))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getRouteHandler(): ((c: unknown, conn: ConnContext) => void | Promise<void>) | null {
|
|
77
|
+
if (!config.routes) return null
|
|
78
|
+
if (config.routes["/"]) return config.routes["/"]
|
|
79
|
+
const keys = Object.keys(config.routes)
|
|
80
|
+
if (keys.length > 0) return config.routes[keys[0]]
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildConnContext(attachment: Attachment, ws: WebSocket): ConnContext {
|
|
85
|
+
return createConnContext({
|
|
86
|
+
bus,
|
|
87
|
+
closeFn: (reason) => {
|
|
88
|
+
(ws as unknown as { close(code?: number, reason?: string): void }).close(1000, reason ?? "")
|
|
89
|
+
},
|
|
90
|
+
id: attachment.connId,
|
|
91
|
+
sendFn: (payload) => {
|
|
92
|
+
ensureToken(attachment.reconnectToken)
|
|
93
|
+
const id = buffer.push(attachment.reconnectToken, payload)
|
|
94
|
+
sendFrame(ws, { data: payload, id, t: "msg" })
|
|
95
|
+
},
|
|
96
|
+
transport: "ws",
|
|
97
|
+
userId: attachment.userId,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function handleFetch(req: Request): Promise<Response> {
|
|
102
|
+
const upgradeHeader = req.headers.get("upgrade")
|
|
103
|
+
if (!upgradeHeader || upgradeHeader.toLowerCase() !== "websocket") {
|
|
104
|
+
return new Response("Upgrade Required", { status: 426 })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let userId: string | null = null
|
|
108
|
+
if (config.onAuth) {
|
|
109
|
+
userId = await config.onAuth(req)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const pair = new (globalThis as unknown as { WebSocketPair: new () => { 0: WebSocket; 1: WebSocket } }).WebSocketPair()
|
|
113
|
+
const client = pair[0]
|
|
114
|
+
const server = pair[1]
|
|
115
|
+
|
|
116
|
+
config.ctx.acceptWebSocket(server)
|
|
117
|
+
|
|
118
|
+
const connId = crypto.randomUUID()
|
|
119
|
+
const reconnectToken = buffer.create()
|
|
120
|
+
knownTokens.add(reconnectToken)
|
|
121
|
+
|
|
122
|
+
const attachment: Attachment = { connId, reconnectToken, userId }
|
|
123
|
+
;(server as unknown as { serializeAttachment(data: string): void }).serializeAttachment(
|
|
124
|
+
JSON.stringify(attachment),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
sendFrame(server, { reconnectToken, t: "ready" })
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* CF Workers returns a special Response with status 101 and the webSocket property.
|
|
131
|
+
* Standard Response rejects 101, so we construct the object manually.
|
|
132
|
+
*/
|
|
133
|
+
const response = new Response(null, { status: 200 })
|
|
134
|
+
Object.defineProperty(response, "status", { value: 101 })
|
|
135
|
+
Object.defineProperty(response, "webSocket", { value: client })
|
|
136
|
+
return response
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function handleMessage(ws: WebSocket, msg: ArrayBuffer | string): Promise<void> {
|
|
140
|
+
if (typeof msg !== "string") return
|
|
141
|
+
|
|
142
|
+
const attachment = getAttachment(ws)
|
|
143
|
+
if (!attachment) return
|
|
144
|
+
|
|
145
|
+
if (!msg) return
|
|
146
|
+
|
|
147
|
+
const frame = decodeClientFrame(msg)
|
|
148
|
+
if (!frame) return
|
|
149
|
+
|
|
150
|
+
if (frame.t === "ping") {
|
|
151
|
+
sendFrame(ws, { t: "pong" })
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (frame.t === "msg") {
|
|
156
|
+
/* Ensure token is registered (survives hibernation wake) */
|
|
157
|
+
ensureToken(attachment.reconnectToken)
|
|
158
|
+
buffer.push(attachment.reconnectToken, frame.data)
|
|
159
|
+
|
|
160
|
+
const routeHandler = getRouteHandler()
|
|
161
|
+
if (routeHandler) {
|
|
162
|
+
const conn = buildConnContext(attachment, ws)
|
|
163
|
+
await routeHandler(config.env, conn)
|
|
164
|
+
if (conn._handlers.message) {
|
|
165
|
+
conn._handlers.message(frame.data)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (frame.t === "resume") {
|
|
172
|
+
/* Ensure the connection's own token is known */
|
|
173
|
+
ensureToken(attachment.reconnectToken)
|
|
174
|
+
|
|
175
|
+
const replayed = buffer.replay(frame.reconnectToken, frame.lastId)
|
|
176
|
+
if (!replayed) {
|
|
177
|
+
sendFrame(ws, { reason: "server_restart", t: "bye" })
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const entry of replayed) {
|
|
182
|
+
sendFrame(ws, { data: entry.data, id: entry.id, t: "msg" })
|
|
183
|
+
}
|
|
184
|
+
sendFrame(ws, { reconnectToken: attachment.reconnectToken, t: "ready" })
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function handleClose(ws: WebSocket, _code: number, reason: string, _wasClean: boolean): Promise<void> {
|
|
190
|
+
const attachment = getAttachment(ws)
|
|
191
|
+
if (!attachment) return
|
|
192
|
+
|
|
193
|
+
/* Run route handler to get close callback */
|
|
194
|
+
const routeHandler = getRouteHandler()
|
|
195
|
+
if (routeHandler) {
|
|
196
|
+
const conn = buildConnContext(attachment, ws)
|
|
197
|
+
await routeHandler(config.env, conn)
|
|
198
|
+
if (conn._handlers.close) {
|
|
199
|
+
conn._handlers.close(reason)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
bus.unsubscribeAll(attachment.connId)
|
|
204
|
+
bus.removeHandler(attachment.connId)
|
|
205
|
+
|
|
206
|
+
ensureToken(attachment.reconnectToken)
|
|
207
|
+
buffer.disconnect(attachment.reconnectToken)
|
|
208
|
+
|
|
209
|
+
/* Schedule alarm to clean up expired buffers */
|
|
210
|
+
const existingAlarm = await config.ctx.storage.getAlarm()
|
|
211
|
+
if (!existingAlarm) {
|
|
212
|
+
await config.ctx.storage.setAlarm(Date.now() + BUFFER_TTL_MS)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function handleError(ws: WebSocket, _err: unknown): Promise<void> {
|
|
217
|
+
try {
|
|
218
|
+
(ws as unknown as { close(code?: number, reason?: string): void }).close(1011, "internal error")
|
|
219
|
+
} catch {
|
|
220
|
+
/* swallow — ws may already be closed */
|
|
221
|
+
}
|
|
222
|
+
return Promise.resolve()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function handleAlarm(): Promise<void> {
|
|
226
|
+
await config.ctx.storage.deleteAlarm()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
alarm: handleAlarm,
|
|
231
|
+
close: handleClose,
|
|
232
|
+
error: handleError,
|
|
233
|
+
fetch: handleFetch,
|
|
234
|
+
message: handleMessage,
|
|
235
|
+
}
|
|
236
|
+
}
|