forked from mystiq/hydrogen-web
put everything together to make it roughly work
no local echo yet, and send errors are being swallowed
This commit is contained in:
parent
851100b88a
commit
3ed72df620
15 changed files with 91 additions and 11 deletions
|
@ -1,3 +1,11 @@
|
|||
# Remaining stuffs
|
||||
- don't swallow send errors, they should probably appear in the room error?
|
||||
- not sure it makes sense to show them where the composer is,
|
||||
because they might get sent a long time after you enter them in brawl,
|
||||
so you don't neccessarily have the context of the composer anymore
|
||||
- local echo
|
||||
|
||||
|
||||
takes care of rate limiting,
|
||||
and sending events from different rooms in parallel,
|
||||
NO: txnIds are created inside room. ~~making txnIds? ... it's rooms though that will receive the event in their sync response~~
|
||||
|
|
|
@ -63,4 +63,8 @@ export default class RoomViewModel extends EventEmitter {
|
|||
get avatarInitials() {
|
||||
return avatarInitials(this._room.name);
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
this._room.sendEvent("m.room.message", {msgtype: "m.text", body: message});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import Sync from "./matrix/sync.js";
|
|||
import SessionView from "./ui/web/session/SessionView.js";
|
||||
import SessionViewModel from "./domain/session/SessionViewModel.js";
|
||||
|
||||
const HOST = "192.168.2.108";
|
||||
const HOST = "127.0.0.1";
|
||||
const HOMESERVER = `http://${HOST}:8008`;
|
||||
const USERNAME = "bruno1";
|
||||
const USER_ID = `@${USERNAME}:localhost`;
|
||||
|
@ -76,6 +76,7 @@ export default async function main(container) {
|
|||
if (needsInitialSync) {
|
||||
showSession(container, session, sync);
|
||||
}
|
||||
// this will start sending unsent messages
|
||||
session.notifyNetworkAvailable();
|
||||
} catch(err) {
|
||||
console.error(`${err.message}:\n${err.stack}`);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Platform from "../../../Platform.js";
|
||||
import Platform from "../Platform.js";
|
||||
import {HomeServerError, NetworkError} from "./error.js";
|
||||
|
||||
export class RateLimitingBackoff {
|
||||
|
@ -79,7 +79,7 @@ export class SendScheduler {
|
|||
|
||||
async _sendLoop() {
|
||||
while (this._sendRequests.length) {
|
||||
const request = this._sendRequests.unshift();
|
||||
const request = this._sendRequests.shift();
|
||||
let result;
|
||||
try {
|
||||
// this can throw!
|
||||
|
@ -94,6 +94,7 @@ export class SendScheduler {
|
|||
}
|
||||
this._sendRequests = [];
|
||||
}
|
||||
console.error("error for request", request);
|
||||
request.reject(err);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ export default class SendQueue {
|
|||
this._storage = storage;
|
||||
this._sendScheduler = sendScheduler;
|
||||
this._pendingEvents = new SortedArray((a, b) => a.queueIndex - b.queueIndex);
|
||||
if (pendingEvents.length) {
|
||||
console.info(`SendQueue for room ${roomId} has ${pendingEvents.length} pending events`, pendingEvents);
|
||||
}
|
||||
this._pendingEvents.setManySorted(pendingEvents.map(data => new PendingEvent(data)));
|
||||
this._isSending = false;
|
||||
this._offline = false;
|
||||
|
@ -24,13 +27,17 @@ export default class SendQueue {
|
|||
async _sendLoop() {
|
||||
this._isSending = true;
|
||||
try {
|
||||
console.log("start sending", this._amountSent, "<", this._pendingEvents.length);
|
||||
while (this._amountSent < this._pendingEvents.length) {
|
||||
const pendingEvent = this._pendingEvents.get(this._amountSent);
|
||||
console.log("trying to send", pendingEvent.content.body);
|
||||
this._amountSent += 1;
|
||||
if (pendingEvent.remoteId) {
|
||||
continue;
|
||||
}
|
||||
console.log("really sending now");
|
||||
const response = await this._sendScheduler.request(hsApi => {
|
||||
console.log("got sendScheduler slot");
|
||||
return hsApi.send(
|
||||
pendingEvent.roomId,
|
||||
pendingEvent.eventType,
|
||||
|
@ -39,7 +46,10 @@ export default class SendQueue {
|
|||
);
|
||||
});
|
||||
pendingEvent.remoteId = response.event_id;
|
||||
//
|
||||
console.log("writing remoteId now");
|
||||
await this._tryUpdateEvent(pendingEvent);
|
||||
console.log("keep sending?", this._amountSent, "<", this._pendingEvents.length);
|
||||
}
|
||||
} catch(err) {
|
||||
if (err instanceof NetworkError) {
|
||||
|
@ -97,16 +107,22 @@ export default class SendQueue {
|
|||
|
||||
async _tryUpdateEvent(pendingEvent) {
|
||||
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
|
||||
console.log("_tryUpdateEvent: got txn");
|
||||
try {
|
||||
// pendingEvent might have been removed already here
|
||||
// by a racing remote echo, so check first so we don't recreate it
|
||||
console.log("_tryUpdateEvent: before exists");
|
||||
if (await txn.pendingEvents.exists(pendingEvent.roomId, pendingEvent.queueIndex)) {
|
||||
console.log("_tryUpdateEvent: inside if exists");
|
||||
txn.pendingEvents.update(pendingEvent.data);
|
||||
}
|
||||
console.log("_tryUpdateEvent: after exists");
|
||||
} catch (err) {
|
||||
txn.abort();
|
||||
console.log("_tryUpdateEvent: error", err);
|
||||
throw err;
|
||||
}
|
||||
console.log("_tryUpdateEvent: try complete");
|
||||
await txn.complete();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//entries can be sorted, first by fragment, then by entry index.
|
||||
import EventKey from "../EventKey.js";
|
||||
import { PENDING_FRAGMENT_ID } from "./PendingEventEntry.js";
|
||||
export const PENDING_FRAGMENT_ID = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
export default class BaseEntry {
|
||||
constructor(fragmentIdComparer) {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import BaseEntry from "./BaseEntry.js";
|
||||
|
||||
export const PENDING_FRAGMENT_ID = Number.MAX_SAFE_INTEGER;
|
||||
import BaseEntry, {PENDING_FRAGMENT_ID} from "./BaseEntry.js";
|
||||
|
||||
export default class PendingEventEntry extends BaseEntry {
|
||||
constructor(pendingEvent) {
|
||||
|
|
|
@ -33,13 +33,13 @@ export default class Session {
|
|||
// load rooms
|
||||
const rooms = await txn.roomSummary.getAll();
|
||||
await Promise.all(rooms.map(summary => {
|
||||
const room = this.createRoom(summary.roomId, pendingEventsByRoomId[summary.roomId]);
|
||||
const room = this.createRoom(summary.roomId, pendingEventsByRoomId.get(summary.roomId));
|
||||
return room.load(summary, txn);
|
||||
}));
|
||||
}
|
||||
|
||||
notifyNetworkAvailable() {
|
||||
for (const room of this._rooms) {
|
||||
for (const [, room] of this._rooms) {
|
||||
room.resumeSending();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ export default class QueryTarget {
|
|||
return reqAsPromise(this._target.get(key));
|
||||
}
|
||||
|
||||
getKey(key) {
|
||||
return reqAsPromise(this._target.getKey(key));
|
||||
}
|
||||
|
||||
reduce(range, reducer, initialValue) {
|
||||
return this._reduce(range, reducer, initialValue, "next");
|
||||
}
|
||||
|
|
|
@ -46,6 +46,14 @@ class QueryTargetWrapper {
|
|||
throw new StorageError("get failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
getKey(...params) {
|
||||
try {
|
||||
return this._qt.getKey(...params);
|
||||
} catch(err) {
|
||||
throw new StorageError("getKey failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
delete(...params) {
|
||||
try {
|
||||
|
|
|
@ -6,6 +6,7 @@ import RoomSummaryStore from "./stores/RoomSummaryStore.js";
|
|||
import TimelineEventStore from "./stores/TimelineEventStore.js";
|
||||
import RoomStateStore from "./stores/RoomStateStore.js";
|
||||
import TimelineFragmentStore from "./stores/TimelineFragmentStore.js";
|
||||
import PendingEventStore from "./stores/PendingEventStore.js";
|
||||
|
||||
export default class Transaction {
|
||||
constructor(txn, allowedStoreNames) {
|
||||
|
@ -55,6 +56,10 @@ export default class Transaction {
|
|||
return this._store("roomState", idbStore => new RoomStateStore(idbStore));
|
||||
}
|
||||
|
||||
get pendingEvents() {
|
||||
return this._store("pendingEvents", idbStore => new PendingEventStore(idbStore));
|
||||
}
|
||||
|
||||
complete() {
|
||||
return txnAsPromise(this._txn);
|
||||
}
|
||||
|
|
|
@ -69,3 +69,11 @@
|
|||
.RoomView_error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.MessageComposer > input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.8em;
|
||||
border: none;
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ export function text(str) {
|
|||
export const TAG_NAMES = [
|
||||
"ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6",
|
||||
"p", "strong", "em", "span", "img", "section", "main", "article", "aside",
|
||||
"pre", "button", "time"];
|
||||
"pre", "button", "time", "input", "textarea"];
|
||||
|
||||
export const tag = {};
|
||||
|
||||
|
|
23
src/ui/web/session/room/MessageComposer.js
Normal file
23
src/ui/web/session/room/MessageComposer.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import TemplateView from "../../general/TemplateView.js";
|
||||
|
||||
export default class MessageComposer extends TemplateView {
|
||||
constructor(viewModel) {
|
||||
super(viewModel);
|
||||
this._input = null;
|
||||
}
|
||||
|
||||
render(t) {
|
||||
this._input = t.input({
|
||||
placeholder: "Send a message ...",
|
||||
onKeydown: e => this._onKeyDown(e)
|
||||
});
|
||||
return t.div({className: "MessageComposer"}, [this._input]);
|
||||
}
|
||||
|
||||
_onKeyDown(event) {
|
||||
if (event.key === "Enter") {
|
||||
this.viewModel.sendMessage(this._input.value);
|
||||
this._input.value = "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import TemplateView from "../../general/TemplateView.js";
|
||||
import TimelineList from "./TimelineList.js";
|
||||
import MessageComposer from "./MessageComposer.js";
|
||||
|
||||
export default class RoomView extends TemplateView {
|
||||
constructor(viewModel) {
|
||||
|
@ -18,17 +19,20 @@ export default class RoomView extends TemplateView {
|
|||
]),
|
||||
]),
|
||||
t.div({className: "RoomView_error"}, vm => vm.error),
|
||||
this._timelineList.mount()
|
||||
this._timelineList.mount(),
|
||||
this._composer.mount(),
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
mount() {
|
||||
this._composer = new MessageComposer(this.viewModel);
|
||||
this._timelineList = new TimelineList();
|
||||
return super.mount();
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this._composer.unmount();
|
||||
this._timelineList.unmount();
|
||||
super.unmount();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue