name changing!
parent
2e89be3b84
commit
cc1f84cadc
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -2,5 +2,5 @@
|
|||
* @module preload
|
||||
*/
|
||||
|
||||
export {sha256sum} from './nodeCrypto';
|
||||
export { getClientId, setClientId } from './clientId';
|
||||
export {versions} from './versions';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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={() => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
/>;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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`);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `messages`
|
||||
DROP `from`;
|
||||
|
|
@ -0,0 +1 @@
|
|||
SELECT name FROM clients WHERE uid=?
|
||||
|
|
@ -0,0 +1 @@
|
|||
CALL new_client("Anonymous");
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
UPDATE clients
|
||||
SET name=?
|
||||
WHERE uid=?;
|
||||
|
|
@ -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 ( ?, ?, ?, ?, ? );
|
||||
|
|
@ -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;
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
});
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
Reference in New Issue