diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index db60ac58..78d93da0 100755 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -348,16 +348,21 @@ app.post('/connect/app-identity', async (req, res) => { app.get('/whoami', async (req, res) => { console.log('GET whoami'); - const sessionToken = req.headers.authorization?.split(' ')[1]; - if (!sessionToken) { - res.status(401).send('Unauthorized'); - return; - } try { - const { accountAddress } = await getAppIdentityBySessionToken({ sessionToken }); - res.status(200).send(accountAddress); + const sessionToken = req.headers.authorization?.split(' ')[1]; + if (!sessionToken) { + res.status(401).send('Unauthorized'); + return; + } + try { + const { accountAddress } = await getAppIdentityBySessionToken({ sessionToken }); + res.status(200).send(accountAddress); + } catch (error) { + res.status(401).send('Unauthorized'); + } } catch (error) { - res.status(401).send('Unauthorized'); + console.error('Error getting whoami:', error); + res.status(401).json({ message: 'Unauthorized' }); } }); @@ -388,169 +393,199 @@ app.get('/identity', async (req, res) => { app.get('/spaces/:spaceId/inboxes', async (req, res) => { console.log('GET spaces/:spaceId/inboxes'); - const spaceId = req.params.spaceId; - const inboxes = await listPublicSpaceInboxes({ spaceId }); - const outgoingMessage: Messages.ResponseListSpaceInboxesPublic = { - inboxes, - }; - res.status(200).send(outgoingMessage); + try { + const spaceId = req.params.spaceId; + const inboxes = await listPublicSpaceInboxes({ spaceId }); + const outgoingMessage: Messages.ResponseListSpaceInboxesPublic = { + inboxes, + }; + res.status(200).send(outgoingMessage); + } catch (error) { + console.error('Error getting spaces/:spaceId/inboxes:', error); + res.status(500).json({ message: 'Internal server error' }); + } }); app.get('/spaces/:spaceId/inboxes/:inboxId', async (req, res) => { console.log('GET spaces/:spaceId/inboxes/:inboxId'); - const spaceId = req.params.spaceId; - const inboxId = req.params.inboxId; - const inbox = await getSpaceInbox({ spaceId, inboxId }); - const outgoingMessage: Messages.ResponseSpaceInboxPublic = { - inbox, - }; - res.status(200).send(outgoingMessage); + try { + const spaceId = req.params.spaceId; + const inboxId = req.params.inboxId; + const inbox = await getSpaceInbox({ spaceId, inboxId }); + const outgoingMessage: Messages.ResponseSpaceInboxPublic = { + inbox, + }; + res.status(200).send(outgoingMessage); + } catch (error) { + console.error('Error getting spaces/:spaceId/inboxes/:inboxId:', error); + res.status(500).json({ message: 'Internal server error' }); + } }); app.post('/spaces/:spaceId/inboxes/:inboxId/messages', async (req, res) => { console.log('POST spaces/:spaceId/inboxes/:inboxId/messages'); - const spaceId = req.params.spaceId; - const inboxId = req.params.inboxId; - const message = Schema.decodeUnknownSync(Messages.RequestCreateSpaceInboxMessage)(req.body); - let spaceInbox: Messages.SpaceInboxPublic; try { - spaceInbox = await getSpaceInbox({ spaceId, inboxId }); - } catch (error) { - res.status(404).send({ error: 'Inbox not found' }); - return; - } + const spaceId = req.params.spaceId; + const inboxId = req.params.inboxId; + const message = Schema.decodeUnknownSync(Messages.RequestCreateSpaceInboxMessage)(req.body); + let spaceInbox: Messages.SpaceInboxPublic; + try { + spaceInbox = await getSpaceInbox({ spaceId, inboxId }); + } catch (error) { + res.status(404).send({ error: 'Inbox not found' }); + return; + } - switch (spaceInbox.authPolicy) { - case 'requires_auth': - if (!message.signature || !message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress required' }); + switch (spaceInbox.authPolicy) { + case 'requires_auth': + if (!message.signature || !message.authorAccountAddress) { + res.status(400).send({ error: 'Signature and authorAccountAddress required' }); + return; + } + break; + case 'anonymous': + if (message.signature || message.authorAccountAddress) { + res.status(400).send({ error: 'Signature and authorAccountAddress not allowed' }); + return; + } + break; + case 'optional_auth': + if ( + (message.signature && !message.authorAccountAddress) || + (!message.signature && message.authorAccountAddress) + ) { + res.status(400).send({ error: 'Signature and authorAccountAddress must be provided together' }); + return; + } + break; + default: + // This shouldn't happen + res.status(500).send({ error: 'Unknown auth policy' }); return; - } - break; - case 'anonymous': - if (message.signature || message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress not allowed' }); + } + + if (message.signature && message.authorAccountAddress) { + // Recover the public key from the signature + const authorPublicKey = Inboxes.recoverSpaceInboxMessageSigner(message, spaceId, inboxId); + + // Check if this public key corresponds to a user's identity + let authorIdentity: GetIdentityResult; + try { + authorIdentity = await getConnectIdentity({ connectSignaturePublicKey: authorPublicKey }); + } catch (error) { + res.status(403).send({ error: 'Not authorized to post to this inbox' }); return; } - break; - case 'optional_auth': - if ( - (message.signature && !message.authorAccountAddress) || - (!message.signature && message.authorAccountAddress) - ) { - res.status(400).send({ error: 'Signature and authorAccountAddress must be provided together' }); + if (authorIdentity.accountAddress !== message.authorAccountAddress) { + res.status(403).send({ error: 'Not authorized to post to this inbox' }); return; } - break; - default: - // This shouldn't happen - res.status(500).send({ error: 'Unknown auth policy' }); - return; - } - - if (message.signature && message.authorAccountAddress) { - // Recover the public key from the signature - const authorPublicKey = Inboxes.recoverSpaceInboxMessageSigner(message, spaceId, inboxId); - - // Check if this public key corresponds to a user's identity - let authorIdentity: GetIdentityResult; - try { - authorIdentity = await getConnectIdentity({ connectSignaturePublicKey: authorPublicKey }); - } catch (error) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; - } - if (authorIdentity.accountAddress !== message.authorAccountAddress) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; } + const createdMessage = await createSpaceInboxMessage({ spaceId, inboxId, message }); + res.status(200).send({}); + broadcastSpaceInboxMessage({ spaceId, inboxId, message: createdMessage }); + } catch (error) { + console.error('Error posting spaces/:spaceId/inboxes/:inboxId/messages:', error); + res.status(500).json({ message: 'Internal server error' }); } - const createdMessage = await createSpaceInboxMessage({ spaceId, inboxId, message }); - res.status(200).send({}); - broadcastSpaceInboxMessage({ spaceId, inboxId, message: createdMessage }); }); app.get('/accounts/:accountAddress/inboxes', async (req, res) => { console.log('GET accounts/:accountAddress/inboxes'); - const accountAddress = req.params.accountAddress; - const inboxes = await listPublicAccountInboxes({ accountAddress }); - const outgoingMessage: Messages.ResponseListAccountInboxesPublic = { - inboxes, - }; - res.status(200).send(outgoingMessage); + try { + const accountAddress = req.params.accountAddress; + const inboxes = await listPublicAccountInboxes({ accountAddress }); + const outgoingMessage: Messages.ResponseListAccountInboxesPublic = { + inboxes, + }; + res.status(200).send(outgoingMessage); + } catch (error) { + console.error('Error getting accounts/:accountAddress/inboxes:', error); + res.status(500).json({ message: 'Internal server error' }); + } }); app.get('/accounts/:accountAddress/inboxes/:inboxId', async (req, res) => { console.log('GET accounts/:accountAddress/inboxes/:inboxId'); - const accountAddress = req.params.accountAddress; - const inboxId = req.params.inboxId; - const inbox = await getAccountInbox({ accountAddress, inboxId }); - const outgoingMessage: Messages.ResponseAccountInboxPublic = { - inbox, - }; - res.status(200).send(outgoingMessage); + try { + const accountAddress = req.params.accountAddress; + const inboxId = req.params.inboxId; + const inbox = await getAccountInbox({ accountAddress, inboxId }); + const outgoingMessage: Messages.ResponseAccountInboxPublic = { + inbox, + }; + res.status(200).send(outgoingMessage); + } catch (error) { + console.error('Error getting accounts/:accountAddress/inboxes/:inboxId:', error); + res.status(500).json({ message: 'Internal server error' }); + } }); app.post('/accounts/:accountAddress/inboxes/:inboxId/messages', async (req, res) => { console.log('POST accounts/:accountAddress/inboxes/:inboxId/messages'); - const accountAddress = req.params.accountAddress; - const inboxId = req.params.inboxId; - const message = Schema.decodeUnknownSync(Messages.RequestCreateAccountInboxMessage)(req.body); - let accountInbox: Messages.AccountInboxPublic; try { - accountInbox = await getAccountInbox({ accountAddress, inboxId }); - } catch (error) { - res.status(404).send({ error: 'Inbox not found' }); - return; - } + const accountAddress = req.params.accountAddress; + const inboxId = req.params.inboxId; + const message = Schema.decodeUnknownSync(Messages.RequestCreateAccountInboxMessage)(req.body); + let accountInbox: Messages.AccountInboxPublic; + try { + accountInbox = await getAccountInbox({ accountAddress, inboxId }); + } catch (error) { + res.status(404).send({ error: 'Inbox not found' }); + return; + } - switch (accountInbox.authPolicy) { - case 'requires_auth': - if (!message.signature || !message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress required' }); + switch (accountInbox.authPolicy) { + case 'requires_auth': + if (!message.signature || !message.authorAccountAddress) { + res.status(400).send({ error: 'Signature and authorAccountAddress required' }); + return; + } + break; + case 'anonymous': + if (message.signature || message.authorAccountAddress) { + res.status(400).send({ error: 'Signature and authorAccountAddress not allowed' }); + return; + } + break; + case 'optional_auth': + if ( + (message.signature && !message.authorAccountAddress) || + (!message.signature && message.authorAccountAddress) + ) { + res.status(400).send({ error: 'Signature and authorAccountAddress must be provided together' }); + return; + } + break; + default: + // This shouldn't happen + res.status(500).send({ error: 'Unknown auth policy' }); return; - } - break; - case 'anonymous': - if (message.signature || message.authorAccountAddress) { - res.status(400).send({ error: 'Signature and authorAccountAddress not allowed' }); + } + if (message.signature && message.authorAccountAddress) { + // Recover the public key from the signature + const authorPublicKey = Inboxes.recoverAccountInboxMessageSigner(message, accountAddress, inboxId); + + // Check if this public key corresponds to a user's identity + let authorIdentity: GetIdentityResult; + try { + authorIdentity = await getConnectIdentity({ connectSignaturePublicKey: authorPublicKey }); + } catch (error) { + res.status(403).send({ error: 'Not authorized to post to this inbox' }); return; } - break; - case 'optional_auth': - if ( - (message.signature && !message.authorAccountAddress) || - (!message.signature && message.authorAccountAddress) - ) { - res.status(400).send({ error: 'Signature and authorAccountAddress must be provided together' }); + if (authorIdentity.accountAddress !== message.authorAccountAddress) { + res.status(403).send({ error: 'Not authorized to post to this inbox' }); return; } - break; - default: - // This shouldn't happen - res.status(500).send({ error: 'Unknown auth policy' }); - return; - } - if (message.signature && message.authorAccountAddress) { - // Recover the public key from the signature - const authorPublicKey = Inboxes.recoverAccountInboxMessageSigner(message, accountAddress, inboxId); - - // Check if this public key corresponds to a user's identity - let authorIdentity: GetIdentityResult; - try { - authorIdentity = await getConnectIdentity({ connectSignaturePublicKey: authorPublicKey }); - } catch (error) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; - } - if (authorIdentity.accountAddress !== message.authorAccountAddress) { - res.status(403).send({ error: 'Not authorized to post to this inbox' }); - return; } + const createdMessage = await createAccountInboxMessage({ accountAddress, inboxId, message }); + res.status(200).send({}); + broadcastAccountInboxMessage({ accountAddress, inboxId, message: createdMessage }); + } catch (error) { + console.error('Error posting accounts/:accountAddress/inboxes/:inboxId/messages:', error); + res.status(500).json({ message: 'Internal server error' }); } - const createdMessage = await createAccountInboxMessage({ accountAddress, inboxId, message }); - res.status(200).send({}); - broadcastAccountInboxMessage({ accountAddress, inboxId, message: createdMessage }); }); const server = app.listen(PORT, () => { @@ -562,17 +597,21 @@ function broadcastSpaceEvents({ event, currentClient, }: { spaceId: string; event: SpaceEvents.SpaceEvent; currentClient: CustomWebSocket }) { - for (const client of webSocketServer.clients as Set) { - if (currentClient === client) continue; + try { + for (const client of webSocketServer.clients as Set) { + if (currentClient === client) continue; - const outgoingMessage: Messages.ResponseSpaceEvent = { - type: 'space-event', - spaceId, - event, - }; - if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { - client.send(Messages.serialize(outgoingMessage)); + const outgoingMessage: Messages.ResponseSpaceEvent = { + type: 'space-event', + spaceId, + event, + }; + if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { + client.send(Messages.serialize(outgoingMessage)); + } } + } catch (error) { + console.error('Error broadcasting space events:', error); } } @@ -581,17 +620,21 @@ function broadcastUpdates({ updates, currentClient, }: { spaceId: string; updates: Messages.Updates; currentClient: CustomWebSocket }) { - for (const client of webSocketServer.clients as Set) { - if (currentClient === client) continue; + try { + for (const client of webSocketServer.clients as Set) { + if (currentClient === client) continue; - const outgoingMessage: Messages.ResponseUpdatesNotification = { - type: 'updates-notification', - updates, - spaceId, - }; - if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { - client.send(Messages.serialize(outgoingMessage)); + const outgoingMessage: Messages.ResponseUpdatesNotification = { + type: 'updates-notification', + updates, + spaceId, + }; + if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { + client.send(Messages.serialize(outgoingMessage)); + } } + } catch (error) { + console.error('Error broadcasting updates:', error); } } @@ -600,28 +643,36 @@ function broadcastSpaceInboxMessage({ inboxId, message, }: { spaceId: string; inboxId: string; message: Messages.InboxMessage }) { - const outgoingMessage: Messages.ResponseSpaceInboxMessage = { - type: 'space-inbox-message', - spaceId, - inboxId, - message, - }; - for (const client of webSocketServer.clients as Set) { - if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { - client.send(Messages.serialize(outgoingMessage)); + try { + const outgoingMessage: Messages.ResponseSpaceInboxMessage = { + type: 'space-inbox-message', + spaceId, + inboxId, + message, + }; + for (const client of webSocketServer.clients as Set) { + if (client.readyState === WebSocket.OPEN && client.subscribedSpaces.has(spaceId)) { + client.send(Messages.serialize(outgoingMessage)); + } } + } catch (error) { + console.error('Error broadcasting space inbox message:', error); } } function broadcastAccountInbox({ inbox }: { inbox: Messages.AccountInboxPublic }) { - const outgoingMessage: Messages.ResponseAccountInbox = { - type: 'account-inbox', - inbox, - }; - for (const client of webSocketServer.clients as Set) { - if (client.readyState === WebSocket.OPEN && client.accountAddress === inbox.accountAddress) { - client.send(Messages.serialize(outgoingMessage)); + try { + const outgoingMessage: Messages.ResponseAccountInbox = { + type: 'account-inbox', + inbox, + }; + for (const client of webSocketServer.clients as Set) { + if (client.readyState === WebSocket.OPEN && client.accountAddress === inbox.accountAddress) { + client.send(Messages.serialize(outgoingMessage)); + } } + } catch (error) { + console.error('Error broadcasting account inbox:', error); } } @@ -630,16 +681,20 @@ function broadcastAccountInboxMessage({ inboxId, message, }: { accountAddress: string; inboxId: string; message: Messages.InboxMessage }) { - const outgoingMessage: Messages.ResponseAccountInboxMessage = { - type: 'account-inbox-message', - accountAddress, - inboxId, - message, - }; - for (const client of webSocketServer.clients as Set) { - if (client.readyState === WebSocket.OPEN && client.accountAddress === accountAddress) { - client.send(Messages.serialize(outgoingMessage)); + try { + const outgoingMessage: Messages.ResponseAccountInboxMessage = { + type: 'account-inbox-message', + accountAddress, + inboxId, + message, + }; + for (const client of webSocketServer.clients as Set) { + if (client.readyState === WebSocket.OPEN && client.accountAddress === accountAddress) { + client.send(Messages.serialize(outgoingMessage)); + } } + } catch (error) { + console.error('Error broadcasting account inbox message:', error); } } @@ -667,265 +722,273 @@ webSocketServer.on('connection', async (webSocket: CustomWebSocket, request: Req console.log('Account Address:', accountAddress); webSocket.subscribedSpaces = new Set(); + webSocket.on('error', (error) => { + console.error('WebSocket error:', error); + }); + console.log('Connection established', accountAddress); webSocket.on('message', async (message) => { - const rawData = Messages.deserialize(message.toString()); - const result = decodeRequestMessage(rawData); - if (result._tag === 'Right') { - const data = result.right; - switch (data.type) { - case 'subscribe-space': { - const space = await getSpace({ accountAddress, spaceId: data.id }); - const outgoingMessage: Messages.ResponseSpace = { - ...space, - type: 'space', - }; - webSocket.subscribedSpaces.add(data.id); - webSocket.send(Messages.serialize(outgoingMessage)); - break; - } - case 'list-spaces': { - const spaces = await listSpacesByAppIdentity({ appIdentityAddress }); - const outgoingMessage: Messages.ResponseListSpaces = { type: 'list-spaces', spaces: spaces }; - webSocket.send(Messages.serialize(outgoingMessage)); - break; - } - case 'list-invitations': { - const invitations = await listInvitations({ accountAddress }); - const outgoingMessage: Messages.ResponseListInvitations = { - type: 'list-invitations', - invitations: invitations, - }; - webSocket.send(Messages.serialize(outgoingMessage)); - break; - } - case 'create-space-event': { - const getVerifiedIdentity = (accountAddressToFetch: string) => { - console.log( - 'TODO getVerifiedIdentity should work for app identities', - accountAddressToFetch, - accountAddress, - ); - if (accountAddressToFetch !== accountAddress) { - return Effect.fail(new Identity.InvalidIdentityError()); - } - - return Effect.gen(function* () { - const identity = yield* Effect.tryPromise({ - try: () => getConnectIdentity({ accountAddress: accountAddressToFetch }), - catch: () => new Identity.InvalidIdentityError(), + try { + const rawData = Messages.deserialize(message.toString()); + const result = decodeRequestMessage(rawData); + if (result._tag === 'Right') { + const data = result.right; + switch (data.type) { + case 'subscribe-space': { + const space = await getSpace({ accountAddress, spaceId: data.id }); + const outgoingMessage: Messages.ResponseSpace = { + ...space, + type: 'space', + }; + webSocket.subscribedSpaces.add(data.id); + webSocket.send(Messages.serialize(outgoingMessage)); + break; + } + case 'list-spaces': { + const spaces = await listSpacesByAppIdentity({ appIdentityAddress }); + const outgoingMessage: Messages.ResponseListSpaces = { type: 'list-spaces', spaces: spaces }; + webSocket.send(Messages.serialize(outgoingMessage)); + break; + } + case 'list-invitations': { + const invitations = await listInvitations({ accountAddress }); + const outgoingMessage: Messages.ResponseListInvitations = { + type: 'list-invitations', + invitations: invitations, + }; + webSocket.send(Messages.serialize(outgoingMessage)); + break; + } + case 'create-space-event': { + const getVerifiedIdentity = (accountAddressToFetch: string) => { + console.log( + 'TODO getVerifiedIdentity should work for app identities', + accountAddressToFetch, + accountAddress, + ); + if (accountAddressToFetch !== accountAddress) { + return Effect.fail(new Identity.InvalidIdentityError()); + } + + return Effect.gen(function* () { + const identity = yield* Effect.tryPromise({ + try: () => getConnectIdentity({ accountAddress: accountAddressToFetch }), + catch: () => new Identity.InvalidIdentityError(), + }); + return identity; }); - return identity; - }); - }; + }; - const applyEventResult = await Effect.runPromiseExit( - SpaceEvents.applyEvent({ - event: data.event, - state: undefined, - getVerifiedIdentity, - }), - ); - if (Exit.isSuccess(applyEventResult)) { - const space = await createSpace({ + const applyEventResult = await Effect.runPromiseExit( + SpaceEvents.applyEvent({ + event: data.event, + state: undefined, + getVerifiedIdentity, + }), + ); + if (Exit.isSuccess(applyEventResult)) { + const space = await createSpace({ + accountAddress, + event: data.event, + keyBox: data.keyBox, + infoContent: new Uint8Array(), + infoSignatureHex: '', + infoSignatureRecovery: 0, + name: data.name, + }); + const spaceWithEvents = await getSpace({ accountAddress, spaceId: space.id }); + const outgoingMessage: Messages.ResponseSpace = { + ...spaceWithEvents, + type: 'space', + }; + webSocket.send(Messages.serialize(outgoingMessage)); + } else { + console.log('Failed to apply create space event'); + console.log(applyEventResult); + } + // TODO send back error + break; + } + case 'create-invitation-event': { + await applySpaceEvent({ accountAddress, + spaceId: data.spaceId, event: data.event, - keyBox: data.keyBox, - infoContent: new Uint8Array(), - infoSignatureHex: '', - infoSignatureRecovery: 0, - name: data.name, + keyBoxes: data.keyBoxes.map((keyBox) => keyBox), }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: space.id }); + const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); + // TODO send back confirmation instead of the entire space const outgoingMessage: Messages.ResponseSpace = { ...spaceWithEvents, type: 'space', }; webSocket.send(Messages.serialize(outgoingMessage)); - } else { - console.log('Failed to apply create space event'); - console.log(applyEventResult); - } - // TODO send back error - break; - } - case 'create-invitation-event': { - await applySpaceEvent({ - accountAddress, - spaceId: data.spaceId, - event: data.event, - keyBoxes: data.keyBoxes.map((keyBox) => keyBox), - }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); - // TODO send back confirmation instead of the entire space - const outgoingMessage: Messages.ResponseSpace = { - ...spaceWithEvents, - type: 'space', - }; - webSocket.send(Messages.serialize(outgoingMessage)); - for (const client of webSocketServer.clients as Set) { - if ( - client.readyState === WebSocket.OPEN && - client.accountAddress === data.event.transaction.inviteeAccountAddress - ) { - const invitations = await listInvitations({ accountAddress: client.accountAddress }); - const outgoingMessage: Messages.ResponseListInvitations = { - type: 'list-invitations', - invitations: invitations, - }; - // for now sending the entire list of invitations to the client - we could send only a single one - client.send(Messages.serialize(outgoingMessage)); + for (const client of webSocketServer.clients as Set) { + if ( + client.readyState === WebSocket.OPEN && + client.accountAddress === data.event.transaction.inviteeAccountAddress + ) { + const invitations = await listInvitations({ accountAddress: client.accountAddress }); + const outgoingMessage: Messages.ResponseListInvitations = { + type: 'list-invitations', + invitations: invitations, + }; + // for now sending the entire list of invitations to the client - we could send only a single one + client.send(Messages.serialize(outgoingMessage)); + } } - } - broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); - break; - } - case 'accept-invitation-event': { - await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); - const outgoingMessage: Messages.ResponseSpace = { - ...spaceWithEvents, - type: 'space', - }; - webSocket.send(Messages.serialize(outgoingMessage)); - broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); - break; - } - case 'create-space-inbox-event': { - await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); - const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); - // TODO send back confirmation instead of the entire space - const outgoingMessage: Messages.ResponseSpace = { - ...spaceWithEvents, - type: 'space', - }; - webSocket.send(Messages.serialize(outgoingMessage)); - broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); - break; - } - case 'create-account-inbox': { - try { - // Check that the signature is valid for the corresponding accountAddress - if (data.accountAddress !== accountAddress) { - throw new Error('Invalid accountAddress'); - } - const signer = Inboxes.recoverAccountInboxCreatorKey(data); - const signerAccount = await getConnectIdentity({ connectSignaturePublicKey: signer }); - if (signerAccount.accountAddress !== accountAddress) { - throw new Error('Invalid signature'); - } - // Create the inbox (if it doesn't exist) - await createAccountInbox(data); - // Broadcast the inbox to other clients from the same account - broadcastAccountInbox({ inbox: data }); - } catch (error) { - console.error('Error creating account inbox:', error); - return; + broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); + break; } - break; - } - case 'get-latest-space-inbox-messages': { - try { - // Check that the user has access to this space - await getSpace({ accountAddress, spaceId: data.spaceId }); - const messages = await getLatestSpaceInboxMessages({ - inboxId: data.inboxId, - since: data.since, - }); - const outgoingMessage: Messages.ResponseSpaceInboxMessages = { - type: 'space-inbox-messages', - spaceId: data.spaceId, - inboxId: data.inboxId, - messages, + case 'accept-invitation-event': { + await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); + const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); + const outgoingMessage: Messages.ResponseSpace = { + ...spaceWithEvents, + type: 'space', }; webSocket.send(Messages.serialize(outgoingMessage)); - } catch (error) { - console.error('Error getting latest space inbox messages:', error); - return; + broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); + break; } - break; - } - case 'get-latest-account-inbox-messages': { - try { - // Check that the user has access to this inbox - await getAccountInbox({ accountAddress, inboxId: data.inboxId }); - const messages = await getLatestAccountInboxMessages({ - inboxId: data.inboxId, - since: data.since, - }); - const outgoingMessage: Messages.ResponseAccountInboxMessages = { - type: 'account-inbox-messages', - accountAddress, - inboxId: data.inboxId, - messages, + case 'create-space-inbox-event': { + await applySpaceEvent({ accountAddress, spaceId: data.spaceId, event: data.event, keyBoxes: [] }); + const spaceWithEvents = await getSpace({ accountAddress, spaceId: data.spaceId }); + // TODO send back confirmation instead of the entire space + const outgoingMessage: Messages.ResponseSpace = { + ...spaceWithEvents, + type: 'space', }; webSocket.send(Messages.serialize(outgoingMessage)); - } catch (error) { - console.error('Error getting latest account inbox messages:', error); - return; + broadcastSpaceEvents({ spaceId: data.spaceId, event: data.event, currentClient: webSocket }); + break; } - break; - } - case 'get-account-inboxes': { - const inboxes = await listAccountInboxes({ accountAddress }); - const outgoingMessage: Messages.ResponseAccountInboxes = { - type: 'account-inboxes', - inboxes, - }; - webSocket.send(Messages.serialize(outgoingMessage)); - break; - } - case 'create-update': { - try { - // Check that the update was signed by a valid identity - // belonging to this accountAddress - const signer = Messages.recoverUpdateMessageSigner(data); - const identity = await getConnectIdentity({ connectSignaturePublicKey: signer }); - if (identity.accountAddress !== accountAddress) { - throw new Error('Invalid signature'); + case 'create-account-inbox': { + try { + // Check that the signature is valid for the corresponding accountAddress + if (data.accountAddress !== accountAddress) { + throw new Error('Invalid accountAddress'); + } + const signer = Inboxes.recoverAccountInboxCreatorKey(data); + const signerAccount = await getConnectIdentity({ connectSignaturePublicKey: signer }); + if (signerAccount.accountAddress !== accountAddress) { + throw new Error('Invalid signature'); + } + // Create the inbox (if it doesn't exist) + await createAccountInbox(data); + // Broadcast the inbox to other clients from the same account + broadcastAccountInbox({ inbox: data }); + } catch (error) { + console.error('Error creating account inbox:', error); + return; } - const update = await createUpdate({ - accountAddress, - spaceId: data.spaceId, - update: data.update, - signatureHex: data.signature.hex, - signatureRecovery: data.signature.recovery, - updateId: data.updateId, - }); - const outgoingMessage: Messages.ResponseUpdateConfirmed = { - type: 'update-confirmed', - updateId: data.updateId, - clock: update.clock, - spaceId: data.spaceId, + break; + } + case 'get-latest-space-inbox-messages': { + try { + // Check that the user has access to this space + await getSpace({ accountAddress, spaceId: data.spaceId }); + const messages = await getLatestSpaceInboxMessages({ + inboxId: data.inboxId, + since: data.since, + }); + const outgoingMessage: Messages.ResponseSpaceInboxMessages = { + type: 'space-inbox-messages', + spaceId: data.spaceId, + inboxId: data.inboxId, + messages, + }; + webSocket.send(Messages.serialize(outgoingMessage)); + } catch (error) { + console.error('Error getting latest space inbox messages:', error); + return; + } + break; + } + case 'get-latest-account-inbox-messages': { + try { + // Check that the user has access to this inbox + await getAccountInbox({ accountAddress, inboxId: data.inboxId }); + const messages = await getLatestAccountInboxMessages({ + inboxId: data.inboxId, + since: data.since, + }); + const outgoingMessage: Messages.ResponseAccountInboxMessages = { + type: 'account-inbox-messages', + accountAddress, + inboxId: data.inboxId, + messages, + }; + webSocket.send(Messages.serialize(outgoingMessage)); + } catch (error) { + console.error('Error getting latest account inbox messages:', error); + return; + } + break; + } + case 'get-account-inboxes': { + const inboxes = await listAccountInboxes({ accountAddress }); + const outgoingMessage: Messages.ResponseAccountInboxes = { + type: 'account-inboxes', + inboxes, }; webSocket.send(Messages.serialize(outgoingMessage)); - - broadcastUpdates({ - spaceId: data.spaceId, - updates: { - updates: [ - { - accountAddress, - update: data.update, - signature: data.signature, - updateId: data.updateId, - }, - ], - firstUpdateClock: update.clock, - lastUpdateClock: update.clock, - }, - currentClient: webSocket, - }); - } catch (err) { - console.error('Error creating update:', err); + break; } - break; + case 'create-update': { + try { + // Check that the update was signed by a valid identity + // belonging to this accountAddress + const signer = Messages.recoverUpdateMessageSigner(data); + const identity = await getConnectIdentity({ connectSignaturePublicKey: signer }); + if (identity.accountAddress !== accountAddress) { + throw new Error('Invalid signature'); + } + const update = await createUpdate({ + accountAddress, + spaceId: data.spaceId, + update: data.update, + signatureHex: data.signature.hex, + signatureRecovery: data.signature.recovery, + updateId: data.updateId, + }); + const outgoingMessage: Messages.ResponseUpdateConfirmed = { + type: 'update-confirmed', + updateId: data.updateId, + clock: update.clock, + spaceId: data.spaceId, + }; + webSocket.send(Messages.serialize(outgoingMessage)); + + broadcastUpdates({ + spaceId: data.spaceId, + updates: { + updates: [ + { + accountAddress, + update: data.update, + signature: data.signature, + updateId: data.updateId, + }, + ], + firstUpdateClock: update.clock, + lastUpdateClock: update.clock, + }, + currentClient: webSocket, + }); + } catch (err) { + console.error('Error creating update:', err); + } + break; + } + default: + Utils.assertExhaustive(data); + break; } - default: - Utils.assertExhaustive(data); - break; } + } catch (error) { + console.error('Error processing message:', error); } }); webSocket.on('close', () => {