clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

remember phone signers per device, share pages natively and keep the screen awake while uploading

Dami committed 88d0641
commit
88d0641c934981c0a13bde054eb84989ab60007c
parent
791c183f5afe15152fffc30662e28d97366cce51
author
Dami <dami.etoile@gmail.com>
6 files changed, +48 -13
Minternal/webui/bridge.js29+++++++++++++++++++----------
Minternal/webui/components.js23++++++++++++++++++++++-
Minternal/webui/file-workspace.js3+++
Minternal/webui/public.html3++-
Minternal/webui/style.css1+
Mscripts/signer.test.mjs2+-
M internal/webui/bridge.js +19 -10
@@ -105,6 +105,9 @@
105105 document.getElementById("menu")?.focus();106106 });107107 if ("serviceWorker" in navigator) navigator.serviceWorker.register(localPath("/sw.js")).catch(() => {});108+ // An installed app asks to keep its cached shell and parked shares. Browsers109+ // grant this silently for installed apps, so it never shows a prompt.110+ if (matchMedia("(display-mode: standalone)").matches) navigator.storage?.persist?.().catch(() => {});108111 Object.assign(window.tiny, {root, localPath, sha256hex, authorization, signedFetch});109112 window.tinySignedFetch = signedFetch;110113
@@ -115,16 +118,21 @@
115118 location.assign(localPath("/"));116119 };117120 121+ // A remote signer session lives in this tab unless the person chose to122+ // remember the device; then it survives the installed app being closed.118123 const forgetRemote = () => {119- Object.values(storage).forEach(name => sessionStorage.removeItem(name));120- sessionStorage.removeItem("tiny.bunker");124+ for (const store of [sessionStorage, localStorage]) {125+ Object.values(storage).forEach(name => store.removeItem(name));126+ store.removeItem("tiny.bunker");127+ }121128 };122129 123130 const rememberRemote = async (uri, key, signer) => {124- sessionStorage.setItem(storage.uri, uri);125- sessionStorage.setItem(storage.key, window.NostrSigner.bytesToHex(key));126- sessionStorage.setItem(storage.pubkey, await signer.getPublicKey());127- sessionStorage.setItem(storage.remote, JSON.stringify(signer.bp));131+ const store = document.getElementById("remember-signer")?.checked ? localStorage : sessionStorage;132+ store.setItem(storage.uri, uri);133+ store.setItem(storage.key, window.NostrSigner.bytesToHex(key));134+ store.setItem(storage.pubkey, await signer.getPublicKey());135+ store.setItem(storage.remote, JSON.stringify(signer.bp));128136 };129137 130138 const setSigner = signer => {
@@ -229,10 +237,11 @@
229237 230238 // Resume a remote signer stored for this tab so page loads keep working.231239 (async () => {232- const uri = sessionStorage.getItem(storage.uri);233- const keyHex = sessionStorage.getItem(storage.key);234- const expected = sessionStorage.getItem(storage.pubkey);235- const remote = sessionStorage.getItem(storage.remote);240+ const store = sessionStorage.getItem(storage.key) ? sessionStorage : localStorage;241+ const uri = store.getItem(storage.uri);242+ const keyHex = store.getItem(storage.key);243+ const expected = store.getItem(storage.pubkey);244+ const remote = store.getItem(storage.remote);236245 if (!uri || !keyHex) return;237246 try {238247 const key = window.NostrSigner.hexToBytes(keyHex);
M internal/webui/components.js +22 -1
@@ -26,6 +26,8 @@
2626 // <nostr-compose kind="…" coordinate="30617:…" [root="…" root-pubkey="…" root-kind="…"] [parent="…" …]>2727 // Signs a NIP-34 issue, pull request, NIP-22 reply or status event and2828 // posts it to the relay.29+// <share-link [url="…"] [title="…"]>30+// The device share sheet for the page; empty without browser support.2931 // <push-toggle>3032 // Opts this browser into relay notifications; asks for permission only3133 // when pressed and registers the device with a signed request.
@@ -218,7 +220,7 @@
218220 connectedCallback() {219221 if (this.bound) return;220222 this.bound = true;221- this.innerHTML = '<h3>Share</h3><p><button type="button" data-copy>Copy Blossom URI</button> <button type="button" data-link>Copy share link</button></p><form data-share><label>Send to <input name="recipient" inputmode="text" placeholder="npub or hex pubkey"></label><button>Send privately</button></form><output role="status"></output>';223+ this.innerHTML = '<h3>Share</h3><p><button type="button" data-copy>Copy Blossom URI</button> <button type="button" data-link>Copy share link</button> <share-link></share-link></p><form data-share><label>Send to <input name="recipient" inputmode="text" placeholder="npub or hex pubkey"></label><button>Send privately</button></form><output role="status"></output>';222224 this.output = this.querySelector("output");223225 this.querySelector("[data-copy]").addEventListener("click", () => this.copy(this.blossomURI()));224226 this.querySelector("[data-link]").addEventListener("click", () => this.copy(this.shareLink()));
@@ -405,6 +407,24 @@
405407 // JsonView renders any JSON value. Arrays of objects become tables, objects406408 // become definition lists, scalar arrays become lists, and deep nesting407409 // falls back to compact JSON so a response never explodes the page.410+ // ShareLink offers the device's own share sheet for the current page, or411+ // the url and title attributes when set. It stays empty where the browser412+ // has no share support, so the page reads the same without it.413+ class ShareLink extends HTMLElement {414+ connectedCallback() {415+ if (this.bound) return;416+ this.bound = true;417+ if (!navigator.share) return;418+ const button = el("button", "Share…");419+ button.type = "button";420+ button.addEventListener("click", () => {421+ const data = {url: this.getAttribute("url") || location.href.split("#", 1)[0], title: this.getAttribute("title") || document.title};422+ navigator.share(data).catch(() => {});423+ });424+ this.replaceChildren(button);425+ }426+ }427+408428 // PushToggle opts this browser into relay notifications. Nothing happens409429 // until the button is pressed: permission is requested then, and the410430 // device subscription is registered with a signed request.
@@ -810,6 +830,7 @@
810830 customElements.define("publish-list", PublishList);811831 customElements.define("nostr-compose", NostrCompose);812832 customElements.define("push-toggle", PushToggle);833+ customElements.define("share-link", ShareLink);813834 customElements.define("nostr-key", NostrKey);814835 customElements.define("json-view", JsonView);815836
M internal/webui/file-workspace.js +3 -0
M internal/webui/public.html +2 -1
M internal/webui/style.css +1 -0
M scripts/signer.test.mjs +1 -1