mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 08:41:09 +00:00
Impl room listing for testing frontend
This commit is contained in:
parent
6831c3d25a
commit
57b17547ca
2 changed files with 122 additions and 68 deletions
|
@ -22,24 +22,25 @@
|
||||||
.log {
|
.log {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
&::before {
|
||||||
.log::before {
|
|
||||||
content: "«";
|
content: "«";
|
||||||
}
|
}
|
||||||
.log::after {
|
&::after {
|
||||||
content: "»";
|
content: "»";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#input-area > * {
|
#input-area > * {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
& > label {
|
||||||
#input-area > * > label {
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
#input-area > * > input {
|
& > input,
|
||||||
|
& > select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -53,17 +54,24 @@
|
||||||
<input type="text" id="user-pubkey" placeholder="-" readonly />
|
<input type="text" id="user-pubkey" placeholder="-" readonly />
|
||||||
<button id="regen-key">regenerate</button>
|
<button id="regen-key">regenerate</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="room-url">room url:</label>
|
<label for="server-url">server url:</label>
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
id="room-url"
|
id="server-url"
|
||||||
placeholder="https://example.com"
|
placeholder="https://example.com"
|
||||||
pattern="https://.*"
|
pattern="https://.*"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<button id="join-room">join room</button>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="rooms">joined rooms:</label>
|
||||||
|
<select id="rooms"></select>
|
||||||
|
|
||||||
|
<label for="join-new-room">join public room:</label>
|
||||||
|
<select id="join-new-room"></select>
|
||||||
|
|
||||||
|
<button id="refresh-rooms">refresh room list</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="chat">chat:</label>
|
<label for="chat">chat:</label>
|
||||||
|
|
148
pages/main.js
148
pages/main.js
|
@ -1,12 +1,13 @@
|
||||||
const msgFlow = document.querySelector('#msg-flow');
|
const msgFlow = document.querySelector('#msg-flow');
|
||||||
const userPubkeyDisplay = document.querySelector('#user-pubkey');
|
const userPubkeyDisplay = document.querySelector('#user-pubkey');
|
||||||
const roomUrlInput = document.querySelector('#room-url');
|
const serverUrlInput = document.querySelector('#server-url');
|
||||||
|
const roomsInput = document.querySelector('#rooms');
|
||||||
|
const joinNewRoomInput = document.querySelector('#join-new-room');
|
||||||
const chatInput = document.querySelector('#chat');
|
const chatInput = document.querySelector('#chat');
|
||||||
const regenKeyBtn = document.querySelector('#regen-key');
|
const regenKeyBtn = document.querySelector('#regen-key');
|
||||||
const joinRoomBtn = document.querySelector('#join-room');
|
|
||||||
|
|
||||||
let roomUrl = '';
|
let serverUrl = null;
|
||||||
let roomUuid = null;
|
let curRoom = null;
|
||||||
let ws = null;
|
let ws = null;
|
||||||
let keypair = null;
|
let keypair = null;
|
||||||
let defaultConfig = {};
|
let defaultConfig = {};
|
||||||
|
@ -65,9 +66,7 @@ async function loadKeypair() {
|
||||||
|
|
||||||
async function generateKeypair() {
|
async function generateKeypair() {
|
||||||
log('generating keypair');
|
log('generating keypair');
|
||||||
regenKeyBtn.disabled = true;
|
document.querySelectorAll('input, button, select').forEach((el) => el.disabled = true);
|
||||||
chatInput.disabled = true;
|
|
||||||
joinRoomBtn.disabled = true;
|
|
||||||
try {
|
try {
|
||||||
keypair = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']);
|
keypair = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -84,10 +83,7 @@ async function generateKeypair() {
|
||||||
}
|
}
|
||||||
|
|
||||||
log('keypair generated');
|
log('keypair generated');
|
||||||
|
document.querySelectorAll('input, button, select').forEach((el) => el.disabled = false);
|
||||||
regenKeyBtn.disabled = false;
|
|
||||||
chatInput.disabled = false;
|
|
||||||
joinRoomBtn.disabled = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const serialize = (k) => crypto.subtle.exportKey('jwk', k);
|
const serialize = (k) => crypto.subtle.exportKey('jwk', k);
|
||||||
|
@ -165,23 +161,21 @@ function escapeHtml(text) {
|
||||||
.replaceAll("'", ''');
|
.replaceAll("'", ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectRoom(url) {
|
async function genAuthHeader() {
|
||||||
if (url === '' || url == roomUrl || keypair === null) return;
|
return {
|
||||||
const match = url.match(/^https?:\/\/.*\/([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\/?/);
|
headers: {
|
||||||
if (match === null) {
|
'Authorization': await signData({ typ: 'auth' }),
|
||||||
log('invalid room url');
|
},
|
||||||
return;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
roomUrl = url;
|
async function enterRoom(ruuid) {
|
||||||
roomUuid = match[1];
|
log(`loading room: ${ruuid}`);
|
||||||
|
curRoom = ruuid;
|
||||||
|
|
||||||
log(`fetching room: ${url}`);
|
genAuthHeader()
|
||||||
|
.then(opts => fetch(`${serverUrl}/room/${ruuid}`, opts))
|
||||||
const genFetchOpts = async () => ({ headers: { 'Authorization': await signData({ typ: 'auth' }) } });
|
.then(async (resp) => [resp.status, await resp.json()])
|
||||||
genFetchOpts()
|
|
||||||
.then(opts => fetch(url, opts))
|
|
||||||
.then(async (resp) => { return [resp.status, await resp.json()]; })
|
|
||||||
.then(async ([status, json]) => {
|
.then(async ([status, json]) => {
|
||||||
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
||||||
document.title = `room: ${json.title}`
|
document.title = `room: ${json.title}`
|
||||||
|
@ -190,8 +184,8 @@ async function connectRoom(url) {
|
||||||
log(`failed to get room metadata: ${e}`);
|
log(`failed to get room metadata: ${e}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
genFetchOpts()
|
genAuthHeader()
|
||||||
.then(opts => fetch(`${url}/item`, opts))
|
.then(opts => fetch(`${serverUrl}/room/${ruuid}/item`, opts))
|
||||||
.then(async (resp) => { return [resp.status, await resp.json()]; })
|
.then(async (resp) => { return [resp.status, await resp.json()]; })
|
||||||
.then(async ([status, json]) => {
|
.then(async ([status, json]) => {
|
||||||
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
||||||
|
@ -205,24 +199,31 @@ async function connectRoom(url) {
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
log(`failed to fetch history: ${e}`);
|
log(`failed to fetch history: ${e}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: There is a time window where events would be lost.
|
|
||||||
|
|
||||||
await connectWs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectWs() {
|
async function connectServer(newServerUrl) {
|
||||||
|
if (newServerUrl === '' || keypair === null) return;
|
||||||
|
let wsUrl
|
||||||
|
try {
|
||||||
|
wsUrl = new URL(newServerUrl);
|
||||||
|
} catch (e) {
|
||||||
|
log(`invalid url: ${e}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serverUrl = newServerUrl;
|
||||||
|
|
||||||
if (ws !== null) {
|
if (ws !== null) {
|
||||||
ws.close();
|
ws.close();
|
||||||
}
|
}
|
||||||
const wsUrl = new URL(roomUrl);
|
|
||||||
|
log('connecting server');
|
||||||
wsUrl.protocol = wsUrl.protocol == 'http:' ? 'ws:' : 'wss:';
|
wsUrl.protocol = wsUrl.protocol == 'http:' ? 'ws:' : 'wss:';
|
||||||
wsUrl.pathname = '/ws';
|
wsUrl.pathname = '/ws';
|
||||||
ws = new WebSocket(wsUrl);
|
ws = new WebSocket(wsUrl);
|
||||||
ws.onopen = async (_) => {
|
ws.onopen = async (_) => {
|
||||||
const auth = await signData({ typ: 'auth' });
|
const auth = await signData({ typ: 'auth' });
|
||||||
await ws.send(auth);
|
await ws.send(auth);
|
||||||
log('listening on events');
|
log(`listening events on server: ${serverUrl}`);
|
||||||
}
|
}
|
||||||
ws.onclose = (e) => {
|
ws.onclose = (e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -236,32 +237,69 @@ async function connectWs() {
|
||||||
console.log('ws event', e.data);
|
console.log('ws event', e.data);
|
||||||
const msg = JSON.parse(e.data);
|
const msg = JSON.parse(e.data);
|
||||||
if (msg.chat !== undefined) {
|
if (msg.chat !== undefined) {
|
||||||
showChatMsg(msg.chat);
|
if (msg.chat.signee.payload.room === curRoom) {
|
||||||
|
await showChatMsg(msg.chat);
|
||||||
|
} else {
|
||||||
|
console.log('ignore background room item');
|
||||||
|
}
|
||||||
} else if (msg.lagged !== undefined) {
|
} else if (msg.lagged !== undefined) {
|
||||||
log('some events are dropped because of queue overflow')
|
log('some events are dropped because of queue overflow')
|
||||||
} else {
|
} else {
|
||||||
log(`unknown ws message: ${e.data}`);
|
log(`unknown ws message: ${e.data}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loadRoomList(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function joinRoom() {
|
async function loadRoomList(autoJoin) {
|
||||||
|
log('loading room list');
|
||||||
|
|
||||||
|
async function loadInto(targetEl, filter) {
|
||||||
try {
|
try {
|
||||||
joinRoomBtn.disabled = true;
|
targetEl.replaceChildren();
|
||||||
await signAndPost(`${roomUrl}/admin`, {
|
const resp = await fetch(`${serverUrl}/room?filter=${filter}`, await genAuthHeader())
|
||||||
|
const json = await resp.json()
|
||||||
|
if (resp.status !== 200) throw new Error(`status ${resp.status}: ${json.error.message}`);
|
||||||
|
for (const { ruuid, title, attrs } of json.rooms) {
|
||||||
|
const el = document.createElement('option');
|
||||||
|
el.value = ruuid;
|
||||||
|
el.innerText = `${title} (uuid=${ruuid}, attrs=${attrs})`;
|
||||||
|
targetEl.appendChild(el);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(`failed to load room list: ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadInto(roomsInput, 'joined')
|
||||||
|
.then(async (_) => {
|
||||||
|
if (autoJoin && roomsInput.value !== '') {
|
||||||
|
await enterRoom(roomsInput.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loadInto(joinNewRoomInput, 'public')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function joinRoom(ruuid) {
|
||||||
|
try {
|
||||||
|
joinNewRoomInput.disabled = true;
|
||||||
|
await signAndPost(`${serverUrl}/room/${ruuid}/admin`, {
|
||||||
// sorted fields.
|
// sorted fields.
|
||||||
permission: 1, // POST_CHAT
|
permission: 1, // POST_CHAT
|
||||||
room: roomUuid,
|
room: ruuid,
|
||||||
typ: 'add_member',
|
typ: 'add_member',
|
||||||
user: await getUserPubkey(),
|
user: await getUserPubkey(),
|
||||||
});
|
});
|
||||||
log('joined room');
|
log('joined room');
|
||||||
await connectWs();
|
await loadRoomList(false)
|
||||||
|
await enterRoom(ruuid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
log(`failed to join room: ${e}`);
|
log(`failed to join room: ${e}`);
|
||||||
} finally {
|
} finally {
|
||||||
joinRoomBtn.disabled = false;
|
joinNewRoomInput.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +340,7 @@ async function signData(payload) {
|
||||||
|
|
||||||
async function postChat(text) {
|
async function postChat(text) {
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
if (keypair === null || roomUuid === null || text === '') return;
|
if (keypair === null || curRoom === null || text === '') return;
|
||||||
|
|
||||||
chatInput.disabled = true;
|
chatInput.disabled = true;
|
||||||
|
|
||||||
|
@ -313,10 +351,10 @@ async function postChat(text) {
|
||||||
} else {
|
} else {
|
||||||
richText = [text];
|
richText = [text];
|
||||||
}
|
}
|
||||||
await signAndPost(`${roomUrl}/item`, {
|
await signAndPost(`${serverUrl}/room/${curRoom}/item`, {
|
||||||
// sorted fields.
|
// sorted fields.
|
||||||
rich_text: richText,
|
rich_text: richText,
|
||||||
room: roomUuid,
|
room: curRoom,
|
||||||
typ: 'chat',
|
typ: 'chat',
|
||||||
});
|
});
|
||||||
chatInput.value = '';
|
chatInput.value = '';
|
||||||
|
@ -342,13 +380,15 @@ window.onload = async (_) => {
|
||||||
if (keypair !== null) {
|
if (keypair !== null) {
|
||||||
userPubkeyDisplay.value = await getUserPubkey();
|
userPubkeyDisplay.value = await getUserPubkey();
|
||||||
}
|
}
|
||||||
if (roomUrlInput.value === '' && defaultConfig.room_url) {
|
if (serverUrlInput.value === '' && defaultConfig.server_url) {
|
||||||
roomUrlInput.value = defaultConfig.room_url;
|
serverUrlInput.value = defaultConfig.server_url;
|
||||||
|
}
|
||||||
|
if (serverUrlInput.value !== '') {
|
||||||
|
await connectServer(serverUrlInput.value);
|
||||||
}
|
}
|
||||||
await connectRoom(roomUrlInput.value);
|
|
||||||
};
|
};
|
||||||
roomUrlInput.onchange = async (e) => {
|
serverUrlInput.onchange = async (e) => {
|
||||||
await connectRoom(e.target.value);
|
await connectServer(e.target.value);
|
||||||
};
|
};
|
||||||
chatInput.onkeypress = async (e) => {
|
chatInput.onkeypress = async (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
|
@ -358,6 +398,12 @@ chatInput.onkeypress = async (e) => {
|
||||||
regenKeyBtn.onclick = async (_) => {
|
regenKeyBtn.onclick = async (_) => {
|
||||||
await generateKeypair();
|
await generateKeypair();
|
||||||
};
|
};
|
||||||
joinRoomBtn.onclick = async (_) => {
|
roomsInput.onchange = async (_) => {
|
||||||
await joinRoom();
|
await enterRoom(roomsInput.value);
|
||||||
|
};
|
||||||
|
joinNewRoomInput.onchange = async (_) => {
|
||||||
|
await joinRoom(joinNewRoomInput.value);
|
||||||
|
};
|
||||||
|
document.querySelector('#refresh-rooms').onclick = async (_) => {
|
||||||
|
await loadRoomList(true);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue