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
*/
export {sha256sum} from './nodeCrypto';
export { getClientId, setClientId } from './clientId';
export {versions} from './versions';

View File

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

View File

@ -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<string | null>(null);
export default function App() {
const [channel, setChannel] = useState<string | null>(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 (
<channelContext.Provider value={channelContextValue}>
<div style={{
display: 'grid',
gridTemplateColumns: '200px 1fr',
gridTemplateRows: '1fr',
height: '100%',
}}>
<clientIdContext.Provider value={clientId}>
<channelContext.Provider value={channelContextValue}>
<div style={{
background: 'rgba(25, 26, 33)',
borderRight: '1px solid #bd93f9',
display: 'grid',
gridTemplateColumns: '200px 1fr',
gridTemplateRows: '1fr',
height: '100%',
}}>
<Channels></Channels>
<div style={{
background: '#21222c',
borderRight: '1px solid #bd93f9',
}}>
<Channels></Channels>
</div>
<div>
<Chat></Chat>
</div>
</div>
<div>
<Chat></Chat>
</div>
</div>
</channelContext.Provider>
</channelContext.Provider>
</clientIdContext.Provider>
);
}

View File

@ -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<IChannel[]>([]);
const {channel, setChannel} = useContext(channelContext);
const [unreads, setUnreads] = useState<IUnreads>({});
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<HTMLInputElement>(null);
const add = useCallback(() => {
if(textbox.current === null) return;
@ -127,6 +132,7 @@ export default function Channels() {
borderRadius: '8px',
// lineHeight: '20px'
}}>ADD</button>
<NameTextbox></NameTextbox>
</>
);
}

View File

@ -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<IMessage[]>([]);
const [hist, setHist] = useState(false);
const textBoxRef = useRef<HTMLDivElement>(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 => (
<Message key={message.uid} message={message}></Message>
))}
{messages.map(message => (
<Message key={message.uid} message={message}></Message>
))}
</div>
</div>
<div onClick={() => {

View File

@ -22,8 +22,7 @@ export function Message({
return (
<>
<div key={message.uid} style={{
<div style={{
display: 'grid',
gridTemplateColumns: '128px 1fr',
width: '100%',
@ -63,6 +62,5 @@ export function Message({
</div>
</span>
</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
(`text`, `from`, `uid`, `t_sent`, channel_uid)
VALUES (
?,
?,
?,
/* UNIX_TIMESTAMP(), */
?,
?
)
(`text`, sender_uid, `uid`, `t_sent`, channel_uid)
VALUES ( ?, ?, ?, ?, ? );

View File

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

View File

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

View File

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

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 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({