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
|
* @module preload
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {sha256sum} from './nodeCrypto';
|
export { getClientId, setClientId } from './clientId';
|
||||||
export {versions} from './versions';
|
export {versions} from './versions';
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
INSERT INTO messages
|
||||||
(`text`, `from`, `uid`, `t_sent`, channel_uid)
|
(`text`, sender_uid, `uid`, `t_sent`, channel_uid)
|
||||||
VALUES (
|
VALUES ( ?, ?, ?, ?, ? );
|
||||||
?,
|
|
||||||
?,
|
|
||||||
?,
|
|
||||||
/* UNIX_TIMESTAMP(), */
|
|
||||||
?,
|
|
||||||
?
|
|
||||||
)
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
SELECT * FROM messages
|
|
||||||
WHERE channel_uid=?
|
SELECT
|
||||||
ORDER BY t_sent
|
messages.t_sent,
|
||||||
LIMIT 100;
|
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 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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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({
|
||||||
|
|
|
||||||
Reference in New Issue