diff --git a/doc/SENDING.md b/doc/SENDING.md index b4e6382c..f7e1e7f3 100644 --- a/doc/SENDING.md +++ b/doc/SENDING.md @@ -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~~ diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 3f3e8d77..a628349e 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -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}); + } } diff --git a/src/main.js b/src/main.js index 0b9ef4b8..9f957513 100644 --- a/src/main.js +++ b/src/main.js @@ -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}`); diff --git a/src/matrix/SendScheduler.js b/src/matrix/SendScheduler.js index 9fbab290..b7407238 100644 --- a/src/matrix/SendScheduler.js +++ b/src/matrix/SendScheduler.js @@ -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; } diff --git a/src/matrix/room/sending/SendQueue.js b/src/matrix/room/sending/SendQueue.js index d1666c02..b147d0f1 100644 --- a/src/matrix/room/sending/SendQueue.js +++ b/src/matrix/room/sending/SendQueue.js @@ -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(); } diff --git a/src/matrix/room/timeline/entries/BaseEntry.js b/src/matrix/room/timeline/entries/BaseEntry.js index 3c3d31b2..6c55788c 100644 --- a/src/matrix/room/timeline/entries/BaseEntry.js +++ b/src/matrix/room/timeline/entries/BaseEntry.js @@ -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) { diff --git a/src/matrix/room/timeline/entries/PendingEventEntry.js b/src/matrix/room/timeline/entries/PendingEventEntry.js index 03b79cf4..8b3fd656 100644 --- a/src/matrix/room/timeline/entries/PendingEventEntry.js +++ b/src/matrix/room/timeline/entries/PendingEventEntry.js @@ -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) { diff --git a/src/matrix/session.js b/src/matrix/session.js index 251debb9..d39404a8 100644 --- a/src/matrix/session.js +++ b/src/matrix/session.js @@ -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(); } } diff --git a/src/matrix/storage/idb/query-target.js b/src/matrix/storage/idb/query-target.js index 45d268b6..fa5b99f5 100644 --- a/src/matrix/storage/idb/query-target.js +++ b/src/matrix/storage/idb/query-target.js @@ -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"); } diff --git a/src/matrix/storage/idb/store.js b/src/matrix/storage/idb/store.js index ae2008bb..58709573 100644 --- a/src/matrix/storage/idb/store.js +++ b/src/matrix/storage/idb/store.js @@ -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 { diff --git a/src/matrix/storage/idb/transaction.js b/src/matrix/storage/idb/transaction.js index e66b6d5f..1c2c8286 100644 --- a/src/matrix/storage/idb/transaction.js +++ b/src/matrix/storage/idb/transaction.js @@ -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); } diff --git a/src/ui/web/css/room.css b/src/ui/web/css/room.css index 3a9dc886..955ad177 100644 --- a/src/ui/web/css/room.css +++ b/src/ui/web/css/room.css @@ -69,3 +69,11 @@ .RoomView_error { color: red; } + +.MessageComposer > input { + display: block; + width: 100%; + box-sizing: border-box; + padding: 0.8em; + border: none; +} diff --git a/src/ui/web/general/html.js b/src/ui/web/general/html.js index e316d3c8..a5488b2b 100644 --- a/src/ui/web/general/html.js +++ b/src/ui/web/general/html.js @@ -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 = {}; diff --git a/src/ui/web/session/room/MessageComposer.js b/src/ui/web/session/room/MessageComposer.js new file mode 100644 index 00000000..a4fb3715 --- /dev/null +++ b/src/ui/web/session/room/MessageComposer.js @@ -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 = ""; + } + } +} diff --git a/src/ui/web/session/room/RoomView.js b/src/ui/web/session/room/RoomView.js index c9225294..f431c16c 100644 --- a/src/ui/web/session/room/RoomView.js +++ b/src/ui/web/session/room/RoomView.js @@ -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(); }