put everything together to make it roughly work

no local echo yet, and send errors are being swallowed
This commit is contained in:
Bruno Windels 2019-07-27 10:40:56 +02:00
parent 851100b88a
commit 3ed72df620
15 changed files with 91 additions and 11 deletions

View file

@ -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~~

View file

@ -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});
}
}

View file

@ -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}`);

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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");
}

View file

@ -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 {

View file

@ -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);
}

View file

@ -69,3 +69,11 @@
.RoomView_error {
color: red;
}
.MessageComposer > input {
display: block;
width: 100%;
box-sizing: border-box;
padding: 0.8em;
border: none;
}

View file

@ -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 = {};

View 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 = "";
}
}
}

View file

@ -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();
}