diff --git a/app/src/broadcast.ts b/app/src/broadcast.ts
index 7eca8b4..cc2929d 100644
--- a/app/src/broadcast.ts
+++ b/app/src/broadcast.ts
@@ -1,13 +1,19 @@
import { onCleanup } from "solid-js";
-import { Group } from "./database/schema";
+import { Group, IncomingMessage } from "./database/schema";
export const BROADCAST_NAME = "meal";
const broadcast = new BroadcastChannel(BROADCAST_NAME);
-export type BroadcaseMessage = {
- type: "Group created";
- group: Group;
-};
+export type BroadcaseMessage =
+ | {
+ type: "Group created";
+ group: Group;
+ }
+ | {
+ type: "Message received";
+ groupId: string;
+ message: IncomingMessage;
+ };
export function broadcastMessage(message: BroadcaseMessage) {
broadcast.postMessage(message);
diff --git a/app/src/routes/chat.tsx b/app/src/routes/chat.tsx
index 8314ecd..b53ae79 100644
--- a/app/src/routes/chat.tsx
+++ b/app/src/routes/chat.tsx
@@ -1,7 +1,15 @@
import { Navigate, useParams } from "@solidjs/router";
-import { createResource, For, JSX, Show, Suspense } from "solid-js";
+import {
+ createEffect,
+ createResource,
+ For,
+ JSX,
+ Show,
+ Suspense,
+} from "solid-js";
import { getGroup } from "../database";
import { setupCrackle } from "../useCrackle";
+import { useBroadcast } from "../broadcast";
// TODO take this inspiration https://firebasestorage.googleapis.com/v0/b/design-spec/o/projects%2Fgoogle-material-3%2Fimages%2Fly7219l1-1.png?alt=media&token=67ff316b-7515-4e9f-9971-4e580290b1f2
// from https://m3.material.io/foundations/layout/applying-layout/compact#283b4432-e3ee-46df-aa66-9ec87965c6ef
@@ -12,7 +20,26 @@ export default function Chat() {
return ;
}
- const [group] = createResource(parameters.groupId, getGroup);
+ const [group, { mutate }] = createResource(parameters.groupId, getGroup);
+ createEffect(() => {
+ const currentGroup = group();
+ if (currentGroup === undefined) return;
+
+ // Can be used in effects because the internal onCleanup unsubscribes from the event when this effect is cleaned up
+ useBroadcast((event) => {
+ if (
+ event.data.type !== "Message received" ||
+ event.data.groupId !== parameters.groupId
+ )
+ return;
+
+ const message = event.data.message;
+ mutate((group) => {
+ currentGroup.messages.push(message);
+ return group;
+ });
+ });
+ });
async function handleSend(
event: Parameters>[0]
diff --git a/app/src/service-worker/serviceWorker.ts b/app/src/service-worker/serviceWorker.ts
index 97b64e0..87624ad 100644
--- a/app/src/service-worker/serviceWorker.ts
+++ b/app/src/service-worker/serviceWorker.ts
@@ -128,7 +128,7 @@ async function postMessage(friendId: string, body: Uint8Array) {
//TODO error handling
//TODO retry
- await fetch(request);
+ return await fetch(request);
}
function assertNotShared(
@@ -223,7 +223,9 @@ const handler = {
void updateClient(client);
assertNotShared(welcomePackage);
- await postMessage(keyPackage.friend.id, welcomePackage);
+ const response = await postMessage(keyPackage.friend.id, welcomePackage);
+ if (response.status !== 201)
+ throw new Error(`Unexpected status code response ${response.status}`);
},
async sendMessage(message: OutgoingMessage) {
@@ -232,7 +234,7 @@ const handler = {
const client = await getClient;
const body = client.send_message(group.id, {
- sent_at: message.sentAt,
+ sent_at: message.sentAt.toISOString(),
text: message.text,
});
@@ -281,6 +283,7 @@ self.addEventListener("activate", () => {
async function receiveMessage(event: MessageEvent) {
const client = await getClient;
const message = client.process_message(new Uint8Array(event.data));
+ console.debug("Processed message", message.type);
void updateClient(client);
switch (message.type) {
case "Welcome": {
@@ -314,6 +317,11 @@ async function receiveMessage(event: MessageEvent) {
// Assume it is sorted by time
group.messages.push(messageEntry);
await updateGroup(group);
+ broadcastMessage({
+ type: "Message received",
+ groupId: group.id,
+ message: messageEntry,
+ });
}
return;
}
@@ -324,6 +332,7 @@ async function setupWebsocket() {
// Assume client id does not change
const client = await getClient;
+ console.debug("[Service worker] Connecting to socket", client.id);
// https:// is automatically replaced with wss://
const socketUrl = new URL(client.id, messagesUrl);
const socket = new WebSocket(socketUrl);
diff --git a/core/src/lib.rs b/core/src/lib.rs
index e21f78f..e649e94 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -59,6 +59,7 @@ pub struct Friend {
pub struct MessageContent {
/// The time the sender said they supposedly sent the message
#[serde(with = "time::serde::iso8601")]
+ // #[tsify(type = "string")]
sent_at: OffsetDateTime,
text: String,
}
diff --git a/core/src/v2/serializable.rs b/core/src/v2/serializable.rs
index e5fc9f0..8549f4c 100644
--- a/core/src/v2/serializable.rs
+++ b/core/src/v2/serializable.rs
@@ -253,7 +253,8 @@ impl Client {
let mut vector = Vec::new();
vector.push(welcome);
vector.push(message);
- Ok(TlsSliceU8(&vector).tls_serialize_detached()?)
+ // The u16 describes the length of the message e.g. u8 would be max length 255
+ Ok(TlsSliceU16(&vector).tls_serialize_detached()?)
}
fn process_private_message(
@@ -335,7 +336,8 @@ impl Client {
}
pub fn process_message(&mut self, data: &[u8]) -> Result {
- let mut messages = TlsVecU8::::tls_deserialize_exact_bytes(data)?
+ // Have to use TlsVecU16 because the bit length (16) stands for the space reserved to encode the length of the message not the integer stored like in a Vec
+ let mut messages = TlsVecU16::::tls_deserialize_exact_bytes(data)?
.into_vec()
.into_iter();
@@ -369,7 +371,7 @@ impl Client {
// We can batch send messages so we need to wrap it in a collection
let messages = &[message];
- let message = TlsSliceU8(messages);
+ let message = TlsSliceU16(messages);
let serialized = message.tls_serialize_detached()?;
diff --git a/delivery-service/src/main.rs b/delivery-service/src/main.rs
index 3a0aa7c..3f3a2ca 100644
--- a/delivery-service/src/main.rs
+++ b/delivery-service/src/main.rs
@@ -20,6 +20,7 @@ use tower_http::{
set_header::SetResponseHeaderLayer,
set_status::SetStatus,
};
+use tracing::debug;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod extractor;
@@ -124,6 +125,7 @@ async fn create_message(
let channels = state.channels.lock().await;
//TODO think about not leaking if they exist or not
//TODO think about leaking data through timings
+ debug!("New message for client {}", to);
let Some(sender) = channels.get(to.as_ref()) else {
return StatusCode::NOT_FOUND;
};
@@ -136,6 +138,7 @@ async fn create_message(
}
async fn handle_socket(mut socket: WebSocket, State(state): State, client_id: Arc) {
+ debug!("New socket connection for client {}", client_id);
//TODO keep alive
//TODO add client authentication to avoid session hijacking
// Hijackers can deny messages to the client and analyze meta data but not read messages if they don't have the clients credentials