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,
|
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~~
|
||||||
|
|
|
@ -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});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}`);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 = {};
|
||||||
|
|
||||||
|
|
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 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();
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue