From 422cca746b75a93fc3ea60dd2f7e585e113159d9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Sun, 9 Jun 2019 16:26:17 +0200 Subject: [PATCH] add notes and prototypes for sending, etc --- doc/FRAGMENTS.md | 2 +- doc/GOAL.md | 10 +- doc/RELATIONS.md | 8 ++ doc/RELEASE.md | 14 +++ doc/SENDING.md | 143 +++++++++++++++++++++++++++- doc/domexception_mapping.md | 2 + prototypes/idb-store-files.html | 161 ++++++++++++++++++++++++++++++++ prototypes/online.html | 26 ++++++ 8 files changed, 355 insertions(+), 11 deletions(-) create mode 100644 doc/RELATIONS.md create mode 100644 doc/RELEASE.md create mode 100644 doc/domexception_mapping.md create mode 100644 prototypes/idb-store-files.html create mode 100644 prototypes/online.html diff --git a/doc/FRAGMENTS.md b/doc/FRAGMENTS.md index 621e155a..8c2a3dc8 100644 --- a/doc/FRAGMENTS.md +++ b/doc/FRAGMENTS.md @@ -28,7 +28,7 @@ - DONE: turn ObservableArray into ObservableSortedArray - upsert already sorted sections - DONE: upsert single entry - - adapt TilesCollection & Tile to entry changes + - DONE: adapt TilesCollection & Tile to entry changes - add live fragment id optimization if we haven't done so already - lets try to not have to have the fragmentindex in memory if the timeline isn't loaded diff --git a/doc/GOAL.md b/doc/GOAL.md index c643751a..9ee0a283 100644 --- a/doc/GOAL.md +++ b/doc/GOAL.md @@ -1,11 +1,5 @@ goal: -to write a minimal matrix client that should you all your rooms, allows you to pick one and read and write messages in it. +write client that works on lumia 950 phone, so I can use matrix on my phone. -on the technical side, the goal is to go low-memory, and test the performance of storing every event individually in indexeddb. - -nice properties of this approach: - -easy to delete oldest events when db becomes certain size/full (do we need new pagination token after deleting oldest? how to do that) - -sync is persisted in one transaction, so you always have state at some sync_token \ No newline at end of file +try approach offline to indexeddb. go low-memory, and test the performance of storing every event individually in indexeddb. diff --git a/doc/RELATIONS.md b/doc/RELATIONS.md new file mode 100644 index 00000000..3b5afa38 --- /dev/null +++ b/doc/RELATIONS.md @@ -0,0 +1,8 @@ +Relations and redactions + +events that refer to another event will need support in the SyncWriter, Timeline and SendQueue I think. +SyncWriter will need to resolve the related remote id to a [fragmentId, eventIndex] and persist that on the event that relates to some other. Same for SendQueue? If unknown remote id, not much to do. However, once the remote id comes in, how do we handle it correctly? We might need a index on m.relates_to/event_id? + +The timeline can take incoming events from both the SendQueue and SyncWriter, and see if their related to fragmentId/eventIndex is in view, and then update it? + +alternatively, SyncWriter/SendQueue could have a section with updatedEntries apart from newEntries? diff --git a/doc/RELEASE.md b/doc/RELEASE.md new file mode 100644 index 00000000..20bdcf2d --- /dev/null +++ b/doc/RELEASE.md @@ -0,0 +1,14 @@ +release: + - bundling css files + - bundling javascript + - run index.html template for release as opposed to develop version? + - make list of all resources needed (images, html page) + - create appcache manifest + service worker + - create tarball + sign + - make gh release with tarball + signature +publish: + - extract tarball + - upload to static website + - overwrite index.html + - overwrite service worker & appcache manifest + - put new version files under /x.x.x diff --git a/doc/SENDING.md b/doc/SENDING.md index 31b91c68..578317e9 100644 --- a/doc/SENDING.md +++ b/doc/SENDING.md @@ -10,6 +10,7 @@ how will we do local echo? a special kind of entry? will they be added to the same list? how do we store pending events? + OBSOLETE, see PendingEvent below: separate store with: roomId txnId @@ -20,16 +21,18 @@ how do we store pending events? // all the fields that might need to be sent to the server when posting a particular kind of event PendingEvent - queueOrder + queueOrder //is this high enough to priority //high priority means it also takes precedence over events sent in other rooms ... but how will that scheduling work? txnId type stateKey redacts content - blobUploadByteOffset: to support resumable uploads? + localRelatedId //what's the id? queueOrder? e.g. this would be a local id that this event relates to. We might need an index on it to update the PendingEvent once the related PendingEvent is sent. blob: a blob that needs to be uploaded and turned into a mxc to put into the content.url field before sending the event there is also info.thumbnail_url + blobMimeType? Or stored as part of blob? + //blobUploadByteOffset: to support resumable uploads? so when sending an event, we don't post a whole object, just the content, or a state key and content, or a redacts id. however, it's somewhat interesting to pretend an event has the same structure before it is sent, then when it came down from the server, so all the logic can reuse the same structure... @@ -57,3 +60,139 @@ we'll need to support some states for the UI: - sent offline is an external factor ... we probably need to deal with it throughout the app / matrix level in some way ... + - we could have callback on room for online/offline that is invoked by session, where they can start sending again? + perhaps with a transaction already open on the pending_events store + + +How could the SendQueue update the timeline? By having an ObservableMap for it's entries in the queue + Room + SendQueue + Timeline + +steps of sending + +```javascript + //at some point: + // sender is the thing that is shared across rooms to handle rate limiting. + const sendQueue = new SendQueue({roomId, hsApi, sender, storage}); + await sendQueue.load(); //loads the queue? + //might need to load members for e2e rooms + + class SendQueue { + // when trying to send + enqueueEvent(pendingEvent) { + // store event + // if online and not running send loop + // start sending loop + } + // send loop + // findNextPendingEvent comes from memory or store? + // if different object then in timeline, how to update timeline thingy? + // by entryKey? update it? + _sendLoop() { + while (let pendingEvent = await findNextPendingEvent()) { + pendingEvent.status = QUEUED; + try { + await this.sender.sendEvent(() => { + // callback gets called + pendingEvent.status = SENDING; + return pendingEvent; + }); + } catch (err) { + //offline + } + pendingEvent.status = SENT; + } + } + + resumeSending(online) { + // start loop again when back online + } + + // on sync, when received an event with transaction_id + // the first is the transaction_id, + // the second is the storage transaction to modify the pendingevent store if needed + receiveRemoteEcho(txnId, txn) { + + } + + // returns entries? to be appended to timeline? + // return an ObservableList here? Rather ObservableMap? what ID? queueOrder? that won't be unique over time? + + // wrt to relations and redactions, we will also need the list of current + // or we could just do a lookup of the local id to remote once + // it's time to send an event ... perhaps we already have the txn open anyways. + // so we will need to store the event_id returned from /send... + // but by the time it's time to send an event, the one it relates to might already have been + // remove from pendingevents? + // maybe we should have an index on relatedId or something stored in pendingevents and that way + // we can update it once the relatedto event is sent + // ok, so we need an index on relatedId, not the full list for anything apart from timeline display? think so ... + get entriesMap() { + + } + + } + + + class Room { + resumeSending(online) { + if (online) { + this.sendQueue.setOnline(online); + } + } + } +``` + +we were thinking before of having a more lightweight structure to export from timeline, where we only keep a sorted list/set of keys in the collection, and we emit ranges of sorted keys that are either added, updated or removed. we could easily join this with the timeline and values are only stored by the TilesCollection. We do however need to peek into the queue to update local relatedTo ids. + +probably best to keep send queue in memory. +so, persistence steps in sending: + - get largest queueOrder + 1 as id/new queueOrder + - the downside of this that when the last event is sent at the same time as adding a new event it would become an update? but the code paths being separate (receiveRemoteEcho and enqueueEvent) probably prevent this. + - persist incoming pending event + - update with remote id if relatedId for pending event + - update once attachment(s) are sent + - send in-memory updates of upload progress through pending event entry + - if the media store supports resumable uploads, we *could* also periodically store how much was uploaded already. But the current REST API can't support this. + - update once sent (we don't remove here until we've receive remote echo) + - store the remote event id so events that will relate to this pending event can get the remote id through getRelateToId() + - remove once remote echo is received + +(Pending)EventEntry will need a method getRelateToId() that can return an instance of LocalId or something for unsent events + +if we're not rate limited, we'll want to upload attachments in parallel with sending messages before attachee event. + +so as long as not rate limited, we'd want several queues to send per room + + +``` + sender (room 1) +--------------------- + ^ ^ +event1 attachment1 + ^ | +event2------- +``` + +later on we can make this possible, for now we just upload the attachments right before event. + + +so, we need to write: +RateLimitedSender + all rate-limited rest api calls go through here so it can coordinate which ones should be prioritized and not + do more requests than needed while rate limited. It will have a list of current requests and initially just go from first to last but later could implement prioritizing the current room, events before attachments, ... +SendQueue (talks to store, had queue logic) for now will live under timeline as you can't send events for rooms you are not watching? could also live under Room so always available if needed +PendingEvent (what the store returns) perhaps doesn't even need a class? can all go in the entry +PendingEventEntry (conforms to Entry API) + can have static helper functions to create given kind of events + PendingEventEntry.stateEvent(type, stateKey, content) + PendingEventEntry.event(type, content, {url: file, "info.thumbnail_url": thumb_file}) + PendingEventEntry.redaction(redacts) +PendingEventStore + add() + maxQueueOrder + getAll() + get() + update() + remove() diff --git a/doc/domexception_mapping.md b/doc/domexception_mapping.md new file mode 100644 index 00000000..0b0fdf20 --- /dev/null +++ b/doc/domexception_mapping.md @@ -0,0 +1,2 @@ +err.name: explanation +DataError: parameters to idb request where invalid diff --git a/prototypes/idb-store-files.html b/prototypes/idb-store-files.html new file mode 100644 index 00000000..cef12f2c --- /dev/null +++ b/prototypes/idb-store-files.html @@ -0,0 +1,161 @@ + + + + + + + +

+ + + +

+ + + diff --git a/prototypes/online.html b/prototypes/online.html new file mode 100644 index 00000000..056037e3 --- /dev/null +++ b/prototypes/online.html @@ -0,0 +1,26 @@ + + + + + + + + + +