use std::cell::RefCell;
use foundation_deployment_cloudflare::workers::durable_object::{do_broadcast, do_connect};
use foundation_deployment_cloudflare::workers::websocket::{
accept_websocket, close_socket, SignalrHandshaker,
};
use worker::{
durable_object, DurableObject, Env, Request, Response, Result as WorkerResult, State,
WebSocket, WebSocketIncomingMessage,
};
use crate::core::notifications::{
decode_signalr_frame, encode_signalr_notification, Notification, UpdateType,
};
#[durable_object]
pub struct SignalRHub {
handshaker: RefCell<SignalrHandshaker>,
messages: RefCell<u64>,
state: State,
#[allow(dead_code)]
env: Env,
}
impl DurableObject for SignalRHub {
fn new(state: State, env: Env) -> Self {
Self {
handshaker: RefCell::new(SignalrHandshaker::new()),
messages: RefCell::new(0),
state,
env,
}
}
async fn fetch(&self, mut req: Request) -> WorkerResult<Response> {
let path = req.path();
let method = req.method().to_string().to_uppercase();
match (method.as_str(), path.as_str()) {
("GET", "/ws") => {
let (server, resp) = accept_websocket()?;
do_connect(&self.state, &server, &[])?;
Ok(resp)
}
("POST", "/notify") => {
let body = req.text().await.unwrap_or_default();
match serde_json::from_str::<(Option<serde_json::Value>, i32)>(&body) {
Ok((payload, update_type)) => {
let notif = Notification::new(
match update_type {
1 => UpdateType::SyncCipherUpdate,
2 => UpdateType::SyncCipherCreate,
3 => UpdateType::SyncLoginDelete,
4 => UpdateType::SyncFolderDelete,
5 => UpdateType::SyncCiphers,
6 => UpdateType::SyncVault,
7 => UpdateType::SyncOrgKeys,
8 => UpdateType::SyncFolderCreate,
9 => UpdateType::SyncFolderUpdate,
10 => UpdateType::SyncCipherDelete,
11 => UpdateType::SyncSettings,
12 => UpdateType::SyncLogOut,
14 => UpdateType::SyncSendUpdate,
15 => UpdateType::SyncSendDelete,
_ => UpdateType::SyncVault,
},
payload,
);
self.broadcast(¬if);
}
Err(_) => {
return Response::error("invalid notification payload".to_string(), 400);
}
}
Response::ok("ok")
}
("GET", "/health") => {
let msgs = *self.messages.borrow();
Response::from_json(&serde_json::json!({
"messages": msgs,
"sockets": foundation_deployment_cloudflare::workers::durable_object::connected_count(&self.state, None),
}))
}
_ => Response::error("not found".to_string(), 404),
}
}
async fn websocket_message(
&self,
ws: WebSocket,
message: WebSocketIncomingMessage,
) -> WorkerResult<()> {
match message {
WebSocketIncomingMessage::String(text) => {
let mut h = self.handshaker.borrow_mut();
h.process(&ws, &text)?;
}
WebSocketIncomingMessage::Binary(data) => {
if !self.handshaker.borrow().is_connected() {
return Ok(()); }
if let Some((msg_type, _payload)) = decode_signalr_frame(&data) {
if msg_type == 6 {
let _ = ws.send_with_bytes(vec![0x91, 0x06]);
}
*self.messages.borrow_mut() += 1;
}
}
}
Ok(())
}
async fn websocket_close(
&self,
ws: WebSocket,
code: usize,
reason: String,
was_clean: bool,
) -> WorkerResult<()> {
let _ = (code, reason, was_clean);
close_socket(&ws, None, None);
Ok(())
}
async fn websocket_error(&self, _ws: WebSocket, _error: worker::Error) -> WorkerResult<()> {
Ok(())
}
async fn alarm(&self) -> WorkerResult<Response> {
do_broadcast(&self.state, &[0x91, 0x06]);
Response::ok("pinged")
}
}
impl SignalRHub {
fn broadcast(&self, notif: &Notification) {
let frame = match encode_signalr_notification(notif) {
Ok(f) => f,
Err(_) => return,
};
do_broadcast(&self.state, &frame);
}
}