| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is in lieu of saving the outbox. I tried that, and:
* If messages are dropped from the saved outbox before calling `api.postToChannel`, then messages "in flight" are lost when the page is reloaded unless the send succeeds after the client vanishes, as they are not re-sent when the page loads.
* If messages are dropped from the saved outbox after calling `api.postToChannel`, then messages "in flight" are duplicated when the page is reloaded and they get re-sent.
The CAP theorem is real and can hurt you.
The appropriate compensating mechanism would be a client-generated per-operation nonce, with server-side support for replaying responses by nonce if an operation already completed. That's a pretty big undertaking, and it's one we should probably do, but it's larger than I want to take on right now. Instead, we warn the user, and they can make their own decision.
Except we don't, sometimes.
When the client runs in a browser, this event handler prompts the user for confirmation before reloading, navigating away, closing the tab, or quitting. When run in a Safari app container, though, it only warns before reloading. Closing the window or quitting the app do not provoke a prompt. The warning is "best" effort. The failure mode is lost messages, which isn't particularly best.
|
| |
|
|
|
|
|
|
| |
directly from the UI.
This primarily serves to free up the message input immediately, so that the user can start drafting their next message right away. The wait while a message is being sent is actively disruptive when using Pilcrow on a server with noticable latency (hi.grimoire.ca has around 700ms), and this largely alleviates it.
Unsent messages can be lost if the client is closed or deactivated before they make it to the head of the queue.
|
| |\
| |
| |
| | |
Merged in spite of misgivings. This method will loop over the request until it completes, even if the user moves to a view where the response is no longer relevant.
|
| | | |
|
| | |
| |
| |
| |
| |
| | |
This is intended to transparently resume the session (using `boot` to start over) after more serious connection interruptions. It interacts with the heartbeat timeout: we let the browser try to reconnect through `EventSource` on its own for up to 30 seconds, before intervening, closing the event source, and starting attempts to call `boot`.
This covers both initial boot, which will now hang if the server is unavailable (sorry), and reconnection after an event timeout. No other operations are retried (particularly, sending a message is _not_ retried).
|
| | |
| |
| |
| | |
This was always intended, but it wasn't working that way because `channelsMeta.get(id)?.lastReadAt` evaluates to `undefined`, not `null`. Strict equality (`===`) treats those as distinct values.
|
| | | | |
| | \ | |
| |\ \ \
| |_|/
|/| |
| | |
| | | |
* Open all outbound links from messages in new tabs/windows.
* Stop user agents from sending referer headers pointing to Pilcrow instances when opening outbound links.
|
| | |/
|/|
| |
| |
| |
| |
| |
| | |
contrast.
We were ending up with colours picked by a more specific rule (`a, a:hover, a:visited, a:active`, from `app.css`), which was suppressing the colours we wanted. This is not a particularly elegant solution, but it is _an_ solution.
Code organized this way to avoid giving `a` elements a background colour.
|
| | | |
|
| |/
|
|
|
|
|
|
| |
* `target="_blank"`: when Pilcrow is running in a browser, clicking a link should not replace Pilcrow with the target of the link. Pilcrow is "app-like" enough that opening links in a new tab _by default_, without user intervention, is likely more appropriate.
* `rel="noreferrer"`, which (A) stops most UAs from setting a referrer header when following those links, and (B) also implies `noopener`, preventing the link target from using `window.opener` from reaching back into Pilcrow's DOM.
I briefly experimented with DOMPurify's `RETURN_DOM_FRAGMENT` mode, which would have made the tests somewhat easier to write, but I wasn't able to find a good way to integrate the returned `DocumentFragment` objects with Svelte components, so HTML-as-strings it is. Sigh.
|
| |
|
|
|
|
|
|
| |
The changes introduced in the previous commit make it possible to detect lost connections and restart them, so do so. The process is pretty simple - a new remote state is spun up using `/api/boot`, swapped in for the existing state, and a `new EventSource` is started from that new remote state to consume events.
This can induce some anomalies. For example, messages that arrive on the server between the loss of one connection and the creation of the next one just "show up" in boot, without ever appearing in the event stream. (This is technically also true on client startup, but it's easier to expect in that situation.) This is something we'll need to consider when implementing things like notifications or unread flags, though the ones we have today, which are state-based, do work fine.
By design, this _does not_ retry either the `/api/boot` call or the new event source setup. Event sources will try to reconnect on their own, up to a point, so that's fine, but we need to build something more robust for `/api/boot`. I want to tackle that separately from detecting lost connections and reacting to them, but that does mean that this is not a complete solution to client reconnects.
|
| | |
|
| |\ |
|
| | | |
|
| |/ |
|
| |
|
|
|
|
|
|
|
|
|
| |
Sorry about the thousand-line omnibus change; this is functionally a rewrite of the client's state tracking, flavoured to resemble the existing code as far as is possible, rather than something that can be parted out and committed in pieces.
Highlights:
* No more `store.writeable()`s. All state is now tracked using state runs or derivatives. State is still largely structured the way it was, but several bits of nested state have been rewritten to ensure that their properties are reactive just as much as their containers are.
* State is no longer global. `(app)/+layout` manages a stateful session, created via its load hook and started/stopped via component mount and destroy events. The session also tracks an event source for the current state, and feeds events into the state, broadly along the same lines as the previous stores-based approach.
Together these two changes fix up several rough spots integrating state with Svelte, and allow for the possibility of multiple states. This is a major step towards restartable states, and thus towards better connection management, which will require the ability to "start over" once a connection is restored.
|
| | |
|
| |\ |
|
| | |
| |
| |
| | |
Only once on load, then once per new message.
|
| |/ |
|
| |\ |
|
| | | |
|
| | | |
|
| | | |
|
| | |\ |
|
| | | |
| | |
| | |
| | |
| | |
| | | |
the DOM.
Prevents this from breaking during DOM unmounting, when leaving a channel.
|
| | | |
| | |
| | |
| | | |
That no longer vitally pertains.
|
| | | |
| | |
| | |
| | | |
To facilitate PWA behaviour.
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
This is stored locally, and, while parallel to channel info, is not the
same as.
Eventually, this may hold info about moot/decayed channels, and grow
unbounded. That'll need to be addressed.
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
When we hit the boot endpoint, we get the server's view of things. If we
just setChannels with that, we overwrite all our locally-stored info in
on things like lastReadAt. So we need to merge data.
Eventually, this might militate for a `meta` key containing an object of
locally stored data, rather than having to handle each key specially.
|
| | |/
|/|
| |
| |
| |
| |
| |
| | |
Access to "global" (maybe "external?") state is now handled at the top level of the component hierarchy, in `+page.svelte`, `+layout.svelte`, and their associated scripts. State is otherwise passed down through props, and changes are passed up through callbacks.
This is - hopefully - groundwork for refactoring state management a bit. I wanted to move access to state out to a smaller number of places, so that I have fewer places to update to implement reconnect logic. My broader goal is to make it easier to refactor these kinds of external side effects, as well, though no such changes are in this branch.
This change also makes testing a mile easier, since tests can interact with props and callbacks instead of emulating the whole HTTP request stack and the Pilcrow API. This change removes do-very-little tests.
|
| | | |
|
| | | |
|
| | | |
|
| | |
| |
| |
| | |
Browsers cope with weird nestings mostly fine, but there's no upside for us in testing that.
|
| | | |
|
| | |
| |
| |
| |
| |
| | |
According to <https://stackoverflow.com/a/6803278>, this was needed for old versions of Safari. However, since at least 2022, Safari has supported `content: none` just fine.
Related Safari bug (still open as of this writing, comments relevnat): <https://bugs.webkit.org/show_bug.cgi?id=20032>
|
| | | |
|
| | | |
|
| | | |
|
| | | |
|
| | |
| |
| |
| | |
It's amazing what you can learn by skimming the docs.
|
| | | |
|
| |/
|
|
|
|
|
|
|
|
|
|
|
| |
list.
This fixes a bug. To reproduce:
1. Open the client and log in.
2. Create a new channel using the `create channel` UI.
The expected result - and the behaviour after this commit - is that the newly-created channel will be shown in the sidebar immediately. The buggy behaviour is that it was not, but would appear in the sidebar once the client is reloaded. The channel would also not appear for other clients until they reloaded.
I'm not actually completely sure of _why_ this fixes the bug, but it does.
|
| | |
|
| |\ |
|
| | |
| |
| |
| | |
Let's try this.
|
| | | |
|
| | | |
|
| | | |
|