diff --git a/packages/preload/src/settings.ts b/packages/preload/src/settings.ts index 179ba25..e612d43 100644 --- a/packages/preload/src/settings.ts +++ b/packages/preload/src/settings.ts @@ -42,10 +42,8 @@ export function getClientId() { return fileContents; } -export function setClientId(id: string) { - if(!validUuid(id)) return false; - writeFileSync(clientIdPath, id); - return true; +export function setClientId(id: string | null) { + writeFileSync(clientIdPath, id ?? ''); } export function getHomeServer() { @@ -58,12 +56,8 @@ export function getHomeServer() { } } -export function setHomeServer(url: string) { - if(url === null) { - writeFileSync(homeServerPath, ''); - return null - } - writeFileSync(homeServerPath, url); +export function setHomeServer(url: string | null) { + writeFileSync(homeServerPath, url ?? ''); } export function getSessionToken() { @@ -72,6 +66,6 @@ export function getSessionToken() { return token; } -export function setSessionToken(token: string) { - writeFileSync(sessionTokenPath, token); +export function setSessionToken(token: string | null) { + writeFileSync(sessionTokenPath, token ?? ''); } \ No newline at end of file diff --git a/packages/renderer/src/App.tsx b/packages/renderer/src/App.tsx new file mode 100644 index 0000000..dcfd37e --- /dev/null +++ b/packages/renderer/src/App.tsx @@ -0,0 +1,62 @@ +import { createContext, useCallback, useEffect, useState, useMemo } from 'react'; +import Channels from './pages/Channels'; +import Chat from './pages/Chat'; +import Sidebar from './components/Sidebar'; +import NewAccount from './pages/NewAccount'; +import ServerConnection from './components/ServerConnection'; +import EphemeralState from './contexts/EphemeralState/EphemeralState'; +import PersistentState from '/@/contexts/PersistentState/PersistentState'; +import Router from './Router'; + +export default function App() { + const [transparent, setTransparent] = useState(false); + + + // font-size: 16px; + // font-family: 'Lato', sans-serif; + // font-family: 'Red Hat Text', sans-serif; + // font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + // color: #f8f8f2; + // background: #282a36; + + return ( + <> + + + + +
+ + + + + +
+ + ); +} \ No newline at end of file diff --git a/packages/renderer/src/Router.tsx b/packages/renderer/src/Router.tsx new file mode 100644 index 0000000..da099d3 --- /dev/null +++ b/packages/renderer/src/Router.tsx @@ -0,0 +1,42 @@ +import ServerConnection from "./components/ServerConnection"; +import Sidebar from "./components/Sidebar"; +import useHomeServer from "./contexts/PersistentState/useHomeServerNative"; +import useClientId from "./hooks/useClientId"; +import useSessionToken from "./hooks/useSessionToken"; +import Channels from "./pages/Channels"; +import Chat from "./pages/Chat"; +import NewAccount from "./pages/NewAccount"; + +interface RouterProps { + [name: string]: React.ReactNode; + children?: React.ReactNode; +} + +export default function Router(props: RouterProps) { + + const { clientId } = useClientId(); + const { sessionToken } = useSessionToken(); + const { homeServer } = useHomeServer(); + + const configured = + homeServer !== null && + clientId !== null && + sessionToken !== null; + + return ( + configured ? ( + + + + + + + ) : ( + + ) + ) + +} \ No newline at end of file diff --git a/packages/renderer/src/components/Logout.tsx b/packages/renderer/src/components/Logout.tsx new file mode 100644 index 0000000..b92d157 --- /dev/null +++ b/packages/renderer/src/components/Logout.tsx @@ -0,0 +1,10 @@ +import useSessionToken from "../hooks/useSessionToken" + +export default function Logout() { + + const { setSessionToken } = useSessionToken(); + + return ( + + ) +} \ No newline at end of file diff --git a/packages/renderer/src/components/ServerConnection.tsx b/packages/renderer/src/components/ServerConnection.tsx index 9ef6940..30f48cf 100644 --- a/packages/renderer/src/components/ServerConnection.tsx +++ b/packages/renderer/src/components/ServerConnection.tsx @@ -1,6 +1,5 @@ import { createContext, PropsWithChildren, ReactNode, useEffect, useMemo } from "react"; import { connectApi } from "../lib/api"; -import { usePrevious } from "./usePrevious"; interface ServerConnectionProps { children: ReactNode, diff --git a/packages/renderer/src/components/Totp.tsx b/packages/renderer/src/components/Totp.tsx index 993c350..466228b 100644 --- a/packages/renderer/src/components/Totp.tsx +++ b/packages/renderer/src/components/Totp.tsx @@ -1,15 +1,15 @@ import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import 'reactjs-popup/dist/index.css'; import { useApi } from '../lib/useApi'; -import { ClientIdContext } from '../pages/App'; import QR from 'qrcode'; -import { usePrevious } from './usePrevious'; +import { usePrevious } from '../hooks/usePrevious'; +import useClientId from '../hooks/useClientId'; export default function Totp () { const [open, setOpen] = useState(false); const previousOpen = usePrevious(open); - const { clientId } = useContext(ClientIdContext); + const { clientId } = useClientId() const [qr, setQr] = useState(null); const [key, setKey] = useState(''); diff --git a/packages/renderer/src/contexts/EphemeralState/EphemeralState.tsx b/packages/renderer/src/contexts/EphemeralState/EphemeralState.tsx new file mode 100644 index 0000000..bc0c253 --- /dev/null +++ b/packages/renderer/src/contexts/EphemeralState/EphemeralState.tsx @@ -0,0 +1,38 @@ +import { createContext, useState, useMemo, useEffect } from "react"; + +export const ChannelContext = createContext<{ + channel: string | null, + setChannel: (uid: string) => void +}>({ + channel: null, + setChannel: () => {}, +}); +export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {}); + + +export default function EphemeralState(props: { + onTransparencyChange: (value: boolean) => void, + children?: React.ReactNode +}) { + + const [channel, setChannel] = useState(null); + const [transparent, setTransparent] = useState(false); + + const channelContextValue = useMemo(() => { + return { channel, setChannel }; + }, [channel, setChannel]); + + useEffect(() => { + if('onTransparencyChange' in props) { + props.onTransparencyChange(transparent) + } + }, [transparent]) + + return ( + + + {props.children} + + + ); +} \ No newline at end of file diff --git a/packages/renderer/src/contexts/PersistentState/PersistentState.tsx b/packages/renderer/src/contexts/PersistentState/PersistentState.tsx new file mode 100644 index 0000000..0356cbc --- /dev/null +++ b/packages/renderer/src/contexts/PersistentState/PersistentState.tsx @@ -0,0 +1,46 @@ +import { createContext } from "react"; +import useHomeServerNative from "./useHomeServerNative"; +import useClientIdNative from "./useClientIdNative"; +import useSessionTokenNative from "./useSessionTokenNative"; + + + +export const ClientIdContext = createContext<{ + clientId: string | null, + setClientId: (id: string | null) => void +}>({ + clientId: null, + setClientId: () => {} +}); +export const HomeServerContext = createContext<{ + homeServer: string | null, + setHomeServer: (uid: string | null) => void +}>({ + homeServer: null, + setHomeServer: () => {} +}); +export const SessionTokenContext = createContext<{ + sessionToken: string | null, + setSessionToken: (token: string | null) => void +}>({ + sessionToken: null, + setSessionToken() {} +}) + + +export default function PersistentState(props: any) { + + const homeServerContextValue = useHomeServerNative(); + const clientIdContextValue = useClientIdNative(); + const sessionTokenContextValue = useSessionTokenNative(); + + return ( + + + + {props.children} + + + + ) +} \ No newline at end of file diff --git a/packages/renderer/src/contexts/PersistentState/useClientIdNative.ts b/packages/renderer/src/contexts/PersistentState/useClientIdNative.ts new file mode 100644 index 0000000..0264506 --- /dev/null +++ b/packages/renderer/src/contexts/PersistentState/useClientIdNative.ts @@ -0,0 +1,24 @@ + +import { useCallback, useMemo, useState } from 'react'; +import { + getClientId, + setClientId +} from '/@/lib/native'; + +export default function useClientIdNative() { + const [cachedClientId, setCachedClientId] = + useState(getClientId()); + + const setClientIdCallback = useCallback((id: string | null) => { + setClientId(id); + setCachedClientId(getClientId()); + }, [cachedClientId]); + + return useMemo(() => { + return { + clientId: cachedClientId, + setClientId: setClientIdCallback + }; + }, [cachedClientId, setClientIdCallback]); + +} \ No newline at end of file diff --git a/packages/renderer/src/contexts/PersistentState/useHomeServerNative.ts b/packages/renderer/src/contexts/PersistentState/useHomeServerNative.ts new file mode 100644 index 0000000..877d7d1 --- /dev/null +++ b/packages/renderer/src/contexts/PersistentState/useHomeServerNative.ts @@ -0,0 +1,23 @@ + +import { useCallback, useMemo, useState } from 'react'; +import { + setHomeServer, + getHomeServer +} from '/@/lib/native'; + +export default function useHomeServer() { + const [cachedHomeServer, setCachedHomeServer] = + useState(getHomeServer()); + + const setHomeServerCallback = useCallback((url: string | null) => { + setHomeServer(url); + setCachedHomeServer(getHomeServer()); + }, [cachedHomeServer]); + + return useMemo(() => { + return { + homeServer: cachedHomeServer, + setHomeServer: setHomeServerCallback + }; + }, [cachedHomeServer, setHomeServerCallback]) +} \ No newline at end of file diff --git a/packages/renderer/src/contexts/PersistentState/useSessionTokenNative.ts b/packages/renderer/src/contexts/PersistentState/useSessionTokenNative.ts new file mode 100644 index 0000000..911d628 --- /dev/null +++ b/packages/renderer/src/contexts/PersistentState/useSessionTokenNative.ts @@ -0,0 +1,23 @@ + +import { useCallback, useMemo, useState } from 'react'; +import { + getSessionToken, + setSessionToken +} from '/@/lib/native'; + +export default function useSessionTokenNative() { + const [cachedSessionToken, setCachedSessionToken] = + useState(getSessionToken()); + + const setSessionTokenCallback = useCallback((token: string | null) => { + setSessionToken(token); + setCachedSessionToken(getSessionToken()); + }, [cachedSessionToken]); + + return useMemo(() => { + return { + sessionToken: cachedSessionToken, + setSessionToken: setSessionTokenCallback + }; + }, [cachedSessionToken, setSessionTokenCallback]) +} \ No newline at end of file diff --git a/packages/renderer/src/hooks/useChannel.ts b/packages/renderer/src/hooks/useChannel.ts new file mode 100644 index 0000000..7058e69 --- /dev/null +++ b/packages/renderer/src/hooks/useChannel.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { ChannelContext } from "../contexts/EphemeralState/EphemeralState"; + +export default function useChannel() { + return useContext(ChannelContext); +} \ No newline at end of file diff --git a/packages/renderer/src/hooks/useClientId.ts b/packages/renderer/src/hooks/useClientId.ts new file mode 100644 index 0000000..cdcc8d3 --- /dev/null +++ b/packages/renderer/src/hooks/useClientId.ts @@ -0,0 +1,7 @@ +import { useContext } from "react"; +import { ClientIdContext } from "../contexts/PersistentState/PersistentState"; + + +export default function useClientId() { + return useContext(ClientIdContext); +} \ No newline at end of file diff --git a/packages/renderer/src/hooks/useHomeServer.ts b/packages/renderer/src/hooks/useHomeServer.ts new file mode 100644 index 0000000..c6582d4 --- /dev/null +++ b/packages/renderer/src/hooks/useHomeServer.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { HomeServerContext } from "../contexts/PersistentState/PersistentState"; + +export default function useHomeServer() { + return useContext(HomeServerContext); +} \ No newline at end of file diff --git a/packages/renderer/src/components/usePrevious.tsx b/packages/renderer/src/hooks/usePrevious.ts similarity index 100% rename from packages/renderer/src/components/usePrevious.tsx rename to packages/renderer/src/hooks/usePrevious.ts diff --git a/packages/renderer/src/hooks/useSessionToken.ts b/packages/renderer/src/hooks/useSessionToken.ts new file mode 100644 index 0000000..5f1d49f --- /dev/null +++ b/packages/renderer/src/hooks/useSessionToken.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { SessionTokenContext } from "../contexts/PersistentState/PersistentState"; + +export default function useSessionToken() { + return useContext(SessionTokenContext); +} \ No newline at end of file diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx index 81bfd83..ac3fbc1 100644 --- a/packages/renderer/src/index.tsx +++ b/packages/renderer/src/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import Sidebar from './components/Sidebar'; -import App from './pages/App'; +import App from './App'; const container = document.getElementById('app'); if(container !== null) { diff --git a/packages/renderer/src/pages/App.tsx b/packages/renderer/src/pages/App.tsx deleted file mode 100644 index f785317..0000000 --- a/packages/renderer/src/pages/App.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { createContext, useCallback, useEffect, useState, useMemo } from 'react'; -import Channels from './Channels'; -import Chat from './Chat'; -import { - getClientId, - setClientId, - getHomeServer, - setHomeServer, - getSessionToken, - setSessionToken -} from '../lib/native'; -import { useApi } from '../lib/useApi'; -import Sidebar from '../components/Sidebar'; -import NewAccount from './NewAccount'; -import ServerConnection from '../components/ServerConnection'; - -export const ChannelContext = createContext<{ - channel: string | null, - setChannel: (uid: string) => void -}>({ - channel: null, - setChannel: () => {}, -}); -export const ClientIdContext = createContext<{ - clientId: string | null, - setClientId: (id: string | null) => void -}>({ - clientId: null, - setClientId: () => {} -}); -export const HomeServerContext = createContext<{ - homeServer: string | null, - setHomeServer: (uid: string | null) => void -}>({ - homeServer: null, - setHomeServer: () => {} -}); -export const SessionTokenContext = createContext<{ - sessionToken: string | null, - setSessionToken: (token: string) => void -}>({ - sessionToken: null, - setSessionToken() {} -}) -export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {}); - -export default function App() { - const [channel, setChannel] = useState(null); - const [clientId, setCachedClientId] = useState(getClientId()); - const [homeServer, setCachedHomeServer] = useState(getHomeServer()); - const channelContextValue = { channel, setChannel } - const [cachedSessionToken, setCachedSessionToken] = useState(null); - const [transparent, setTransparent] = useState(false); - - const setHomeServerCallback = useCallback((url: string | null) => { - console.log('SETTING HOME SERVER', url) - setHomeServer(url); - setCachedHomeServer(getHomeServer()); - }, [homeServer]); - - const homeServerContextValue = useMemo(() => { - return { - homeServer, - setHomeServer: setHomeServerCallback - }; - }, [homeServer, setHomeServerCallback]) - - // persist given clientId to disk - useEffect(() => { - if(clientId === null) return; - setClientId(clientId); - }, [clientId]); - - const updateCachedSessionToken = useCallback((token?: string) => { - setSessionToken(token ?? ''); - setCachedSessionToken(getSessionToken()); - }, []); - - const SessionTokenContextValue = useMemo(() => { - return { - sessionToken: cachedSessionToken, - setSessionToken: updateCachedSessionToken - } - }, [cachedSessionToken, updateCachedSessionToken]) - - // const { send } = useApi({ - // 'client:new'(data: string) { - // setCachedClientId(data); - // }, - // }, [setCachedClientId]); - - // useEffect(() => { - // if(clientId !== null) return; - // send('client:new'); - // }, [clientId]); - - const clientIdContextValue = { clientId, setClientId: setCachedClientId }; - - // font-size: 16px; - // font-family: 'Lato', sans-serif; - // font-family: 'Red Hat Text', sans-serif; - // font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - // color: #f8f8f2; - // background: #282a36; - - return ( - <> - - - - - - - - - -
- {(cachedSessionToken === null || homeServer === null) ? ( - - ) : ( - - - - - - - )} -
-
-
-
-
-
- - ); -} \ No newline at end of file diff --git a/packages/renderer/src/pages/Channels.tsx b/packages/renderer/src/pages/Channels.tsx index b1c344e..3859844 100644 --- a/packages/renderer/src/pages/Channels.tsx +++ b/packages/renderer/src/pages/Channels.tsx @@ -1,10 +1,14 @@ import { useCallback, useContext, useEffect, useRef, useState } from 'react'; -import { ChannelContext, ClientIdContext, HomeServerContext } from './App'; import { useApi } from '../lib/useApi'; import type { IMessage } from './Message'; import NameTextbox from './NameTextbox'; import LoginQR from './LoginQR'; import Totp from '../components/Totp'; +import useChannel from '../hooks/useChannel'; +import useClientId from '../hooks/useClientId'; +import useHomeServer from '../contexts/PersistentState/useHomeServerNative'; +import Logout from '../components/Logout'; +import { CgHashtag } from 'react-icons/cg'; interface IChannel { uid: string; @@ -12,11 +16,11 @@ interface IChannel { } function Hashmark() { - return #; + }}>#; } interface IUnreads { @@ -28,10 +32,10 @@ export default function Channels() { const [channels, setChannels] = useState([]); const [unreads, setUnreads] = useState({}); - const { channel, setChannel } = useContext(ChannelContext); - const { clientId } = useContext(ClientIdContext); + const { channel, setChannel } = useChannel() + const { clientId } = useClientId() - const { setHomeServer } = useContext(HomeServerContext); + const { setHomeServer } = useHomeServer(); const { send } = useApi({ 'channels:list'(data: IChannel[]) { @@ -142,7 +146,7 @@ export default function Channels() { // lineHeight: '20px' }}>ADD

-

+

{/* */} {/* */} diff --git a/packages/renderer/src/pages/Chat.tsx b/packages/renderer/src/pages/Chat.tsx index 054a34c..cf5155f 100644 --- a/packages/renderer/src/pages/Chat.tsx +++ b/packages/renderer/src/pages/Chat.tsx @@ -1,10 +1,11 @@ import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { v4 } from 'uuid'; import { useApi } from '../lib/useApi'; -import { ChannelContext, ClientIdContext } from './App'; import type { IMessage} from './Message'; import { Message } from './Message'; import { MdSend } from 'react-icons/md'; +import useChannel from '../hooks/useChannel'; +import useClientId from '../hooks/useClientId'; function createMessage(from: string, text: string, channel: string, t = 0): IMessage { @@ -26,8 +27,8 @@ export default () => { const textBoxRef = useRef(null); - const { channel, setChannel } = useContext(ChannelContext); - const { clientId } = useContext(ClientIdContext); + const { channel, setChannel } = useChannel(); + const { clientId } = useClientId(); const { send } = useApi({ 'message:message'(data: IMessage) { diff --git a/packages/renderer/src/pages/LoginQR.tsx b/packages/renderer/src/pages/LoginQR.tsx index 8a0ed29..c3222e7 100644 --- a/packages/renderer/src/pages/LoginQR.tsx +++ b/packages/renderer/src/pages/LoginQR.tsx @@ -1,10 +1,11 @@ import { useContext, useEffect, useState } from "react"; -import { ClientIdContext, HomeServerContext } from "./App"; import QR from 'qrcode'; +import useHomeServer from "../contexts/PersistentState/useHomeServerNative"; +import useClientId from "../hooks/useClientId"; export default function LoginQR() { - const { homeServer } = useContext(HomeServerContext); - const { clientId } = useContext(ClientIdContext); + const { homeServer } = useHomeServer() + const { clientId } = useClientId(); const [qr, setQr] = useState(null); useEffect(() => { diff --git a/packages/renderer/src/pages/NameTextbox.tsx b/packages/renderer/src/pages/NameTextbox.tsx index 11e8508..de0386d 100644 --- a/packages/renderer/src/pages/NameTextbox.tsx +++ b/packages/renderer/src/pages/NameTextbox.tsx @@ -5,12 +5,12 @@ import { useRef, useState, } from 'react'; -import { ClientIdContext } from './App'; +import useClientId from '../hooks/useClientId'; import { useApi } from '../lib/useApi'; export default function NameTextbox() { - const { clientId } = useContext(ClientIdContext); + const { clientId } = useClientId() const [name, setName] = useState(null); const [inputElement, setInputElement] = useState(null); diff --git a/packages/renderer/src/pages/NewAccount.tsx b/packages/renderer/src/pages/NewAccount.tsx index e3e74a0..42d1d9a 100644 --- a/packages/renderer/src/pages/NewAccount.tsx +++ b/packages/renderer/src/pages/NewAccount.tsx @@ -2,8 +2,8 @@ import { useEffect, useState } from "react"; import { useCallback, useContext, useRef } from "react" import ServerConnection from "../components/ServerConnection"; import { useApi } from "../lib/useApi"; -import { ClientIdContext, HomeServerContext, SessionTokenContext, TransparencyContext } from "./App" import QR from 'qrcode'; +import useSessionToken from "../hooks/useSessionToken"; export default function NewAccount() { @@ -73,27 +73,24 @@ export default function NewAccount() { if(connecting) return; setHomeServer(url); setConnecting(true); - try { - const ws = new WebSocket(url); - - ws.addEventListener('open', () => { - setConnecting(false); - setConnection(ws); - setConnectionError(''); - }); - - ws.addEventListener('close', (e) => { - setConnecting(false); - setConnection(null); - console.log(e) - }); + + const ws = new WebSocket(url); - ws.addEventListener('error', (e) => { - setConnectionError('Couldn\'t connect to ' + url) - }); - } catch(e: any) { - console.log('ASDFASDFASDF'); - } + ws.addEventListener('open', () => { + setConnecting(false); + setConnection(ws); + setConnectionError(''); + }); + + ws.addEventListener('close', (e) => { + setConnecting(false); + setConnection(null); + console.log(e) + }); + + ws.addEventListener('error', (e) => { + setConnectionError('Connection failed') + }); }, [connecting]) return ( @@ -101,17 +98,36 @@ export default function NewAccount() { display: 'grid', placeContent: 'center center', height: '100%', + textAlign: 'center' }}> {returning ? ( - <> - Login +
+ + Login + +   +   +   setReturning(false)}>Sign up - +
) : ( <> - - - {connecting ? `Connecting to ${homeServer}` : connectionError} +
+ setReturning(true)}> + Login + +   +   +   + + Sign up + +
+

+ + + + {connecting ? `Connecting...` : connectionError}

{connection !== null && ( @@ -144,7 +160,7 @@ const SignUp = (props: any) => { const [clientId, setClientId] = useState(null); // const [totpToken, setTotpToken] = useState(null); const [qr, setQr] = useState(null); - const { setSessionToken } = useContext(SessionTokenContext) + const { setSessionToken } = useSessionToken(); const { send } = useApi({ 'client:new'(data: any) {