1+ import { DebugSpaceEvents } from '@/components/debug-space-events' ;
2+ import { DebugSpaceState } from '@/components/debug-space-state' ;
13import { Button } from '@/components/ui/button' ;
24import { assertExhaustive } from '@/lib/assertExhaustive' ;
35import { createFileRoute } from '@tanstack/react-router' ;
4- import { Effect } from 'effect' ;
6+ import { Effect , Exit } from 'effect' ;
57import * as Schema from 'effect/Schema' ;
6- import type { EventMessage , RequestListSpaces , RequestSubscribeToSpace } from 'graph-framework' ;
7- import { ResponseMessage , createSpace } from 'graph-framework' ;
8+ import type { EventMessage , RequestListSpaces , RequestSubscribeToSpace , SpaceEvent , SpaceState } from 'graph-framework' ;
9+ import { ResponseMessage , applyEvent , createInvitation , createSpace } from 'graph-framework' ;
810import { useEffect , useState } from 'react' ;
911
12+ const availableAccounts = [
13+ {
14+ accountId : '0262701b2eb1b6b37ad03e24445dfcad1b91309199e43017b657ce2604417c12f5' ,
15+ signaturePrivateKey : '88bb6f20de8dc1787c722dc847f4cf3d00285b8955445f23c483d1237fe85366' ,
16+ } ,
17+ {
18+ accountId : '03bf5d2a1badf15387b08a007d1a9a13a9bfd6e1c56f681e251514d9ba10b57462' ,
19+ signaturePrivateKey : '1eee32d3bc202dcb5d17c3b1454fb541d2290cb941860735408f1bfe39e7bc15' ,
20+ } ,
21+ {
22+ accountId : '0351460706cf386282d9b6ebee2ccdcb9ba61194fd024345e53037f3036242e6a2' ,
23+ signaturePrivateKey : '434518a2c9a665a7c20da086232c818b6c1592e2edfeecab29a40cf5925ca8fe' ,
24+ } ,
25+ ] ;
26+
27+ type SpaceStorageEntry = {
28+ id : string ;
29+ events : SpaceEvent [ ] ;
30+ state : SpaceState | undefined ;
31+ } ;
32+
1033const decodeResponseMessage = Schema . decodeUnknownEither ( ResponseMessage ) ;
1134
1235export const Route = createFileRoute ( '/playground' ) ( {
@@ -15,26 +38,63 @@ export const Route = createFileRoute('/playground')({
1538
1639const App = ( { accountId, signaturePrivateKey } : { accountId : string ; signaturePrivateKey : string } ) => {
1740 const [ websocketConnection , setWebsocketConnection ] = useState < WebSocket > ( ) ;
18- const [ spaces , setSpaces ] = useState < { id : string } [ ] > ( [ ] ) ;
41+ const [ spaces , setSpaces ] = useState < SpaceStorageEntry [ ] > ( [ ] ) ;
1942
2043 useEffect ( ( ) => {
2144 // temporary until we have a way to create accounts and authenticate them
2245 const websocketConnection = new WebSocket ( `ws://localhost:3030/?accountId=${ accountId } ` ) ;
2346 setWebsocketConnection ( websocketConnection ) ;
2447
25- const onMessage = ( event : MessageEvent ) => {
48+ const onMessage = async ( event : MessageEvent ) => {
2649 console . log ( 'message received' , event . data ) ;
2750 const data = JSON . parse ( event . data ) ;
2851 const message = decodeResponseMessage ( data ) ;
2952 if ( message . _tag === 'Right' ) {
3053 const response = message . right ;
3154 switch ( response . type ) {
3255 case 'list-spaces' : {
33- setSpaces ( response . spaces . map ( ( space ) => ( { id : space . id } ) ) ) ;
56+ setSpaces ( ( existingSpaces ) => {
57+ return response . spaces . map ( ( space ) => {
58+ const existingSpace = existingSpaces . find ( ( s ) => s . id === space . id ) ;
59+ return { id : space . id , events : existingSpace ?. events ?? [ ] , state : existingSpace ?. state } ;
60+ } ) ;
61+ } ) ;
62+ // fetch all spaces (for debugging purposes)
63+ for ( const space of response . spaces ) {
64+ const message : RequestSubscribeToSpace = { type : 'subscribe-space' , id : space . id } ;
65+ websocketConnection ?. send ( JSON . stringify ( message ) ) ;
66+ }
3467 break ;
3568 }
3669 case 'space' : {
37- console . log ( 'space' , response ) ;
70+ let state : SpaceState | undefined = undefined ;
71+
72+ // TODO fix typing
73+ for ( const event of response . events ) {
74+ if ( state === undefined ) {
75+ const applyEventResult = await Effect . runPromiseExit ( applyEvent ( { event } ) ) ;
76+ if ( Exit . isSuccess ( applyEventResult ) ) {
77+ state = applyEventResult . value ;
78+ }
79+ } else {
80+ const applyEventResult = await Effect . runPromiseExit ( applyEvent ( { event, state } ) ) ;
81+ if ( Exit . isSuccess ( applyEventResult ) ) {
82+ state = applyEventResult . value ;
83+ }
84+ }
85+ }
86+
87+ const newState = state as SpaceState ;
88+
89+ setSpaces ( ( spaces ) =>
90+ spaces . map ( ( space ) => {
91+ if ( space . id === response . id ) {
92+ // TODO fix readonly type issue
93+ return { ...space , events : response . events as SpaceEvent [ ] , state : newState } ;
94+ }
95+ return space ;
96+ } ) ,
97+ ) ;
3898 break ;
3999 }
40100 case 'event' : {
@@ -86,7 +146,7 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
86146 } ,
87147 } ) ,
88148 ) ;
89- const message : EventMessage = { type : 'event' , event : spaceEvent } ;
149+ const message : EventMessage = { type : 'event' , event : spaceEvent , spaceId : spaceEvent . transaction . id } ;
90150 websocketConnection ?. send ( JSON . stringify ( message ) ) ;
91151 } }
92152 >
@@ -107,7 +167,7 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
107167 { spaces . map ( ( space ) => {
108168 return (
109169 < li key = { space . id } >
110- < h3 > { space . id } </ h3 >
170+ < h3 > Space id: { space . id } </ h3 >
111171 < Button
112172 onClick = { ( ) => {
113173 const message : RequestSubscribeToSpace = { type : 'subscribe-space' , id : space . id } ;
@@ -116,6 +176,47 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
116176 >
117177 Get data and subscribe to Space
118178 </ Button >
179+ < br />
180+ { availableAccounts . map ( ( invitee ) => {
181+ return (
182+ < Button
183+ key = { invitee . accountId }
184+ onClick = { async ( ) => {
185+ if ( ! space . state ) {
186+ console . error ( 'No state found for space' ) ;
187+ return ;
188+ }
189+ const spaceEvent = await Effect . runPromiseExit (
190+ createInvitation ( {
191+ author : {
192+ signaturePublicKey : accountId ,
193+ encryptionPublicKey : 'TODO' ,
194+ signaturePrivateKey,
195+ } ,
196+ previousEventHash : space . state . lastEventHash ,
197+ invitee : {
198+ signaturePublicKey : invitee . accountId ,
199+ encryptionPublicKey : 'TODO' ,
200+ } ,
201+ } ) ,
202+ ) ;
203+ if ( Exit . isFailure ( spaceEvent ) ) {
204+ console . error ( 'Failed to create invitation' , spaceEvent ) ;
205+ return ;
206+ }
207+ const message : EventMessage = { type : 'event' , event : spaceEvent . value , spaceId : space . id } ;
208+ websocketConnection ?. send ( JSON . stringify ( message ) ) ;
209+ } }
210+ >
211+ Invite { invitee . accountId . substring ( 0 , 4 ) }
212+ </ Button >
213+ ) ;
214+ } ) }
215+ < h3 > State</ h3 >
216+ < DebugSpaceState state = { space . state } />
217+ < h3 > Events</ h3 >
218+ < DebugSpaceEvents events = { space . events } />
219+ < hr />
119220 </ li >
120221 ) ;
121222 } ) }
@@ -132,33 +233,24 @@ export const ChooseAccount = () => {
132233 < h1 > Choose account</ h1 >
133234 < Button
134235 onClick = { ( ) => {
135- setAccount ( {
136- accountId : '0262701b2eb1b6b37ad03e24445dfcad1b91309199e43017b657ce2604417c12f5' ,
137- signaturePrivateKey : '88bb6f20de8dc1787c722dc847f4cf3d00285b8955445f23c483d1237fe85366' ,
138- } ) ;
236+ setAccount ( availableAccounts [ 0 ] ) ;
139237 } }
140238 >
141- `abc`
239+ { availableAccounts [ 0 ] . accountId . substring ( 0 , 4 ) }
142240 </ Button >
143241 < Button
144242 onClick = { ( ) => {
145- setAccount ( {
146- accountId : '03bf5d2a1badf15387b08a007d1a9a13a9bfd6e1c56f681e251514d9ba10b57462' ,
147- signaturePrivateKey : '1eee32d3bc202dcb5d17c3b1454fb541d2290cb941860735408f1bfe39e7bc15' ,
148- } ) ;
243+ setAccount ( availableAccounts [ 1 ] ) ;
149244 } }
150245 >
151- `cde`
246+ { availableAccounts [ 1 ] . accountId . substring ( 0 , 4 ) }
152247 </ Button >
153248 < Button
154249 onClick = { ( ) => {
155- setAccount ( {
156- accountId : '0351460706cf386282d9b6ebee2ccdcb9ba61194fd024345e53037f3036242e6a2' ,
157- signaturePrivateKey : '434518a2c9a665a7c20da086232c818b6c1592e2edfeecab29a40cf5925ca8fe' ,
158- } ) ;
250+ setAccount ( availableAccounts [ 2 ] ) ;
159251 } }
160252 >
161- `def`
253+ { availableAccounts [ 2 ] . accountId . substring ( 0 , 4 ) }
162254 </ Button >
163255 Account: { account ?. accountId ? account . accountId : 'none' }
164256 < hr />
0 commit comments