name changing!

cordova
Valerie 2022-07-23 17:37:24 -04:00
parent 2e89be3b84
commit cc1f84cadc
20 changed files with 259 additions and 59 deletions

View File

@ -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;
}

View File

@ -2,5 +2,5 @@
* @module preload * @module preload
*/ */
export {sha256sum} from './nodeCrypto'; export { getClientId, setClientId } from './clientId';
export {versions} from './versions'; export {versions} from './versions';

View File

@ -1,7 +1,7 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { registerRouter, router, send, unregisterRouter } from './api'; 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; const _router = typeof actions === 'object' ? router(actions) : actions;
useEffect(() => { useEffect(() => {
registerRouter(_router); registerRouter(_router);

View File

@ -1,7 +1,8 @@
import { createContext, useState } from 'react'; import { createContext, useEffect, useState } from 'react';
import Channels from './Channels'; import Channels from './Channels';
import Chat from './Chat'; import Chat from './Chat';
import { getClientId, setClientId } from '#preload';
import { useApi } from '../lib/useApi';
export const channelContext = createContext<{ export const channelContext = createContext<{
channel: string | null, channel: string | null,
@ -11,12 +12,32 @@ export const channelContext = createContext<{
setChannel: () => {}, setChannel: () => {},
}); });
export const clientIdContext = createContext<string | null>(null);
export default function App() { export default function App() {
const [channel, setChannel] = useState<string | null>(null); const [channel, setChannel] = useState<string | null>(null);
const [clientId, setCachedClientId] = useState(getClientId());
const channelContextValue = { channel, setChannel }; 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 ( return (
<clientIdContext.Provider value={clientId}>
<channelContext.Provider value={channelContextValue}> <channelContext.Provider value={channelContextValue}>
<div style={{ <div style={{
display: 'grid', display: 'grid',
@ -25,7 +46,7 @@ export default function App() {
height: '100%', height: '100%',
}}> }}>
<div style={{ <div style={{
background: 'rgba(25, 26, 33)', background: '#21222c',
borderRight: '1px solid #bd93f9', borderRight: '1px solid #bd93f9',
}}> }}>
<Channels></Channels> <Channels></Channels>
@ -35,5 +56,6 @@ export default function App() {
</div> </div>
</div> </div>
</channelContext.Provider> </channelContext.Provider>
</clientIdContext.Provider>
); );
} }

View File

@ -1,7 +1,8 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { channelContext } from './App'; import { channelContext, clientIdContext } from './App';
import { useAPI } from '../lib/useRouter'; import { useApi } from '../lib/useApi';
import type { IMessage } from './Message'; import type { IMessage } from './Message';
import NameTextbox from './NameTextbox';
interface IChannel { interface IChannel {
uid: string; uid: string;
@ -23,14 +24,13 @@ interface IUnreads {
export default function Channels() { export default function Channels() {
const [channels, setChannels] = useState<IChannel[]>([]); const [channels, setChannels] = useState<IChannel[]>([]);
const {channel, setChannel} = useContext(channelContext);
const [unreads, setUnreads] = useState<IUnreads>({}); const [unreads, setUnreads] = useState<IUnreads>({});
const {channel, setChannel} = useContext(channelContext);
const clientId = useContext(clientIdContext);
const { send } = useAPI({ const { send } = useApi({
'channels:list'(data: IChannel[]) { 'channels:list'(data: IChannel[]) {
// console.log(data)
setChannels(data); setChannels(data);
}, },
'channel:add'(channel: IChannel) { 'channel:add'(channel: IChannel) {
@ -69,6 +69,11 @@ export default function Channels() {
}); });
}, [channel]); }, [channel]);
useEffect(() => {
if(clientId === null) return;
send('client:get', clientId);
}, [clientId]);
const textbox = useRef<HTMLInputElement>(null); const textbox = useRef<HTMLInputElement>(null);
const add = useCallback(() => { const add = useCallback(() => {
if(textbox.current === null) return; if(textbox.current === null) return;
@ -127,6 +132,7 @@ export default function Channels() {
borderRadius: '8px', borderRadius: '8px',
// lineHeight: '20px' // lineHeight: '20px'
}}>ADD</button> }}>ADD</button>
<NameTextbox></NameTextbox>
</> </>
); );
} }

View File

@ -1,7 +1,7 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useAPI } from '../lib/useRouter'; import { useApi } from '../lib/useApi';
import { channelContext } from './App'; import { channelContext, clientIdContext } from './App';
import type { IMessage} from './Message'; import type { IMessage} from './Message';
import { Message } from './Message'; import { Message } from './Message';
@ -21,16 +21,18 @@ export default () => {
const [hist, setHist] = useState(false); const [hist, setHist] = useState(false);
const textBoxRef = useRef<HTMLDivElement>(null); const textBoxRef = useRef<HTMLDivElement>(null);
const { channel, setChannel } = useContext(channelContext);
const { send } = useAPI({ const { channel, setChannel } = useContext(channelContext);
const clientId = useContext(clientIdContext);
const { send } = useApi({
'message:message'(data: IMessage) { 'message:message'(data: IMessage) {
if(data.channel !== channel) return; if(data.channel !== channel) return;
setMessages([...messages, data]); setMessages([...messages, data]);
}, },
'message:recent'(data: { messages: IMessage[] }) { 'message:recent'(data: { messages: IMessage[] }) {
setMessages(data.messages); setMessages(data.messages.reverse());
}, },
}, [messages]); }, [messages]);
@ -42,10 +44,11 @@ export default () => {
const sendMessage = useCallback(() => { const sendMessage = useCallback(() => {
if(textBoxRef.current === null) return; if(textBoxRef.current === null) return;
if(channel === null) return; if(channel === null) return;
if(clientId === null) return;
send( send(
'message:message', 'message:message',
createMessage( createMessage(
'Val', clientId,
textBoxRef.current.innerText, textBoxRef.current.innerText,
channel, channel,
), ),

View File

@ -22,8 +22,7 @@ export function Message({
return ( return (
<> <div style={{
<div key={message.uid} style={{
display: 'grid', display: 'grid',
gridTemplateColumns: '128px 1fr', gridTemplateColumns: '128px 1fr',
width: '100%', width: '100%',
@ -63,6 +62,5 @@ export function Message({
</div> </div>
</span> </span>
</div> </div>
</>
); );
} }

View File

@ -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<string | null>(null);
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(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 <input
ref={setInputElement}
value={name ?? ''}
onChange={update}
/>;
}

View File

@ -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;

View File

@ -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`);

View File

@ -0,0 +1,2 @@
ALTER TABLE `messages`
DROP `from`;

View File

@ -0,0 +1 @@
SELECT name FROM clients WHERE uid=?

View File

@ -0,0 +1 @@
CALL new_client("Anonymous");

View File

@ -0,0 +1,3 @@
UPDATE clients
SET name=?
WHERE uid=?;

View File

@ -1,10 +1,3 @@
INSERT INTO messages INSERT INTO messages
(`text`, `from`, `uid`, `t_sent`, channel_uid) (`text`, sender_uid, `uid`, `t_sent`, channel_uid)
VALUES ( VALUES ( ?, ?, ?, ?, ? );
?,
?,
?,
/* UNIX_TIMESTAMP(), */
?,
?
)

View File

@ -1,4 +1,12 @@
SELECT * FROM messages
WHERE channel_uid=? SELECT
ORDER BY t_sent 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; LIMIT 100;

View File

@ -1,7 +1,10 @@
import router from './lib/router'; import router from './lib/router';
import message from './routers/message';
import { expose } from './lib/WebSocketServer'; import { expose } from './lib/WebSocketServer';
import message from './routers/message';
import channel from './routers/channel';
import client from './routers/client';
const api = router({ const api = router({
up() { up() {
console.log(Date.now()); console.log(Date.now());
@ -10,6 +13,8 @@ const api = router({
messages: message, messages: message,
channel: channel, channel: channel,
channels: channel, channels: channel,
client: client,
clients: client,
}); });
expose(api, 3000); expose(api, 3000);
@ -17,7 +22,6 @@ expose(api, 3000);
// ------------- // -------------
import { update } from './db/migrate'; import { update } from './db/migrate';
import channel from './routers/channel';
try { try {
update(); update();

View File

@ -1,4 +1,5 @@
import { WebSocketServer } from 'ws'; import { WebSocketServer } from 'ws';
import { inspect } from 'util';
export function expose(router: Function, port: number) { export function expose(router: Function, port: number) {
const wss = new WebSocketServer({ const wss = new WebSocketServer({
@ -31,7 +32,10 @@ export function expose(router: Function, port: number) {
break; break;
} }
case ResponseType.REPLY: { 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); send(ws, action, _return.data);
break; break;
} }

View File

@ -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
},
});

View File

@ -2,11 +2,12 @@ import query from '../db/query';
import router from '../lib/router'; import router from '../lib/router';
import newMessage from '../db/snippets/message/new.sql'; import newMessage from '../db/snippets/message/new.sql';
import recentMessages from '../db/snippets/message/recent.sql'; import recentMessages from '../db/snippets/message/recent.sql';
import getName from '../db/snippets/client/get.sql';
import { broadcast, reply } from '../lib/WebSocketServer'; import { broadcast, reply } from '../lib/WebSocketServer';
export default router({ export default router({
async message(data: any) { async message(data: any) {
const failed = null === await query( const response = await query(
newMessage, newMessage,
data.text, data.text,
data.from, data.from,
@ -14,11 +15,14 @@ export default router({
data.timestamp, data.timestamp,
data.channel, 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); return broadcast(data);
}, },
async recent(data: any) { async recent(data: any) {
console.log('got recents request ch', data.channel);
const messages = await query(recentMessages, data.channel); const messages = await query(recentMessages, data.channel);
if(messages === null) return; if(messages === null) return;
return reply({ return reply({