@@ -3,40 +3,60 @@ import { DebugSpaceEvents } from '@/components/debug-space-events';
33import { DebugSpaceState } from '@/components/debug-space-state' ;
44import { Button } from '@/components/ui/button' ;
55import { assertExhaustive } from '@/lib/assertExhaustive' ;
6+ import { bytesToHex , hexToBytes } from '@noble/hashes/utils' ;
67import { createFileRoute } from '@tanstack/react-router' ;
78import { Effect , Exit } from 'effect' ;
89import * as Schema from 'effect/Schema' ;
910import type {
1011 EventMessage ,
1112 Invitation ,
13+ RequestCreateInvitationEvent ,
14+ RequestCreateSpaceEvent ,
1215 RequestListInvitations ,
1316 RequestListSpaces ,
1417 RequestSubscribeToSpace ,
1518 SpaceEvent ,
1619 SpaceState ,
1720} from 'graph-framework' ;
18- import { ResponseMessage , applyEvent , createInvitation , createSpace } from 'graph-framework' ;
21+ import {
22+ ResponseMessage ,
23+ acceptInvitation ,
24+ applyEvent ,
25+ createInvitation ,
26+ createKey ,
27+ createSpace ,
28+ decryptKey ,
29+ encryptKey ,
30+ generateId ,
31+ } from 'graph-framework' ;
1932import { useEffect , useState } from 'react' ;
2033
2134const availableAccounts = [
2235 {
2336 accountId : '0262701b2eb1b6b37ad03e24445dfcad1b91309199e43017b657ce2604417c12f5' ,
2437 signaturePrivateKey : '88bb6f20de8dc1787c722dc847f4cf3d00285b8955445f23c483d1237fe85366' ,
38+ encryptionPrivateKey : 'bbf164a93b0f78a85346017fa2673cf367c64d81b1c3d6af7ad45e308107a812' ,
39+ encryptionPublicKey : '595e1a6b0bb346d83bc382998943d2e6d9210fd341bc8b9f41a7229eede27240' ,
2540 } ,
2641 {
2742 accountId : '03bf5d2a1badf15387b08a007d1a9a13a9bfd6e1c56f681e251514d9ba10b57462' ,
2843 signaturePrivateKey : '1eee32d3bc202dcb5d17c3b1454fb541d2290cb941860735408f1bfe39e7bc15' ,
44+ encryptionPrivateKey : 'b32478dc6f40482127a09d0f1cabbf45dc83ebce638d6246f5552191009fda2c' ,
45+ encryptionPublicKey : '0f4e22dc85167597af85cba85988770cd77c25d317f2b14a1f49a54efcbfae3f' ,
2946 } ,
3047 {
3148 accountId : '0351460706cf386282d9b6ebee2ccdcb9ba61194fd024345e53037f3036242e6a2' ,
3249 signaturePrivateKey : '434518a2c9a665a7c20da086232c818b6c1592e2edfeecab29a40cf5925ca8fe' ,
50+ encryptionPrivateKey : 'aaf71397e44fc57b42eaad5b0869d1e0247b4a7f2fe9ec5cc00dec3815849e7a' ,
51+ encryptionPublicKey : 'd494144358a610604c4ab453b442d014f2843772eed19be155dd9fc55fe8a332' ,
3352 } ,
3453] ;
3554
3655type SpaceStorageEntry = {
3756 id : string ;
3857 events : SpaceEvent [ ] ;
3958 state : SpaceState | undefined ;
59+ keys : { id : string ; key : string } [ ] ;
4060} ;
4161
4262const decodeResponseMessage = Schema . decodeUnknownEither ( ResponseMessage ) ;
@@ -45,7 +65,17 @@ export const Route = createFileRoute('/playground')({
4565 component : ( ) => < ChooseAccount /> ,
4666} ) ;
4767
48- const App = ( { accountId, signaturePrivateKey } : { accountId : string ; signaturePrivateKey : string } ) => {
68+ const App = ( {
69+ accountId,
70+ signaturePrivateKey,
71+ encryptionPublicKey,
72+ encryptionPrivateKey,
73+ } : {
74+ accountId : string ;
75+ signaturePrivateKey : string ;
76+ encryptionPrivateKey : string ;
77+ encryptionPublicKey : string ;
78+ } ) => {
4979 const [ websocketConnection , setWebsocketConnection ] = useState < WebSocket > ( ) ;
5080 const [ spaces , setSpaces ] = useState < SpaceStorageEntry [ ] > ( [ ] ) ;
5181 const [ invitations , setInvitations ] = useState < Invitation [ ] > ( [ ] ) ;
@@ -66,7 +96,12 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
6696 setSpaces ( ( existingSpaces ) => {
6797 return response . spaces . map ( ( space ) => {
6898 const existingSpace = existingSpaces . find ( ( s ) => s . id === space . id ) ;
69- return { id : space . id , events : existingSpace ?. events ?? [ ] , state : existingSpace ?. state } ;
99+ return {
100+ id : space . id ,
101+ events : existingSpace ?. events ?? [ ] ,
102+ state : existingSpace ?. state ,
103+ keys : existingSpace ?. keys ?? [ ] ,
104+ } ;
70105 } ) ;
71106 } ) ;
72107 // fetch all spaces (for debugging purposes)
@@ -96,11 +131,26 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
96131
97132 const newState = state as SpaceState ;
98133
134+ const keys = response . keyBoxes . map ( ( keyBox ) => {
135+ const key = decryptKey ( {
136+ keyBoxCiphertext : hexToBytes ( keyBox . ciphertext ) ,
137+ keyBoxNonce : hexToBytes ( keyBox . nonce ) ,
138+ publicKey : hexToBytes ( keyBox . authorPublicKey ) ,
139+ privateKey : hexToBytes ( encryptionPrivateKey ) ,
140+ } ) ;
141+ return { id : keyBox . id , key : bytesToHex ( key ) } ;
142+ } ) ;
143+
99144 setSpaces ( ( spaces ) =>
100145 spaces . map ( ( space ) => {
101146 if ( space . id === response . id ) {
102147 // TODO fix readonly type issue
103- return { ...space , events : response . events as SpaceEvent [ ] , state : newState } ;
148+ return {
149+ ...space ,
150+ events : response . events as SpaceEvent [ ] ,
151+ state : newState ,
152+ keys,
153+ } ;
104154 }
105155 return space ;
106156 } ) ,
@@ -144,7 +194,7 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
144194 websocketConnection . removeEventListener ( 'close' , onClose ) ;
145195 websocketConnection . close ( ) ;
146196 } ;
147- } , [ accountId ] ) ;
197+ } , [ accountId , encryptionPrivateKey ] ) ;
148198
149199 return (
150200 < >
@@ -154,13 +204,28 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
154204 const spaceEvent = await Effect . runPromise (
155205 createSpace ( {
156206 author : {
157- encryptionPublicKey : 'TODO' ,
207+ encryptionPublicKey,
158208 signaturePrivateKey,
159209 signaturePublicKey : accountId ,
160210 } ,
161211 } ) ,
162212 ) ;
163- const message : EventMessage = { type : 'event' , event : spaceEvent , spaceId : spaceEvent . transaction . id } ;
213+ const result = createKey ( {
214+ privateKey : hexToBytes ( encryptionPrivateKey ) ,
215+ publicKey : hexToBytes ( encryptionPublicKey ) ,
216+ } ) ;
217+ const message : RequestCreateSpaceEvent = {
218+ type : 'create-space-event' ,
219+ event : spaceEvent ,
220+ spaceId : spaceEvent . transaction . id ,
221+ keyId : generateId ( ) ,
222+ keyBox : {
223+ accountId,
224+ ciphertext : bytesToHex ( result . keyBoxCiphertext ) ,
225+ nonce : bytesToHex ( result . keyBoxNonce ) ,
226+ authorPublicKey : encryptionPublicKey ,
227+ } ,
228+ } ;
164229 websocketConnection ?. send ( JSON . stringify ( message ) ) ;
165230 } }
166231 >
@@ -186,13 +251,35 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
186251 </ Button >
187252 </ div >
188253 < h2 className = "text-lg" > Invitations</ h2 >
189- < DebugInvitations invitations = { invitations } />
254+ < DebugInvitations
255+ invitations = { invitations }
256+ accept = { async ( invitation ) => {
257+ const spaceEvent = await Effect . runPromiseExit (
258+ acceptInvitation ( {
259+ author : {
260+ signaturePublicKey : accountId ,
261+ encryptionPublicKey,
262+ signaturePrivateKey,
263+ } ,
264+ previousEventHash : invitation . previousEventHash ,
265+ } ) ,
266+ ) ;
267+ if ( Exit . isFailure ( spaceEvent ) ) {
268+ console . error ( 'Failed to accept invitation' , spaceEvent ) ;
269+ return ;
270+ }
271+ const message : EventMessage = { type : 'event' , event : spaceEvent . value , spaceId : invitation . spaceId } ;
272+ websocketConnection ?. send ( JSON . stringify ( message ) ) ;
273+ } }
274+ />
190275 < h2 className = "text-lg" > Spaces</ h2 >
191276 < ul >
192277 { spaces . map ( ( space ) => {
193278 return (
194279 < li key = { space . id } >
195280 < h3 > Space id: { space . id } </ h3 >
281+ < p > Keys:</ p >
282+ < pre className = "text-xs" > { JSON . stringify ( space . keys ) } </ pre >
196283 < Button
197284 onClick = { ( ) => {
198285 const message : RequestSubscribeToSpace = { type : 'subscribe-space' , id : space . id } ;
@@ -215,21 +302,42 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
215302 createInvitation ( {
216303 author : {
217304 signaturePublicKey : accountId ,
218- encryptionPublicKey : 'TODO' ,
305+ encryptionPublicKey,
219306 signaturePrivateKey,
220307 } ,
221308 previousEventHash : space . state . lastEventHash ,
222309 invitee : {
223310 signaturePublicKey : invitee . accountId ,
224- encryptionPublicKey : 'TODO' ,
311+ encryptionPublicKey,
225312 } ,
226313 } ) ,
227314 ) ;
228315 if ( Exit . isFailure ( spaceEvent ) ) {
229316 console . error ( 'Failed to create invitation' , spaceEvent ) ;
230317 return ;
231318 }
232- const message : EventMessage = { type : 'event' , event : spaceEvent . value , spaceId : space . id } ;
319+
320+ const keyBoxes = space . keys . map ( ( key ) => {
321+ const keyBox = encryptKey ( {
322+ key : hexToBytes ( key . key ) ,
323+ publicKey : hexToBytes ( invitee . encryptionPublicKey ) ,
324+ privateKey : hexToBytes ( encryptionPrivateKey ) ,
325+ } ) ;
326+ return {
327+ id : key . id ,
328+ ciphertext : bytesToHex ( keyBox . keyBoxCiphertext ) ,
329+ nonce : bytesToHex ( keyBox . keyBoxNonce ) ,
330+ authorPublicKey : encryptionPublicKey ,
331+ accountId : invitee . accountId ,
332+ } ;
333+ } ) ;
334+
335+ const message : RequestCreateInvitationEvent = {
336+ type : 'create-invitation-event' ,
337+ event : spaceEvent . value ,
338+ spaceId : space . id ,
339+ keyBoxes,
340+ } ;
233341 websocketConnection ?. send ( JSON . stringify ( message ) ) ;
234342 } }
235343 >
@@ -251,7 +359,12 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
251359} ;
252360
253361export const ChooseAccount = ( ) => {
254- const [ account , setAccount ] = useState < { accountId : string ; signaturePrivateKey : string } | null > ( ) ;
362+ const [ account , setAccount ] = useState < {
363+ accountId : string ;
364+ signaturePrivateKey : string ;
365+ encryptionPrivateKey : string ;
366+ encryptionPublicKey : string ;
367+ } | null > ( ) ;
255368
256369 return (
257370 < div >
@@ -285,6 +398,8 @@ export const ChooseAccount = () => {
285398 key = { account . accountId }
286399 accountId = { account . accountId }
287400 signaturePrivateKey = { account . signaturePrivateKey }
401+ encryptionPrivateKey = { account . encryptionPrivateKey }
402+ encryptionPublicKey = { account . encryptionPublicKey }
288403 />
289404 ) }
290405 </ div >
0 commit comments