From cc1f84cadca901cba7f11b00248fc4f08552f12e Mon Sep 17 00:00:00 2001 From: Valerie Date: Sat, 23 Jul 2022 17:37:24 -0400 Subject: [PATCH] name changing! --- packages/preload/src/clientId.ts | 42 ++++++++++++++ packages/preload/src/index.ts | 2 +- .../src/lib/{useRouter.tsx => useApi.tsx} | 2 +- packages/renderer/src/pages/App.tsx | 58 +++++++++++++------ packages/renderer/src/pages/Channels.tsx | 20 ++++--- packages/renderer/src/pages/Chat.tsx | 23 ++++---- packages/renderer/src/pages/Message.tsx | 4 +- packages/renderer/src/pages/NameTextbox.tsx | 44 ++++++++++++++ .../server/public/migrations/4-clients.sql | 18 ++++++ .../migrations/5-link-messages-to-clients.sql | 22 +++++++ .../migrations/6-messages-drop-from.sql | 2 + .../server/src/db/snippets/client/get.sql | 1 + .../server/src/db/snippets/client/new.sql | 1 + .../server/src/db/snippets/client/rename.sql | 3 + .../server/src/db/snippets/message/new.sql | 11 +--- .../server/src/db/snippets/message/recent.sql | 16 +++-- packages/server/src/index.ts | 8 ++- packages/server/src/lib/WebSocketServer.ts | 6 +- packages/server/src/routers/client.ts | 25 ++++++++ packages/server/src/routers/message.ts | 10 +++- 20 files changed, 259 insertions(+), 59 deletions(-) create mode 100644 packages/preload/src/clientId.ts rename packages/renderer/src/lib/{useRouter.tsx => useApi.tsx} (84%) create mode 100644 packages/renderer/src/pages/NameTextbox.tsx create mode 100644 packages/server/public/migrations/4-clients.sql create mode 100644 packages/server/public/migrations/5-link-messages-to-clients.sql create mode 100644 packages/server/public/migrations/6-messages-drop-from.sql create mode 100644 packages/server/src/db/snippets/client/get.sql create mode 100644 packages/server/src/db/snippets/client/new.sql create mode 100644 packages/server/src/db/snippets/client/rename.sql create mode 100644 packages/server/src/routers/client.ts diff --git a/packages/preload/src/clientId.ts b/packages/preload/src/clientId.ts new file mode 100644 index 0000000..88ce7d4 --- /dev/null +++ b/packages/preload/src/clientId.ts @@ -0,0 +1,42 @@ +import { resolve } from 'path'; +import { + writeFileSync, + readFileSync, + mkdirSync, + existsSync, +} from 'fs'; + +const appdataPath = process.env.APPDATA || // windows + (process.platform == 'darwin' ? + process.env.HOME + '/Library/Preferences' : //macos + process.env.HOME + '/.local/share'); // linux + +const cornerDataPath = resolve(appdataPath, 'corner'); +const clientIdPath = resolve(cornerDataPath, 'clientId'); + +// --- setup --- + +if(!existsSync(cornerDataPath)) + mkdirSync(cornerDataPath); +if(!existsSync(clientIdPath)) + writeFileSync(clientIdPath, ''); + +// --- helpers --- + +function validUuid(uuid: string) { + return uuid.length === 36; +} + +// --- export --- + +export function getClientId() { + const fileContents = readFileSync(clientIdPath).toString(); + if(!validUuid(fileContents)) return null; + return fileContents; +} + +export function setClientId(id: string) { + if(!validUuid(id)) return false; + writeFileSync(clientIdPath, id); + return true; +} \ No newline at end of file diff --git a/packages/preload/src/index.ts b/packages/preload/src/index.ts index 884f94a..38150a9 100644 --- a/packages/preload/src/index.ts +++ b/packages/preload/src/index.ts @@ -2,5 +2,5 @@ * @module preload */ -export {sha256sum} from './nodeCrypto'; + export { getClientId, setClientId } from './clientId'; export {versions} from './versions'; diff --git a/packages/renderer/src/lib/useRouter.tsx b/packages/renderer/src/lib/useApi.tsx similarity index 84% rename from packages/renderer/src/lib/useRouter.tsx rename to packages/renderer/src/lib/useApi.tsx index e5deed3..0dcab57 100644 --- a/packages/renderer/src/lib/useRouter.tsx +++ b/packages/renderer/src/lib/useApi.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { registerRouter, router, send, unregisterRouter } from './api'; -export function useAPI(actions: Function | object, deps: any[]) { +export function useApi(actions: Function | object, deps: any[]) { const _router = typeof actions === 'object' ? router(actions) : actions; useEffect(() => { registerRouter(_router); diff --git a/packages/renderer/src/pages/App.tsx b/packages/renderer/src/pages/App.tsx index 339dfd8..6a1ce8b 100644 --- a/packages/renderer/src/pages/App.tsx +++ b/packages/renderer/src/pages/App.tsx @@ -1,7 +1,8 @@ -import { createContext, useState } from 'react'; +import { createContext, useEffect, useState } from 'react'; import Channels from './Channels'; import Chat from './Chat'; - +import { getClientId, setClientId } from '#preload'; +import { useApi } from '../lib/useApi'; export const channelContext = createContext<{ channel: string | null, @@ -11,29 +12,50 @@ export const channelContext = createContext<{ setChannel: () => {}, }); +export const clientIdContext = createContext(null); + export default function App() { const [channel, setChannel] = useState(null); - + const [clientId, setCachedClientId] = useState(getClientId()); const channelContextValue = { channel, setChannel }; + + // persist given clientId to disk + useEffect(() => { + if(clientId === null) return; + setClientId(clientId); + }, [clientId]); + + const { send } = useApi({ + 'client:new'(data: string) { + setCachedClientId(data); + }, + }, [setCachedClientId]); + + useEffect(() => { + if(clientId !== null) return; + send('client:new'); + }, [clientId]); return ( - -
+ +
- +
+ +
+
+ +
-
- -
-
-
+ + ); } \ No newline at end of file diff --git a/packages/renderer/src/pages/Channels.tsx b/packages/renderer/src/pages/Channels.tsx index d6ecbc8..6b86ab8 100644 --- a/packages/renderer/src/pages/Channels.tsx +++ b/packages/renderer/src/pages/Channels.tsx @@ -1,7 +1,8 @@ import { useCallback, useContext, useEffect, useRef, useState } from 'react'; -import { channelContext } from './App'; -import { useAPI } from '../lib/useRouter'; +import { channelContext, clientIdContext } from './App'; +import { useApi } from '../lib/useApi'; import type { IMessage } from './Message'; +import NameTextbox from './NameTextbox'; interface IChannel { uid: string; @@ -23,14 +24,13 @@ interface IUnreads { export default function Channels() { const [channels, setChannels] = useState([]); - const {channel, setChannel} = useContext(channelContext); - const [unreads, setUnreads] = useState({}); + + const {channel, setChannel} = useContext(channelContext); + const clientId = useContext(clientIdContext); - - const { send } = useAPI({ + const { send } = useApi({ 'channels:list'(data: IChannel[]) { - // console.log(data) setChannels(data); }, 'channel:add'(channel: IChannel) { @@ -69,6 +69,11 @@ export default function Channels() { }); }, [channel]); + useEffect(() => { + if(clientId === null) return; + send('client:get', clientId); + }, [clientId]); + const textbox = useRef(null); const add = useCallback(() => { if(textbox.current === null) return; @@ -127,6 +132,7 @@ export default function Channels() { borderRadius: '8px', // lineHeight: '20px' }}>ADD + ); } \ No newline at end of file diff --git a/packages/renderer/src/pages/Chat.tsx b/packages/renderer/src/pages/Chat.tsx index 90c814b..2fe00a4 100644 --- a/packages/renderer/src/pages/Chat.tsx +++ b/packages/renderer/src/pages/Chat.tsx @@ -1,7 +1,7 @@ import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { v4 } from 'uuid'; -import { useAPI } from '../lib/useRouter'; -import { channelContext } from './App'; +import { useApi } from '../lib/useApi'; +import { channelContext, clientIdContext } from './App'; import type { IMessage} from './Message'; import { Message } from './Message'; @@ -19,18 +19,20 @@ function createMessage(from: string, text: string, export default () => { const [messages, setMessages] = useState([]); const [hist, setHist] = useState(false); - + const textBoxRef = useRef(null); + const { channel, setChannel } = useContext(channelContext); + const clientId = useContext(clientIdContext); - const { send } = useAPI({ + const { send } = useApi({ 'message:message'(data: IMessage) { if(data.channel !== channel) return; - + setMessages([...messages, data]); }, 'message:recent'(data: { messages: IMessage[] }) { - setMessages(data.messages); + setMessages(data.messages.reverse()); }, }, [messages]); @@ -42,10 +44,11 @@ export default () => { const sendMessage = useCallback(() => { if(textBoxRef.current === null) return; if(channel === null) return; + if(clientId === null) return; send( 'message:message', createMessage( - 'Val', + clientId, textBoxRef.current.innerText, channel, ), @@ -81,9 +84,9 @@ export default () => { bottom: '0px', width: '100%', }}> - {messages.map(message => ( - - ))} + {messages.map(message => ( + + ))}
{ diff --git a/packages/renderer/src/pages/Message.tsx b/packages/renderer/src/pages/Message.tsx index 75c886b..a4b3930 100644 --- a/packages/renderer/src/pages/Message.tsx +++ b/packages/renderer/src/pages/Message.tsx @@ -22,8 +22,7 @@ export function Message({ return ( - <> -
- ); } \ No newline at end of file diff --git a/packages/renderer/src/pages/NameTextbox.tsx b/packages/renderer/src/pages/NameTextbox.tsx new file mode 100644 index 0000000..4ba3e1e --- /dev/null +++ b/packages/renderer/src/pages/NameTextbox.tsx @@ -0,0 +1,44 @@ +import { + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; +import { clientIdContext } from './App'; +import { useApi } from '../lib/useApi'; + + +export default function NameTextbox() { + const clientId = useContext(clientIdContext); + const [name, setName] = useState(null); + const [inputElement, setInputElement] = useState(null); + + const { send } = useApi({ + 'client:get'(_name: string) { + setName(_name); + }, + }, [name, clientId]); + + const update = useCallback(() => { + if(inputElement === null) return; + if(clientId === null) return; + send('client:rename', { + clientId: clientId, + name: inputElement.value, + }); + setName(inputElement.value); + }, [clientId, name]); + + useEffect(() => { + if(clientId === null) return; + if(inputElement === null) return; + send('client:get', clientId); + }, [inputElement, clientId]); + + return ; +} \ No newline at end of file diff --git a/packages/server/public/migrations/4-clients.sql b/packages/server/public/migrations/4-clients.sql new file mode 100644 index 0000000..1e85b05 --- /dev/null +++ b/packages/server/public/migrations/4-clients.sql @@ -0,0 +1,18 @@ + +CREATE TABLE `clients` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uid` varchar(36) NOT NULL, + `name` tinytext NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE PROCEDURE new_client (in name TINYTEXT) BEGIN + DECLARE client_id INT UNSIGNED DEFAULT 0; + INSERT INTO clients (uid, clients.name) VALUES (UUID(), name); + SET client_id = last_insert_id(); + UPDATE clients + SET clients.name=name + WHERE clients.id=client_id; + + SELECT clients.uid FROM clients WHERE clients.id=client_id; +END; diff --git a/packages/server/public/migrations/5-link-messages-to-clients.sql b/packages/server/public/migrations/5-link-messages-to-clients.sql new file mode 100644 index 0000000..9723066 --- /dev/null +++ b/packages/server/public/migrations/5-link-messages-to-clients.sql @@ -0,0 +1,22 @@ + +-- make uid unique in clients +ALTER TABLE `clients` +ADD UNIQUE `uid` (`uid`); + +-- add a sender column for foreign key reference +ALTER TABLE `messages` +ADD `sender_uid` varchar(36) COLLATE 'utf8mb4_general_ci' NOT NULL; + +-- create an anonymous user for all previous messages +SELECT @anon_uid := UUID(); + +INSERT INTO clients (name, uid) +VALUES ('Anonymous', @anon_uid); + +UPDATE messages +SET sender_uid=@anon_uid +WHERE sender_uid=''; + +-- create the foreign key relationship +ALTER TABLE `messages` +ADD FOREIGN KEY (`sender_uid`) REFERENCES `clients` (`uid`); \ No newline at end of file diff --git a/packages/server/public/migrations/6-messages-drop-from.sql b/packages/server/public/migrations/6-messages-drop-from.sql new file mode 100644 index 0000000..540dae3 --- /dev/null +++ b/packages/server/public/migrations/6-messages-drop-from.sql @@ -0,0 +1,2 @@ +ALTER TABLE `messages` +DROP `from`; \ No newline at end of file diff --git a/packages/server/src/db/snippets/client/get.sql b/packages/server/src/db/snippets/client/get.sql new file mode 100644 index 0000000..5d6e0d5 --- /dev/null +++ b/packages/server/src/db/snippets/client/get.sql @@ -0,0 +1 @@ +SELECT name FROM clients WHERE uid=? \ No newline at end of file diff --git a/packages/server/src/db/snippets/client/new.sql b/packages/server/src/db/snippets/client/new.sql new file mode 100644 index 0000000..08d560e --- /dev/null +++ b/packages/server/src/db/snippets/client/new.sql @@ -0,0 +1 @@ +CALL new_client("Anonymous"); \ No newline at end of file diff --git a/packages/server/src/db/snippets/client/rename.sql b/packages/server/src/db/snippets/client/rename.sql new file mode 100644 index 0000000..3e2c71a --- /dev/null +++ b/packages/server/src/db/snippets/client/rename.sql @@ -0,0 +1,3 @@ +UPDATE clients + SET name=? + WHERE uid=?; \ No newline at end of file diff --git a/packages/server/src/db/snippets/message/new.sql b/packages/server/src/db/snippets/message/new.sql index c9f5767..cd346a3 100644 --- a/packages/server/src/db/snippets/message/new.sql +++ b/packages/server/src/db/snippets/message/new.sql @@ -1,10 +1,3 @@ INSERT INTO messages - (`text`, `from`, `uid`, `t_sent`, channel_uid) - VALUES ( - ?, - ?, - ?, - /* UNIX_TIMESTAMP(), */ - ?, - ? - ) \ No newline at end of file + (`text`, sender_uid, `uid`, `t_sent`, channel_uid) + VALUES ( ?, ?, ?, ?, ? ); \ No newline at end of file diff --git a/packages/server/src/db/snippets/message/recent.sql b/packages/server/src/db/snippets/message/recent.sql index 9944e67..b6d449c 100644 --- a/packages/server/src/db/snippets/message/recent.sql +++ b/packages/server/src/db/snippets/message/recent.sql @@ -1,4 +1,12 @@ -SELECT * FROM messages - WHERE channel_uid=? - ORDER BY t_sent - LIMIT 100; \ No newline at end of file + +SELECT + messages.t_sent, + clients.name as 'from', + messages.`text` as 'text', + messages.channel_uid, + messages.uid as uid +FROM messages +JOIN clients ON messages.sender_uid=clients.uid +WHERE messages.channel_uid=? +ORDER BY -messages.t_sent +LIMIT 100; \ No newline at end of file diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 9a0c234..62b6173 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1,7 +1,10 @@ import router from './lib/router'; -import message from './routers/message'; import { expose } from './lib/WebSocketServer'; +import message from './routers/message'; +import channel from './routers/channel'; +import client from './routers/client'; + const api = router({ up() { console.log(Date.now()); @@ -10,6 +13,8 @@ const api = router({ messages: message, channel: channel, channels: channel, + client: client, + clients: client, }); expose(api, 3000); @@ -17,7 +22,6 @@ expose(api, 3000); // ------------- import { update } from './db/migrate'; -import channel from './routers/channel'; try { update(); diff --git a/packages/server/src/lib/WebSocketServer.ts b/packages/server/src/lib/WebSocketServer.ts index 0aef5d6..f649aeb 100644 --- a/packages/server/src/lib/WebSocketServer.ts +++ b/packages/server/src/lib/WebSocketServer.ts @@ -1,4 +1,5 @@ import { WebSocketServer } from 'ws'; +import { inspect } from 'util'; export function expose(router: Function, port: number) { const wss = new WebSocketServer({ @@ -31,7 +32,10 @@ export function expose(router: Function, port: number) { break; } case ResponseType.REPLY: { - console.log('[OUT]', action, _return.data); + console.log('[OUT]', action, inspect(_return.data, { + depth: 0, + colors: true, + })); send(ws, action, _return.data); break; } diff --git a/packages/server/src/routers/client.ts b/packages/server/src/routers/client.ts new file mode 100644 index 0000000..42234fc --- /dev/null +++ b/packages/server/src/routers/client.ts @@ -0,0 +1,25 @@ +import router from '../lib/router'; +import query from '../db/query'; +import { reply } from '../lib/WebSocketServer'; + +import _new from '../db/snippets/client/new.sql'; +import _get from '../db/snippets/client/get.sql'; +import rename from '../db/snippets/client/rename.sql'; + +export default router({ + async 'new'() { + const response = await query(_new); + if(response === null) return; + return reply(response[0][0].uid); + }, + async 'get'(uid: string) { + const response = await query(_get, uid); + if(response === null) return; + return reply(response[0].name); + }, + async 'rename'(data: any) { + const { clientId, name } = data; + const res = await query(rename, name, clientId); + // silent failure O.O + }, +}); \ No newline at end of file diff --git a/packages/server/src/routers/message.ts b/packages/server/src/routers/message.ts index 371b34b..3258fff 100644 --- a/packages/server/src/routers/message.ts +++ b/packages/server/src/routers/message.ts @@ -2,11 +2,12 @@ import query from '../db/query'; import router from '../lib/router'; import newMessage from '../db/snippets/message/new.sql'; import recentMessages from '../db/snippets/message/recent.sql'; +import getName from '../db/snippets/client/get.sql'; import { broadcast, reply } from '../lib/WebSocketServer'; export default router({ async message(data: any) { - const failed = null === await query( + const response = await query( newMessage, data.text, data.from, @@ -14,11 +15,14 @@ export default router({ data.timestamp, data.channel, ); - if(failed) return; + if(response === null) return; + // translate from to a real name + const nameRes = await query(getName, data.from); + if(nameRes === null) return; + data.from = nameRes[0].name; return broadcast(data); }, async recent(data: any) { - console.log('got recents request ch', data.channel); const messages = await query(recentMessages, data.channel); if(messages === null) return; return reply({