Users list with online statuses syncrhonization works

This commit is contained in:
Pavel Murygin 2021-01-14 12:17:36 +03:00
parent 4879878e9f
commit 735c5cbe0c
3 changed files with 126 additions and 67 deletions

View File

@ -3,35 +3,38 @@ import React, { useEffect, useState } from 'react';
import { connect } from 'src/fluence'; import { connect } from 'src/fluence';
import './App.scss'; import './App.scss';
import { FluenceClientContext } from './FluenceClientContext'; import { FluenceClientContext, useFluenceClient } from './FluenceClientContext';
import { UserList } from './UserList'; import { UserList } from './UserList';
import * as calls from 'src/fluence/calls'; import * as calls from 'src/fluence/calls';
const App = () => { const App = () => {
const [client, setClient] = useState<FluenceClient | null>(null); const [client, setClient] = useState<FluenceClient | null>(null);
const [isConnected, setIsConnected] = useState<boolean>(false); const [isInRoom, setIsInRoom] = useState<boolean>(false);
const [isInRoom, setIsInRoom] = useState(false);
const [nickName, setNickName] = useState('myNickName'); const [nickName, setNickName] = useState('myNickName');
useEffect(() => { useEffect(() => {
const fn = async () => { const fn = async () => {
const c = await connect(); const c = await connect();
setIsConnected(true);
setClient(c); setClient(c);
}; };
// fn(); fn();
}, []); }, []);
const joinRoom = async () => { const joinRoom = async () => {
const c = await connect(); if (!client) {
setIsConnected(true); return;
setClient(c); }
await calls.joinRoom(nickName);
await calls.joinRoom(client, nickName);
setIsInRoom(true); setIsInRoom(true);
}; };
const leaveRoom = async () => { const leaveRoom = async () => {
await calls.leaveRoom(); if (!client) {
return;
}
await calls.leaveRoom(client);
setIsInRoom(false); setIsInRoom(false);
}; };
@ -39,7 +42,7 @@ const App = () => {
<FluenceClientContext.Provider value={client}> <FluenceClientContext.Provider value={client}>
<div className="App"> <div className="App">
<div> <div>
<div>Connection status: {isConnected ? 'connected' : 'disconnected'}</div> <div>Connection status: {client ? 'connected' : 'disconnected'}</div>
<div> <div>
<label>Nickname: </label> <label>Nickname: </label>
<input <input
@ -53,7 +56,7 @@ const App = () => {
/> />
</div> </div>
<div> <div>
<button disabled={isInRoom} onClick={joinRoom}> <button disabled={isInRoom || !client} onClick={joinRoom}>
Join Room Join Room
</button> </button>
</div> </div>
@ -64,7 +67,7 @@ const App = () => {
</div> </div>
</div> </div>
<div>{isConnected && client && isInRoom && <UserList />}</div> <div>{isInRoom && client && <UserList selfName={nickName} />}</div>
</div> </div>
</FluenceClientContext.Provider> </FluenceClientContext.Provider>
); );

View File

@ -28,7 +28,7 @@ type PeerId = string;
const refreshTimeoutMs = 7000; const refreshTimeoutMs = 7000;
export const UserList = () => { export const UserList = (props: { selfName: string }) => {
const client = useFluenceClient()!; const client = useFluenceClient()!;
const [users, setUsers] = useState<Map<PeerId, User>>(new Map()); const [users, setUsers] = useState<Map<PeerId, User>>(new Map());
@ -45,7 +45,7 @@ export const UserList = () => {
calls.updateOnlineStatuses(client); calls.updateOnlineStatuses(client);
}, refreshTimeoutMs); }, refreshTimeoutMs);
subscribeToEvent(client, fluentPadServiceId, notifyUserAddedFnName, (args, _) => { const unsub1 = subscribeToEvent(client, fluentPadServiceId, notifyUserAddedFnName, (args, _) => {
const users = args.flatMap((x) => x).flatMap((x) => x) as calls.User[]; const users = args.flatMap((x) => x).flatMap((x) => x) as calls.User[];
setUsers((prev) => { setUsers((prev) => {
const result = new Map(prev); const result = new Map(prev);
@ -54,29 +54,31 @@ export const UserList = () => {
continue; continue;
} }
const isCurrentUser = u.peer_id === client.selfPeerId.toB58String();
result.set(u.peer_id, { result.set(u.peer_id, {
name: u.name, name: u.name,
id: u.peer_id, id: u.peer_id,
isOnline: false, isOnline: isCurrentUser,
shouldBecomeOnline: false, shouldBecomeOnline: isCurrentUser,
}); });
} }
return result; return result;
}); });
}); });
subscribeToEvent(client, fluentPadServiceId, notifyUserRemovedFnName, (args, _) => { const unsub2 = subscribeToEvent(client, fluentPadServiceId, notifyUserRemovedFnName, (args, _) => {
const users = args.flatMap((x) => x) as calls.User[]; const users = args.flatMap((x) => x) as PeerId[];
setUsers((prev) => { setUsers((prev) => {
const result = new Map(prev); const result = new Map(prev);
for (let u of users) { for (let u of users) {
result.delete(u.peer_id); result.delete(u);
} }
return result; return result;
}); });
}); });
const unsub = subscribeToEvent(client, fluentPadServiceId, notifyOnlineFnName, (args, _) => { const unsub3 = subscribeToEvent(client, fluentPadServiceId, notifyOnlineFnName, (args, _) => {
const [[peerId], immediately] = args; const [[peerId], immediately] = args;
setUsers((prev) => { setUsers((prev) => {
const result = new Map(prev); const result = new Map(prev);
@ -94,11 +96,14 @@ export const UserList = () => {
}); });
// don't block // don't block
calls.getInitialUserList(client); calls.getUserList(client);
calls.notifySelfAdded(client, props.selfName);
return () => { return () => {
clearTimeout(listRefreshTimer); clearTimeout(listRefreshTimer);
unsub(); unsub1();
unsub2();
unsub3();
}; };
}, []); }, []);

View File

@ -5,6 +5,7 @@ import {
historyServiceId, historyServiceId,
notifyOnlineFnName, notifyOnlineFnName,
notifyUserAddedFnName, notifyUserAddedFnName,
notifyUserRemovedFnName,
servicesNodePid, servicesNodePid,
userListServiceId, userListServiceId,
} from './constants'; } from './constants';
@ -40,7 +41,13 @@ const throwIfError = (result: ServiceResult) => {
} }
}; };
const notifyOnlineStatusesAir = ` export const updateOnlineStatuses = async (client: FluenceClient) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call node (userlist "get_users") [] allUsers)
(fold allUsers.$.users! u (fold allUsers.$.users! u
(par (par
(seq (seq
@ -59,16 +66,6 @@ const notifyOnlineStatusesAir = `
(next u) (next u)
) )
) )
`;
export const updateOnlineStatuses = async (client: FluenceClient) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call node (userlist "get_users") [] allUsers)
${notifyOnlineStatusesAir}
) )
) )
`, `,
@ -86,20 +83,54 @@ export const updateOnlineStatuses = async (client: FluenceClient) => {
sendParticle(client, particle); sendParticle(client, particle);
}; };
export const getInitialUserList = async (client: FluenceClient) => { export const notifySelfAdded = (client: FluenceClient, name: string) => {
const particle = new Particle( const particle = new Particle(
` `
(seq (seq
(call myRelay ("op" "identity") []) (call myRelay ("op" "identity") [])
(seq (seq
(call node (userlist "get_users") [] allUsers) (call node (userlist "get_users") [] allUsers)
(fold allUsers.$.users! u
(par (par
(seq
(call u.$.relay_id ("op" "identity") [])
(call u.$.peer_id (fluentPadServiceId notifyUserAdded) [myUser])
)
(next u)
)
)
)
)
`,
{
node: servicesNodePid,
userlist: userListServiceId,
myRelay: client.relayPeerID.toB58String(),
myPeerId: client.selfPeerId.toB58String(),
fluentPadServiceId: fluentPadServiceId,
notifyUserAdded: notifyUserAddedFnName,
myUser: {
name: name,
peer_id: client.selfPeerId.toB58String(),
relay_id: client.relayPeerID.toB58String(),
},
},
);
sendParticle(client, particle);
};
export const getUserList = async (client: FluenceClient) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call node (userlist "get_users") [] allUsers)
(seq (seq
(call myRelay ("op" "identity") []) (call myRelay ("op" "identity") [])
(call myPeerId (fluentPadServiceId notifyUserAdded) [allUsers.$.users!]) (call myPeerId (fluentPadServiceId notifyUserAdded) [allUsers.$.users!])
) )
${notifyOnlineStatusesAir}
)
) )
) )
`, `,
@ -117,7 +148,7 @@ export const getInitialUserList = async (client: FluenceClient) => {
await sendParticle(client, particle); await sendParticle(client, particle);
}; };
export const joinRoom = async (nickName: string) => { export const joinRoom = async (client: FluenceClient, nickName: string) => {
let joinRoomAir = ` let joinRoomAir = `
(call node (userlist "join") [user] result) (call node (userlist "join") [user] result)
`; `;
@ -125,30 +156,50 @@ export const joinRoom = async (nickName: string) => {
const data = new Map(); const data = new Map();
data.set('user', { data.set('user', {
name: nickName, name: nickName,
peer_id: fluenceClient.selfPeerId.toB58String(), peer_id: client.selfPeerId.toB58String(),
relay_id: fluenceClient.relayPeerID.toB58String(), relay_id: client.relayPeerID.toB58String(),
}); });
data.set('userlist', userListServiceId); data.set('userlist', userListServiceId);
data.set('node', servicesNodePid); data.set('node', servicesNodePid);
const [result] = await fluenceClient.fetch<[GetUsersResult]>(joinRoomAir, ['result'], data); const [result] = await client.fetch<[GetUsersResult]>(joinRoomAir, ['result'], data);
throwIfError(result); throwIfError(result);
return result.users; return result.users;
}; };
export const leaveRoom = async () => { export const leaveRoom = async (client: FluenceClient) => {
let leaveRoomAir = ` const particle = new Particle(
(call node (userlist "leave") [ownPeerId] callResult) `
`; (seq
(call myRelay ("op" "identity") [])
(seq
(call node (userlist "leave") [myPeerId])
(seq
(call node (userlist "get_users") [] allUsers)
(fold allUsers.$.users! u
(par
(seq
(call u.$.relay_id ("op" "identity") [])
(call u.$.peer_id (fluentPadServiceId notifyUserRemoved) [myPeerId])
)
(next u)
)
)
)
)
)
`,
{
node: servicesNodePid,
userlist: userListServiceId,
myRelay: client.relayPeerID.toB58String(),
myPeerId: client.selfPeerId.toB58String(),
fluentPadServiceId: fluentPadServiceId,
notifyUserRemoved: notifyUserRemovedFnName,
},
);
const data = new Map(); await sendParticle(client, particle);
data.set('ownPeerId', fluenceClient.selfPeerId.toB58String());
data.set('userlist', userListServiceId);
data.set('node', servicesNodePid);
const [result] = await fluenceClient.fetch<[ServiceResult]>(leaveRoomAir, ['callResult'], data);
throwIfError(result);
return result;
}; };
export const removeUser = async (userPeerId: string) => { export const removeUser = async (userPeerId: string) => {