mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-04-30 16:21:10 +00:00
frontend: improve layout and registration
This commit is contained in:
parent
76a9e501c5
commit
364e517b7d
2 changed files with 150 additions and 69 deletions
|
@ -2,86 +2,147 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>poc</title>
|
||||
<title>Blah testing frontend</title>
|
||||
<script src="./main.js" defer></script>
|
||||
<style>
|
||||
@media screen and (max-width: 50em) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
}
|
||||
#sidebar {
|
||||
width: auto !important;
|
||||
border-bottom: 1px solid grey;
|
||||
border-right: none !important;
|
||||
flex: .5;
|
||||
}
|
||||
}
|
||||
|
||||
input, select {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
#msg-flow {
|
||||
|
||||
input {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
padding: .5em;
|
||||
width: 25em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid grey;
|
||||
& label {
|
||||
margin-left: 0;
|
||||
}
|
||||
& > #rooms {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.label-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
& > label {
|
||||
margin: auto .5em;
|
||||
}
|
||||
& > input, & > select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#mainbar {
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > .flow {
|
||||
padding: .5em;
|
||||
overflow: scroll;
|
||||
border-bottom: 1px solid grey;
|
||||
& > * {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
#msg-flow > * {
|
||||
display: block;
|
||||
#log-flow {
|
||||
flex: 1;
|
||||
}
|
||||
#msg-flow {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.log {
|
||||
margin: auto;
|
||||
font-style: italic;
|
||||
&::before {
|
||||
content: "«";
|
||||
content: "«";
|
||||
}
|
||||
&::after {
|
||||
content: "»";
|
||||
}
|
||||
}
|
||||
|
||||
#input-area > * {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
& > label {
|
||||
margin: auto;
|
||||
}
|
||||
& > input,
|
||||
& > select {
|
||||
flex: 1;
|
||||
content: "»";
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="msg-flow">
|
||||
<span class="log">please enter room url below and press ENTER</span>
|
||||
</div>
|
||||
<div id="input-area">
|
||||
<div>
|
||||
<label for="id-pubkey">id_pubkey:</label>
|
||||
<div id="sidebar">
|
||||
<div class="label-input">
|
||||
<label for="id-url">id_url:</label>
|
||||
<input type="url" id="id-url" placeholder="https://example.com" />
|
||||
<button id="register">register</button>
|
||||
</div>
|
||||
<div class="label-input">
|
||||
<label for="id-pubkey">id_pubkey :</label>
|
||||
<input type="text" id="id-pubkey" placeholder="-" />
|
||||
</div>
|
||||
<div class="label-input">
|
||||
<label for="act-pubkey">act_pubkey:</label>
|
||||
<input type="text" id="act-pubkey" placeholder="-" readonly />
|
||||
<input type="text" id="act-pubkey" placeholder="-" readonly disabled />
|
||||
<button id="regen-key">regenerate</button>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div class="label-input">
|
||||
<label for="server-url">server url:</label>
|
||||
<input
|
||||
type="url"
|
||||
id="server-url"
|
||||
placeholder="https://example.com"
|
||||
pattern="https://.*"
|
||||
pattern="https:\/\/[^\/]*\/?"
|
||||
required
|
||||
/>
|
||||
<button id="register">register</button>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rooms">joined rooms:</label>
|
||||
<select id="rooms"></select>
|
||||
|
||||
<label for="join-new-room">join public room:</label>
|
||||
<div>
|
||||
<label for="rooms">rooms:</label>
|
||||
<button id="refresh-rooms">refresh room list</button>
|
||||
</div>
|
||||
<select id="rooms" size="2"></select>
|
||||
<div class="label-input">
|
||||
<label for="join-new-room">join new room:</label>
|
||||
<select id="join-new-room"></select>
|
||||
|
||||
<button id="leave-room">leave room</select>
|
||||
<button id="refresh-rooms">refresh room list</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="chat">chat:</label>
|
||||
<input type="text" id="chat" placeholder="message" />
|
||||
</div>
|
||||
|
||||
<div id="mainbar">
|
||||
<div id="log-flow" class="flow">
|
||||
<noscript>
|
||||
<span class="log">javascript is required</span>
|
||||
</noscript>
|
||||
</div>
|
||||
<div id="msg-flow" class="flow">
|
||||
</div>
|
||||
<div class="label-input">
|
||||
<label for="chat">chat:</label>
|
||||
<input type="text" id="chat" placeholder="message or raw JSON" />
|
||||
<button id="mark-seen">mark history seen</button>
|
||||
<button id="leave-room">leave room</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const idUrlInput = document.querySelector('#id-url');
|
||||
const logFlow = document.querySelector('#log-flow');
|
||||
const msgFlow = document.querySelector('#msg-flow');
|
||||
const idPubkeyInput = document.querySelector('#id-pubkey');
|
||||
const actPubkeyDisplay = document.querySelector('#act-pubkey');
|
||||
|
@ -36,10 +38,10 @@ async function getActPubkey() {
|
|||
return bufToHex(await crypto.subtle.exportKey('raw', keypair.publicKey));
|
||||
}
|
||||
|
||||
function appendMsg(el) {
|
||||
msgFlow.append(el);
|
||||
msgFlow.scrollTo({
|
||||
top: msgFlow.scrollTopMax,
|
||||
function appendMsg(parent, el) {
|
||||
parent.append(el);
|
||||
parent.scrollTo({
|
||||
top: parent.scrollTopMax,
|
||||
behavior: 'instant',
|
||||
})
|
||||
}
|
||||
|
@ -52,7 +54,7 @@ function log(msg, isHtml) {
|
|||
} else {
|
||||
el.innerText = msg;
|
||||
}
|
||||
appendMsg(el)
|
||||
appendMsg(logFlow, el)
|
||||
}
|
||||
|
||||
async function loadKeypair() {
|
||||
|
@ -114,8 +116,18 @@ async function register() {
|
|||
}
|
||||
|
||||
try {
|
||||
const idUrl = prompt('id_url:', defaultConfig.id_url || '');
|
||||
if (idUrl === null) return;
|
||||
const idUrl = idUrlInput.value.trim();
|
||||
if (idUrl === '') return;
|
||||
|
||||
const wellKnownUrl = (new URL(idUrl)) + '.well-known/blah/identity.json';
|
||||
log(`fetching ${wellKnownUrl}`);
|
||||
const idDescResp = await fetch(wellKnownUrl, { redirect: 'error' });
|
||||
if (idDescResp.status !== 200) throw new Error(`status ${idDescResp.status}`);
|
||||
const idDescJson = await idDescResp.json()
|
||||
if (typeof idDescJson.id_key !== 'string' || idDescJson.id_key.length !== 64) {
|
||||
throw new Error('invalid id_key from identity description response');
|
||||
}
|
||||
idPubkeyInput.value = idDescJson.id_key;
|
||||
|
||||
const getResp = await fetch(`${apiUrl}/user/me`, {
|
||||
cache: 'no-store'
|
||||
|
@ -124,6 +136,7 @@ async function register() {
|
|||
const difficulty = parseInt(getResp.headers.get('x-blah-difficulty'));
|
||||
if (challenge === null) throw new Error('cannot get challenge nonce');
|
||||
|
||||
log('solving challenge')
|
||||
const postResp = await signAndPost(`${apiUrl}/user/me`, {
|
||||
// sorted fields.
|
||||
challenge_nonce: challenge,
|
||||
|
@ -170,7 +183,7 @@ async function showChatMsg(chat) {
|
|||
elContent.innerHTML = richTextToHtml(chat.signee.payload.rich_text);
|
||||
el.appendChild(elHeader);
|
||||
el.appendChild(elContent);
|
||||
appendMsg(el)
|
||||
appendMsg(msgFlow, el)
|
||||
}
|
||||
|
||||
function richTextToHtml(richText) {
|
||||
|
@ -218,33 +231,36 @@ async function enterRoom(rid) {
|
|||
curRoom = rid;
|
||||
roomsInput.value = rid;
|
||||
|
||||
genAuthHeader()
|
||||
.then(opts => fetch(`${apiUrl}/room/${rid}`, opts))
|
||||
.then(async (resp) => [resp.status, await resp.json()])
|
||||
.then(async ([status, json]) => {
|
||||
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
||||
document.title = `room: ${json.title}`
|
||||
})
|
||||
.catch((e) => {
|
||||
log(`failed to get room metadata: ${e}`);
|
||||
});
|
||||
msgFlow.replaceChildren();
|
||||
|
||||
genAuthHeader()
|
||||
.then(opts => fetch(`${apiUrl}/room/${rid}/msg`, opts))
|
||||
.then(async (resp) => { return [resp.status, await resp.json()]; })
|
||||
.then(async ([status, json]) => {
|
||||
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
||||
let roomMetadata;
|
||||
try {
|
||||
const resp = await fetch(`${apiUrl}/room/${rid}`, await genAuthHeader());
|
||||
roomMetadata = await resp.json();
|
||||
if (resp.status !== 200) throw new Error(`status ${resp.status}: ${roomMetadata.error.message}`);
|
||||
document.title = `Blah: ${roomMetadata.title}`;
|
||||
} catch (err) {
|
||||
log(`failed to get room metadata: ${err}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(`${apiUrl}/room/${rid}/msg`, await genAuthHeader());
|
||||
const json = await resp.json();
|
||||
if (resp.status !== 200) throw new Error(`status ${resp.status}: ${json.error.message}`);
|
||||
const { msgs } = json
|
||||
msgs.reverse();
|
||||
for (const msg of msgs) {
|
||||
lastCid = msg.cid;
|
||||
await showChatMsg(msg);
|
||||
if (msg.cid === roomMetadata.last_seen_cid) {
|
||||
const el = document.createElement('span');
|
||||
el.innerText = '---last seen---';
|
||||
appendMsg(msgFlow, el)
|
||||
}
|
||||
}
|
||||
log('---history---');
|
||||
})
|
||||
.catch((e) => {
|
||||
log(`failed to fetch history: ${e}`);
|
||||
});
|
||||
} catch (err) {
|
||||
log(`failed to fetch history: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function connectServer(newServerUrl) {
|
||||
|
@ -366,7 +382,7 @@ async function leaveRoom() {
|
|||
await signAndPost(`${apiUrl}/room/${curRoom}/admin`, {
|
||||
room: curRoom,
|
||||
typ: 'remove_member',
|
||||
user: await getActPubkey(),
|
||||
user: await getIdPubkey(),
|
||||
});
|
||||
log('left room');
|
||||
await loadRoomList(true);
|
||||
|
@ -486,6 +502,9 @@ window.onload = async (_) => {
|
|||
if (keypair !== null) {
|
||||
actPubkeyDisplay.value = await getActPubkey();
|
||||
}
|
||||
if (idUrlInput.value === '' && defaultConfig.id_url) {
|
||||
idUrlInput.value = defaultConfig.id_url;
|
||||
}
|
||||
if (idPubkeyInput.value === '' && defaultConfig.id_key) {
|
||||
idPubkeyInput.value = defaultConfig.id_key;
|
||||
}
|
||||
|
@ -520,6 +539,7 @@ serverUrlInput.onchange = async (e) => {
|
|||
chatInput.onkeypress = async (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
await postChat(chatInput.value);
|
||||
chatInput.focus();
|
||||
}
|
||||
};
|
||||
roomsInput.onchange = async (_) => {
|
||||
|
|
Loading…
Add table
Reference in a new issue