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, takes care of rate limiting,
and sending events from different rooms in parallel, 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~~ 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() { get avatarInitials() {
return avatarInitials(this._room.name); 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 SessionView from "./ui/web/session/SessionView.js";
import SessionViewModel from "./domain/session/SessionViewModel.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 HOMESERVER = `http://${HOST}:8008`;
const USERNAME = "bruno1"; const USERNAME = "bruno1";
const USER_ID = `@${USERNAME}:localhost`; const USER_ID = `@${USERNAME}:localhost`;
@ -76,6 +76,7 @@ export default async function main(container) {
if (needsInitialSync) { if (needsInitialSync) {
showSession(container, session, sync); showSession(container, session, sync);
} }
// this will start sending unsent messages
session.notifyNetworkAvailable(); session.notifyNetworkAvailable();
} catch(err) { } catch(err) {
console.error(`${err.message}:\n${err.stack}`); 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"; import {HomeServerError, NetworkError} from "./error.js";
export class RateLimitingBackoff { export class RateLimitingBackoff {
@ -79,7 +79,7 @@ export class SendScheduler {
async _sendLoop() { async _sendLoop() {
while (this._sendRequests.length) { while (this._sendRequests.length) {
const request = this._sendRequests.unshift(); const request = this._sendRequests.shift();
let result; let result;
try { try {
// this can throw! // this can throw!
@ -94,6 +94,7 @@ export class SendScheduler {
} }
this._sendRequests = []; this._sendRequests = [];
} }
console.error("error for request", request);
request.reject(err); request.reject(err);
break; break;
} }

View File

@ -15,6 +15,9 @@ export default class SendQueue {
this._storage = storage; this._storage = storage;
this._sendScheduler = sendScheduler; this._sendScheduler = sendScheduler;
this._pendingEvents = new SortedArray((a, b) => a.queueIndex - b.queueIndex); 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._pendingEvents.setManySorted(pendingEvents.map(data => new PendingEvent(data)));
this._isSending = false; this._isSending = false;
this._offline = false; this._offline = false;
@ -24,13 +27,17 @@ export default class SendQueue {
async _sendLoop() { async _sendLoop() {
this._isSending = true; this._isSending = true;
try { try {
console.log("start sending", this._amountSent, "<", this._pendingEvents.length);
while (this._amountSent < this._pendingEvents.length) { while (this._amountSent < this._pendingEvents.length) {
const pendingEvent = this._pendingEvents.get(this._amountSent); const pendingEvent = this._pendingEvents.get(this._amountSent);
console.log("trying to send", pendingEvent.content.body);
this._amountSent += 1; this._amountSent += 1;
if (pendingEvent.remoteId) { if (pendingEvent.remoteId) {
continue; continue;
} }
console.log("really sending now");
const response = await this._sendScheduler.request(hsApi => { const response = await this._sendScheduler.request(hsApi => {
console.log("got sendScheduler slot");
return hsApi.send( return hsApi.send(
pendingEvent.roomId, pendingEvent.roomId,
pendingEvent.eventType, pendingEvent.eventType,
@ -39,7 +46,10 @@ export default class SendQueue {
); );
}); });
pendingEvent.remoteId = response.event_id; pendingEvent.remoteId = response.event_id;
//
console.log("writing remoteId now");
await this._tryUpdateEvent(pendingEvent); await this._tryUpdateEvent(pendingEvent);
console.log("keep sending?", this._amountSent, "<", this._pendingEvents.length);
} }
} catch(err) { } catch(err) {
if (err instanceof NetworkError) { if (err instanceof NetworkError) {
@ -97,16 +107,22 @@ export default class SendQueue {
async _tryUpdateEvent(pendingEvent) { async _tryUpdateEvent(pendingEvent) {
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]); const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
console.log("_tryUpdateEvent: got txn");
try { try {
// pendingEvent might have been removed already here // pendingEvent might have been removed already here
// by a racing remote echo, so check first so we don't recreate it // 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)) { if (await txn.pendingEvents.exists(pendingEvent.roomId, pendingEvent.queueIndex)) {
console.log("_tryUpdateEvent: inside if exists");
txn.pendingEvents.update(pendingEvent.data); txn.pendingEvents.update(pendingEvent.data);
} }
console.log("_tryUpdateEvent: after exists");
} catch (err) { } catch (err) {
txn.abort(); txn.abort();
console.log("_tryUpdateEvent: error", err);
throw err; throw err;
} }
console.log("_tryUpdateEvent: try complete");
await txn.complete(); await txn.complete();
} }

View File

@ -1,6 +1,6 @@
//entries can be sorted, first by fragment, then by entry index. //entries can be sorted, first by fragment, then by entry index.
import EventKey from "../EventKey.js"; 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 { export default class BaseEntry {
constructor(fragmentIdComparer) { constructor(fragmentIdComparer) {

View File

@ -1,6 +1,4 @@
import BaseEntry from "./BaseEntry.js"; import BaseEntry, {PENDING_FRAGMENT_ID} from "./BaseEntry.js";
export const PENDING_FRAGMENT_ID = Number.MAX_SAFE_INTEGER;
export default class PendingEventEntry extends BaseEntry { export default class PendingEventEntry extends BaseEntry {
constructor(pendingEvent) { constructor(pendingEvent) {

View File

@ -33,13 +33,13 @@ export default class Session {
// load rooms // load rooms
const rooms = await txn.roomSummary.getAll(); const rooms = await txn.roomSummary.getAll();
await Promise.all(rooms.map(summary => { 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); return room.load(summary, txn);
})); }));
} }
notifyNetworkAvailable() { notifyNetworkAvailable() {
for (const room of this._rooms) { for (const [, room] of this._rooms) {
room.resumeSending(); room.resumeSending();
} }
} }

View File

@ -21,6 +21,10 @@ export default class QueryTarget {
return reqAsPromise(this._target.get(key)); return reqAsPromise(this._target.get(key));
} }
getKey(key) {
return reqAsPromise(this._target.getKey(key));
}
reduce(range, reducer, initialValue) { reduce(range, reducer, initialValue) {
return this._reduce(range, reducer, initialValue, "next"); return this._reduce(range, reducer, initialValue, "next");
} }

View File

@ -46,6 +46,14 @@ class QueryTargetWrapper {
throw new StorageError("get failed", err); throw new StorageError("get failed", err);
} }
} }
getKey(...params) {
try {
return this._qt.getKey(...params);
} catch(err) {
throw new StorageError("getKey failed", err);
}
}
delete(...params) { delete(...params) {
try { try {

View File

@ -6,6 +6,7 @@ import RoomSummaryStore from "./stores/RoomSummaryStore.js";
import TimelineEventStore from "./stores/TimelineEventStore.js"; import TimelineEventStore from "./stores/TimelineEventStore.js";
import RoomStateStore from "./stores/RoomStateStore.js"; import RoomStateStore from "./stores/RoomStateStore.js";
import TimelineFragmentStore from "./stores/TimelineFragmentStore.js"; import TimelineFragmentStore from "./stores/TimelineFragmentStore.js";
import PendingEventStore from "./stores/PendingEventStore.js";
export default class Transaction { export default class Transaction {
constructor(txn, allowedStoreNames) { constructor(txn, allowedStoreNames) {
@ -55,6 +56,10 @@ export default class Transaction {
return this._store("roomState", idbStore => new RoomStateStore(idbStore)); return this._store("roomState", idbStore => new RoomStateStore(idbStore));
} }
get pendingEvents() {
return this._store("pendingEvents", idbStore => new PendingEventStore(idbStore));
}
complete() { complete() {
return txnAsPromise(this._txn); return txnAsPromise(this._txn);
} }

View File

@ -69,3 +69,11 @@
.RoomView_error { .RoomView_error {
color: red; 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 = [ export const TAG_NAMES = [
"ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6",
"p", "strong", "em", "span", "img", "section", "main", "article", "aside", "p", "strong", "em", "span", "img", "section", "main", "article", "aside",
"pre", "button", "time"]; "pre", "button", "time", "input", "textarea"];
export const tag = {}; 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 TemplateView from "../../general/TemplateView.js";
import TimelineList from "./TimelineList.js"; import TimelineList from "./TimelineList.js";
import MessageComposer from "./MessageComposer.js";
export default class RoomView extends TemplateView { export default class RoomView extends TemplateView {
constructor(viewModel) { constructor(viewModel) {
@ -18,17 +19,20 @@ export default class RoomView extends TemplateView {
]), ]),
]), ]),
t.div({className: "RoomView_error"}, vm => vm.error), t.div({className: "RoomView_error"}, vm => vm.error),
this._timelineList.mount() this._timelineList.mount(),
this._composer.mount(),
]) ])
]); ]);
} }
mount() { mount() {
this._composer = new MessageComposer(this.viewModel);
this._timelineList = new TimelineList(); this._timelineList = new TimelineList();
return super.mount(); return super.mount();
} }
unmount() { unmount() {
this._composer.unmount();
this._timelineList.unmount(); this._timelineList.unmount();
super.unmount(); super.unmount();
} }