-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathApp.tsx
More file actions
94 lines (79 loc) · 3.38 KB
/
App.tsx
File metadata and controls
94 lines (79 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import {useEffect, useMemo, useState} from 'react'
import { AnyDocumentId, DocHandle, Repo } from '@automerge/automerge-repo';
import { BrowserWebSocketClientAdapter } from '@automerge/automerge-repo-network-websocket';
import { IndexedDBStorageAdapter } from '@automerge/automerge-repo-storage-indexeddb';
import './App.css'
import { Chat } from './components/Chat'
import { UserSettings } from './components/UserSettings'
import { LoginRequiredError, createClient, createVerifier } from '@featherscloud/auth'
import { ChatDocument, CloudAuthUser, Message, User, sha256 } from './utils';
import {ChatContext, ChatContextValue} from "./components/ChatContext.tsx";
// Initialize Feathers Cloud Auth
const appId = import.meta.env.VITE_CLOUD_APP_ID as string;
const auth = createClient({ appId });
const verifier = createVerifier({ appId });
// Initialize Automerge
const repo = new Repo({
network: [ new BrowserWebSocketClientAdapter('wss://sync.automerge.org') ],
storage: new IndexedDBStorageAdapter()
});
const automergeUrl = import.meta.env.VITE_AUTOMERGE_URL as AnyDocumentId;
function App() {
// Keep references to message list, current user and Automerge handle
const [messages, setMessages] = useState<Message[]>([])
const [cloudAuthUser, setCloudAuthUser] = useState<CloudAuthUser | null>(null)
const [user, setUser] = useState<User|null>(null)
const [users, setUsers] = useState<User[]>([])
const [handle, setHandle] = useState<DocHandle<ChatDocument> | null>(null)
// Sets the current user's username and stores it in the document
const createUser = async (input: string) => {
const username = input.trim().toLowerCase()
if (users.find(user => user.username === username)) {
alert('Username already taken, please choose another one')
} else if (handle && cloudAuthUser) {
const emailHash = await sha256(cloudAuthUser?.email || 'unknown');
handle.change(doc => {
doc.users.push({
id: cloudAuthUser.id,
avatar: `https://www.gravatar.com/avatar/${emailHash}`,
username
})
})
}
}
// Initialize the application
const init = async () => {
try {
// Get Feathers Cloud Auth access token
const accessToken = await auth.getAccessToken();
// Verify our token (this will redirect to the login screen if necessary)
const { user: cloudAuthUser } = await verifier.verify(accessToken);
const currentHandle = repo.find<ChatDocument>(automergeUrl)
// Update application data when document changes
currentHandle.on('change', ({doc}) => {
const existingUser = doc.users.find(user => user.id === cloudAuthUser?.id) || null
setUser(existingUser)
setMessages(doc.messages)
setUsers(doc.users)
})
setCloudAuthUser(cloudAuthUser)
setHandle(currentHandle)
} catch (error) {
// Redirect to Feathers Cloud Auth login
if (error instanceof LoginRequiredError) {
window.location.href = await auth.getLoginUrl(error)
}
throw error;
}
}
const contextValue = useMemo<ChatContextValue>(()=>({user, handle}), [user, handle]);
useEffect(() => {
init()
}, [])
if (handle?.isReady()) {
return user === null
? <UserSettings onSubmit={createUser} />
: <ChatContext.Provider value={contextValue}><Chat messages={messages} users={users} /></ChatContext.Provider>
}
}
export default App