Skip to content

Commit 0baf519

Browse files
committed
chore: storing inbox messages
1 parent d801946 commit 0baf519

File tree

5 files changed

+423
-7
lines changed

5 files changed

+423
-7
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { prisma } from '../prisma';
2+
3+
export async function listAccountInboxes({ accountId }: { accountId: string }) {
4+
const inboxes = await prisma.accountInbox.findMany({
5+
where: { accountId },
6+
select: {
7+
id: true,
8+
isPublic: true,
9+
authPolicy: true,
10+
encryptionPublicKey: true,
11+
account: {
12+
select: {
13+
id: true,
14+
},
15+
},
16+
signatureHex: true,
17+
signatureRecovery: true,
18+
},
19+
});
20+
return inboxes.map((inbox) => {
21+
return {
22+
inboxId: inbox.id,
23+
accountId: inbox.account.id,
24+
isPublic: inbox.isPublic,
25+
authPolicy: inbox.authPolicy,
26+
encryptionPublicKey: inbox.encryptionPublicKey,
27+
signature: {
28+
hex: inbox.signatureHex,
29+
recovery: inbox.signatureRecovery,
30+
},
31+
};
32+
});
33+
}

apps/server/src/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,26 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req
660660
}
661661
case 'create-account-inbox': {
662662
// TODO
663+
// Check that the signature is valid for the corresponding accountId
664+
// Create the inbox (if it doesn't exist)
665+
// Broadcast the inbox to other clients from the same account
666+
break;
667+
}
668+
case 'get-latest-space-inbox-messages': {
669+
// TODO
670+
break;
671+
}
672+
case 'get-latest-account-inbox-messages': {
673+
// TODO
674+
break;
675+
}
676+
case 'get-account-inboxes': {
677+
const inboxes = await listAccountInboxes({ accountId });
678+
const outgoingMessage: Messages.ResponseAccountInboxes = {
679+
type: 'account-inboxes',
680+
inboxes,
681+
};
682+
webSocket.send(Messages.serialize(outgoingMessage));
663683
break;
664684
}
665685
case 'create-update': {

packages/hypergraph-react/src/HypergraphAppContext.tsx

Lines changed: 194 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { DocHandle } from '@automerge/automerge-repo';
66
import { RepoContext } from '@automerge/automerge-repo-react-hooks';
77
import {
88
Identity,
9+
InboxMessageStorageEntry,
910
Inboxes,
1011
Key,
1112
Messages,
@@ -58,6 +59,7 @@ export type HypergraphAppCtx = {
5859
params: Readonly<{ isPublic: boolean; authPolicy: Messages.InboxSenderAuthPolicy }>,
5960
): Promise<unknown>;
6061
getLatestAccountInboxMessages(params: Readonly<{ accountId: string; inboxId: string }>): Promise<unknown>;
62+
getOwnAccountInboxes(): Promise<unknown>;
6163
listPublicAccountInboxes(params: Readonly<{ accountId: string }>): Promise<unknown>;
6264
getAccountInbox(params: Readonly<{ accountId: string; inboxId: string }>): Promise<unknown>;
6365
sendAccountInboxMessage(
@@ -67,6 +69,7 @@ export type HypergraphAppCtx = {
6769
inboxId: string;
6870
encryptionPublicKey: string;
6971
signaturePrivateKey: string | null;
72+
authorAccountId: string;
7073
}>,
7174
): Promise<unknown>;
7275
listSpaces(): void;
@@ -114,6 +117,9 @@ export const HypergraphAppContext = createContext<HypergraphAppCtx>({
114117
async createAccountInbox() {
115118
throw new Error('createAccountInbox is missing');
116119
},
120+
async getOwnAccountInboxes() {
121+
throw new Error('getOwnAccountInboxes is missing');
122+
},
117123
async getLatestAccountInboxMessages() {
118124
throw new Error('getLatestAccountInboxMessages is missing');
119125
},
@@ -448,6 +454,13 @@ export function HypergraphAppProvider({
448454
return {
449455
...message,
450456
plaintext: decryptedMessage,
457+
signature: message.signature
458+
? {
459+
hex: message.signature.hex,
460+
recovery: message.signature.recovery,
461+
}
462+
: null,
463+
authorAccountId: message.authorAccountId ?? null,
451464
};
452465
});
453466
// TODO filter messages checking the signatures are valid for the corresponding authorAccountId
@@ -552,6 +565,7 @@ export function HypergraphAppProvider({
552565
),
553566
lastMessageClock: 0,
554567
messages: [],
568+
seenMessageIds: new Set<string>(),
555569
};
556570
store.send({
557571
type: 'setSpaceInbox',
@@ -608,9 +622,18 @@ export function HypergraphAppProvider({
608622
console.error('Invalid inbox creator', response.inbox);
609623
return;
610624
}
625+
626+
let lastMessageClock = 0;
627+
const messages: InboxMessageStorageEntry[] = [];
628+
611629
store.send({
612630
type: 'setAccountInbox',
613-
inbox: response.inbox,
631+
inbox: {
632+
...response.inbox,
633+
messages,
634+
lastMessageClock,
635+
seenMessageIds: new Set<string>(),
636+
},
614637
});
615638
break;
616639
}
@@ -622,6 +645,109 @@ export function HypergraphAppProvider({
622645
// TODO: validate signature and decrypt message sealed box, then store it
623646
break;
624647
}
648+
case 'account-inboxes': {
649+
response.inboxes.map((inbox) => {
650+
store.send({
651+
type: 'setAccountInbox',
652+
inbox: {
653+
...inbox,
654+
messages: [],
655+
lastMessageClock: 0,
656+
seenMessageIds: new Set<string>(),
657+
},
658+
});
659+
});
660+
break;
661+
}
662+
case 'account-inbox-messages': {
663+
// Validate the signature of the inbox corresponds to the current account's identity
664+
if (!keys.signaturePrivateKey) {
665+
console.error('No signature private key found to process account inbox');
666+
return;
667+
}
668+
const inbox = store.getSnapshot().context.accountInboxes.find((i) => i.inboxId === response.inboxId);
669+
if (!inbox) {
670+
console.error('Inbox not found', response.inboxId);
671+
return;
672+
}
673+
674+
let lastMessageClock = 0;
675+
const messages = response.messages.map((message) => {
676+
const decryptedMessage = Inboxes.decryptInboxMessage({
677+
ciphertext: message.ciphertext,
678+
nonce: message.nonce,
679+
ephemeralPublicKey: message.ephemeralPublicKey,
680+
encryptionPrivateKey,
681+
});
682+
if (message.createdAt > lastMessageClock) {
683+
lastMessageClock = message.createdAt;
684+
}
685+
return {
686+
...message,
687+
plaintext: decryptedMessage,
688+
signature: message.signature
689+
? {
690+
hex: message.signature.hex,
691+
recovery: message.signature.recovery,
692+
}
693+
: null,
694+
authorAccountId: message.authorAccountId ?? null,
695+
};
696+
});
697+
698+
store.send({
699+
type: 'setAccountInboxMessages',
700+
inboxId: response.inboxId,
701+
messages,
702+
lastMessageClock,
703+
});
704+
break;
705+
}
706+
case 'space-inbox-messages': {
707+
const space = store.getSnapshot().context.spaces.find((s) => s.id === response.spaceId);
708+
if (!space) {
709+
console.error('Space not found', response.spaceId);
710+
return;
711+
}
712+
const inbox = space.inboxes.find((i) => i.inboxId === response.inboxId);
713+
if (!inbox) {
714+
console.error('Inbox not found', response.inboxId);
715+
return;
716+
}
717+
718+
let lastMessageClock = 0;
719+
const messages = response.messages.map((message) => {
720+
const decryptedMessage = Inboxes.decryptInboxMessage({
721+
ciphertext: message.ciphertext,
722+
nonce: message.nonce,
723+
ephemeralPublicKey: message.ephemeralPublicKey,
724+
encryptionPrivateKey,
725+
});
726+
if (message.createdAt > lastMessageClock) {
727+
lastMessageClock = message.createdAt;
728+
}
729+
return {
730+
...message,
731+
signature: message.signature
732+
? {
733+
hex: message.signature.hex,
734+
recovery: message.signature.recovery,
735+
}
736+
: null,
737+
authorAccountId: message.authorAccountId ?? null,
738+
plaintext: decryptedMessage,
739+
};
740+
});
741+
742+
store.send({
743+
type: 'setSpaceInboxMessages',
744+
spaceId: response.spaceId,
745+
inboxId: response.inboxId,
746+
messages,
747+
lastMessageClock,
748+
});
749+
break;
750+
}
625751
default: {
626752
Utils.assertExhaustive(response);
627753
}
@@ -759,7 +885,7 @@ export function HypergraphAppProvider({
759885
console.error('Space not found', spaceId);
760886
return;
761887
}
762-
const inbox = space.inboxes.find((i) => i.id === inboxId);
888+
const inbox = space.inboxes.find((i) => i.inboxId === inboxId);
763889
if (!inbox) {
764890
console.error('Inbox not found', inboxId);
765891
return;
@@ -790,6 +916,35 @@ export function HypergraphAppProvider({
790916
[syncServerUri],
791917
);
792918

919+
const sendSpaceInboxMessageForContext = useCallback(
920+
async ({
921+
spaceId,
922+
inboxId,
923+
message,
924+
encryptionPublicKey,
925+
signaturePrivateKey,
926+
authorAccountId,
927+
}: Readonly<{
928+
spaceId: string;
929+
inboxId: string;
930+
message: string;
931+
encryptionPublicKey: string;
932+
signaturePrivateKey: string;
933+
authorAccountId: string;
934+
}>) => {
935+
return await Inboxes.sendSpaceInboxMessage({
936+
spaceId,
937+
inboxId,
938+
message,
939+
encryptionPublicKey,
940+
signaturePrivateKey,
941+
syncServerUri,
942+
authorAccountId,
943+
});
944+
},
945+
[syncServerUri],
946+
);
947+
793948
const createAccountInboxForContext = useCallback(
794949
async ({ isPublic, authPolicy }: Readonly<{ isPublic: boolean; authPolicy: Messages.InboxSenderAuthPolicy }>) => {
795950
if (!accountId) {
@@ -833,6 +988,13 @@ export function HypergraphAppProvider({
833988
[websocketConnection],
834989
);
835990

991+
const getOwnAccountInboxesForContext = useCallback(async () => {
992+
const message: Messages.RequestGetAccountInboxes = {
993+
type: 'get-account-inboxes',
994+
};
995+
websocketConnection?.send(Messages.serialize(message));
996+
}, [websocketConnection]);
997+
836998
const listPublicAccountInboxesForContext = useCallback(
837999
async ({ accountId }: Readonly<{ accountId: string }>) => {
8381000
return await Inboxes.listPublicAccountInboxes({ accountId, syncServerUri });
@@ -847,6 +1009,35 @@ export function HypergraphAppProvider({
8471009
[syncServerUri],
8481010
);
8491011

1012+
const sendAccountInboxMessageForContext = useCallback(
1013+
async ({
1014+
message,
1015+
accountId,
1016+
inboxId,
1017+
encryptionPublicKey,
1018+
signaturePrivateKey,
1019+
authorAccountId,
1020+
}: Readonly<{
1021+
message: string;
1022+
accountId: string;
1023+
inboxId: string;
1024+
encryptionPublicKey: string;
1025+
signaturePrivateKey: string | null;
1026+
authorAccountId: string;
1027+
}>) => {
1028+
return await Inboxes.sendAccountInboxMessage({
1029+
message,
1030+
accountId,
1031+
inboxId,
1032+
encryptionPublicKey,
1033+
signaturePrivateKey,
1034+
syncServerUri,
1035+
authorAccountId,
1036+
});
1037+
},
1038+
[syncServerUri],
1039+
);
1040+
8501041
const acceptInvitationForContext = useCallback(
8511042
async ({
8521043
invitation,
@@ -1007,6 +1198,7 @@ export function HypergraphAppProvider({
10071198
sendSpaceInboxMessage: sendSpaceInboxMessageForContext,
10081199
createAccountInbox: createAccountInboxForContext,
10091200
getLatestAccountInboxMessages: getLatestAccountInboxMessagesForContext,
1201+
getOwnAccountInboxes: getOwnAccountInboxesForContext,
10101202
listPublicAccountInboxes: listPublicAccountInboxesForContext,
10111203
getAccountInbox: getAccountInboxForContext,
10121204
sendAccountInboxMessage: sendAccountInboxMessageForContext,

0 commit comments

Comments
 (0)