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 {
9+ EventMessage ,
10+ RequestListInvitations ,
11+ RequestListSpaces ,
12+ RequestSubscribeToSpace ,
13+ SpaceEvent ,
14+ SpaceState ,
15+ } from 'graph-framework' ;
16+ import { ResponseMessage , applyEvent , createInvitation , createSpace } from 'graph-framework' ;
817import { useEffect , useState } from 'react' ;
918
19+ const availableAccounts = [
20+ {
21+ accountId : '0262701b2eb1b6b37ad03e24445dfcad1b91309199e43017b657ce2604417c12f5' ,
22+ signaturePrivateKey : '88bb6f20de8dc1787c722dc847f4cf3d00285b8955445f23c483d1237fe85366' ,
23+ } ,
24+ {
25+ accountId : '03bf5d2a1badf15387b08a007d1a9a13a9bfd6e1c56f681e251514d9ba10b57462' ,
26+ signaturePrivateKey : '1eee32d3bc202dcb5d17c3b1454fb541d2290cb941860735408f1bfe39e7bc15' ,
27+ } ,
28+ {
29+ accountId : '0351460706cf386282d9b6ebee2ccdcb9ba61194fd024345e53037f3036242e6a2' ,
30+ signaturePrivateKey : '434518a2c9a665a7c20da086232c818b6c1592e2edfeecab29a40cf5925ca8fe' ,
31+ } ,
32+ ] ;
33+
34+ type SpaceStorageEntry = {
35+ id : string ;
36+ events : SpaceEvent [ ] ;
37+ state : SpaceState | undefined ;
38+ } ;
39+
1040const decodeResponseMessage = Schema . decodeUnknownEither ( ResponseMessage ) ;
1141
1242export const Route = createFileRoute ( '/playground' ) ( {
@@ -15,32 +45,73 @@ export const Route = createFileRoute('/playground')({
1545
1646const App = ( { accountId, signaturePrivateKey } : { accountId : string ; signaturePrivateKey : string } ) => {
1747 const [ websocketConnection , setWebsocketConnection ] = useState < WebSocket > ( ) ;
18- const [ spaces , setSpaces ] = useState < { id : string } [ ] > ( [ ] ) ;
48+ const [ spaces , setSpaces ] = useState < SpaceStorageEntry [ ] > ( [ ] ) ;
1949
2050 useEffect ( ( ) => {
2151 // temporary until we have a way to create accounts and authenticate them
2252 const websocketConnection = new WebSocket ( `ws://localhost:3030/?accountId=${ accountId } ` ) ;
2353 setWebsocketConnection ( websocketConnection ) ;
2454
25- const onMessage = ( event : MessageEvent ) => {
55+ const onMessage = async ( event : MessageEvent ) => {
2656 console . log ( 'message received' , event . data ) ;
2757 const data = JSON . parse ( event . data ) ;
2858 const message = decodeResponseMessage ( data ) ;
2959 if ( message . _tag === 'Right' ) {
3060 const response = message . right ;
3161 switch ( response . type ) {
3262 case 'list-spaces' : {
33- setSpaces ( response . spaces . map ( ( space ) => ( { id : space . id } ) ) ) ;
63+ setSpaces ( ( existingSpaces ) => {
64+ return response . spaces . map ( ( space ) => {
65+ const existingSpace = existingSpaces . find ( ( s ) => s . id === space . id ) ;
66+ return { id : space . id , events : existingSpace ?. events ?? [ ] , state : existingSpace ?. state } ;
67+ } ) ;
68+ } ) ;
69+ // fetch all spaces (for debugging purposes)
70+ for ( const space of response . spaces ) {
71+ const message : RequestSubscribeToSpace = { type : 'subscribe-space' , id : space . id } ;
72+ websocketConnection ?. send ( JSON . stringify ( message ) ) ;
73+ }
3474 break ;
3575 }
3676 case 'space' : {
37- console . log ( 'space' , response ) ;
77+ let state : SpaceState | undefined = undefined ;
78+
79+ // TODO fix typing
80+ for ( const event of response . events ) {
81+ if ( state === undefined ) {
82+ const applyEventResult = await Effect . runPromiseExit ( applyEvent ( { event } ) ) ;
83+ if ( Exit . isSuccess ( applyEventResult ) ) {
84+ state = applyEventResult . value ;
85+ }
86+ } else {
87+ const applyEventResult = await Effect . runPromiseExit ( applyEvent ( { event, state } ) ) ;
88+ if ( Exit . isSuccess ( applyEventResult ) ) {
89+ state = applyEventResult . value ;
90+ }
91+ }
92+ }
93+
94+ const newState = state as SpaceState ;
95+
96+ setSpaces ( ( spaces ) =>
97+ spaces . map ( ( space ) => {
98+ if ( space . id === response . id ) {
99+ // TODO fix readonly type issue
100+ return { ...space , events : response . events as SpaceEvent [ ] , state : newState } ;
101+ }
102+ return space ;
103+ } ) ,
104+ ) ;
38105 break ;
39106 }
40107 case 'event' : {
41108 console . log ( 'event' , response ) ;
42109 break ;
43110 }
111+ case 'list-invitations' : {
112+ console . log ( 'list-invitations' , response ) ;
113+ break ;
114+ }
44115 default :
45116 assertExhaustive ( response ) ;
46117 }
@@ -86,7 +157,7 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
86157 } ,
87158 } ) ,
88159 ) ;
89- const message : EventMessage = { type : 'event' , event : spaceEvent } ;
160+ const message : EventMessage = { type : 'event' , event : spaceEvent , spaceId : spaceEvent . transaction . id } ;
90161 websocketConnection ?. send ( JSON . stringify ( message ) ) ;
91162 } }
92163 >
@@ -101,13 +172,22 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
101172 >
102173 List Spaces
103174 </ Button >
175+
176+ < Button
177+ onClick = { ( ) => {
178+ const message : RequestListInvitations = { type : 'list-invitations' } ;
179+ websocketConnection ?. send ( JSON . stringify ( message ) ) ;
180+ } }
181+ >
182+ List Invitations
183+ </ Button >
104184 </ div >
105185 < h2 > Spaces</ h2 >
106186 < ul >
107187 { spaces . map ( ( space ) => {
108188 return (
109189 < li key = { space . id } >
110- < h3 > { space . id } </ h3 >
190+ < h3 > Space id: { space . id } </ h3 >
111191 < Button
112192 onClick = { ( ) => {
113193 const message : RequestSubscribeToSpace = { type : 'subscribe-space' , id : space . id } ;
@@ -116,6 +196,47 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP
116196 >
117197 Get data and subscribe to Space
118198 </ Button >
199+ < br />
200+ { availableAccounts . map ( ( invitee ) => {
201+ return (
202+ < Button
203+ key = { invitee . accountId }
204+ onClick = { async ( ) => {
205+ if ( ! space . state ) {
206+ console . error ( 'No state found for space' ) ;
207+ return ;
208+ }
209+ const spaceEvent = await Effect . runPromiseExit (
210+ createInvitation ( {
211+ author : {
212+ signaturePublicKey : accountId ,
213+ encryptionPublicKey : 'TODO' ,
214+ signaturePrivateKey,
215+ } ,
216+ previousEventHash : space . state . lastEventHash ,
217+ invitee : {
218+ signaturePublicKey : invitee . accountId ,
219+ encryptionPublicKey : 'TODO' ,
220+ } ,
221+ } ) ,
222+ ) ;
223+ if ( Exit . isFailure ( spaceEvent ) ) {
224+ console . error ( 'Failed to create invitation' , spaceEvent ) ;
225+ return ;
226+ }
227+ const message : EventMessage = { type : 'event' , event : spaceEvent . value , spaceId : space . id } ;
228+ websocketConnection ?. send ( JSON . stringify ( message ) ) ;
229+ } }
230+ >
231+ Invite { invitee . accountId . substring ( 0 , 4 ) }
232+ </ Button >
233+ ) ;
234+ } ) }
235+ < h3 > State</ h3 >
236+ < DebugSpaceState state = { space . state } />
237+ < h3 > Events</ h3 >
238+ < DebugSpaceEvents events = { space . events } />
239+ < hr />
119240 </ li >
120241 ) ;
121242 } ) }
@@ -132,33 +253,24 @@ export const ChooseAccount = () => {
132253 < h1 > Choose account</ h1 >
133254 < Button
134255 onClick = { ( ) => {
135- setAccount ( {
136- accountId : '0262701b2eb1b6b37ad03e24445dfcad1b91309199e43017b657ce2604417c12f5' ,
137- signaturePrivateKey : '88bb6f20de8dc1787c722dc847f4cf3d00285b8955445f23c483d1237fe85366' ,
138- } ) ;
256+ setAccount ( availableAccounts [ 0 ] ) ;
139257 } }
140258 >
141- `abc`
259+ { availableAccounts [ 0 ] . accountId . substring ( 0 , 4 ) }
142260 </ Button >
143261 < Button
144262 onClick = { ( ) => {
145- setAccount ( {
146- accountId : '03bf5d2a1badf15387b08a007d1a9a13a9bfd6e1c56f681e251514d9ba10b57462' ,
147- signaturePrivateKey : '1eee32d3bc202dcb5d17c3b1454fb541d2290cb941860735408f1bfe39e7bc15' ,
148- } ) ;
263+ setAccount ( availableAccounts [ 1 ] ) ;
149264 } }
150265 >
151- `cde`
266+ { availableAccounts [ 1 ] . accountId . substring ( 0 , 4 ) }
152267 </ Button >
153268 < Button
154269 onClick = { ( ) => {
155- setAccount ( {
156- accountId : '0351460706cf386282d9b6ebee2ccdcb9ba61194fd024345e53037f3036242e6a2' ,
157- signaturePrivateKey : '434518a2c9a665a7c20da086232c818b6c1592e2edfeecab29a40cf5925ca8fe' ,
158- } ) ;
270+ setAccount ( availableAccounts [ 2 ] ) ;
159271 } }
160272 >
161- `def`
273+ { availableAccounts [ 2 ] . accountId . substring ( 0 , 4 ) }
162274 </ Button >
163275 Account: { account ?. accountId ? account . accountId : 'none' }
164276 < hr />
0 commit comments