diff --git a/src/app/components/KeyDownload.svelte b/src/app/components/KeyDownload.svelte index a6bd8fb3..df0249f6 100644 --- a/src/app/components/KeyDownload.svelte +++ b/src/app/components/KeyDownload.svelte @@ -39,7 +39,7 @@ .replace(/NEWLINE/g, "\n\n") .trim() - const downloadKey = () => { + const downloadKey = async () => { const sharedCopy = ` Most online services keep track of users by giving them a username and password. This gives the service total control over their users, allowing them to ban them at any time, or sell their activity. @@ -52,6 +52,8 @@ account. ` + let instructions: string + if (usePassword) { if (password.length < 12) { return pushToast({ @@ -61,7 +63,7 @@ } const ncryptsec = encrypt(hexToBytes(secret), password) - const instructions = ` + instructions = ` This file contains a backup of your Nostr secret key, downloaded from ${PLATFORM_NAME} and encrypted using a password you chose when you signed up. @@ -74,11 +76,9 @@ To use it to log in to other Nostr apps, find a Nostr Signer app (https://nostrapps.com/#signers is a good place to look), and import your key. ` - - downloadText("Nostr Secret Key.txt", cleanupCopy(instructions)) } else { const nsec = nsecEncode(hexToBytes(secret)) - const instructions = ` + instructions = ` This file contains a backup of your Nostr secret key, downloaded from ${PLATFORM_NAME}. ${sharedCopy} @@ -90,11 +90,23 @@ To use it to log in to other Nostr apps, find a Nostr Signer app (https://nostrapps.com/#signers is a good place to look), and import your key. ` - - downloadText("Nostr Secret Key.txt", cleanupCopy(instructions)) } - didDownload = true + try { + await downloadText("Nostr Secret Key.txt", cleanupCopy(instructions)) + + didDownload = true + } catch (e) { + // Dismissing the native share sheet rejects with "Share canceled" — leave + // the flow gated so the user can try again rather than showing an error. + if ( + !String((e as any)?.message || e) + .toLowerCase() + .includes("cancel") + ) { + pushToast({theme: "error", message: "We couldn't save your key. Please try again."}) + } + } } const onPasswordChange = () => { diff --git a/src/lib/html.ts b/src/lib/html.ts index 398f70cd..6056494f 100644 --- a/src/lib/html.ts +++ b/src/lib/html.ts @@ -1,4 +1,5 @@ import {sleep, randomId} from "@welshman/lib" +import {Capacitor} from "@capacitor/core" export {preventDefault, stopPropagation} from "svelte/legacy" // Anchors an @svelte-plugins/datepicker popup with fixed positioning so it @@ -127,7 +128,28 @@ export const createScroller = ({ export const isMobile = "ontouchstart" in document.documentElement -export const downloadText = (filename: string, text: string) => { +export const downloadText = async (filename: string, text: string) => { + // The blob trick is a no-op in native WebViews (Android in + // particular never triggers a download), so on device we write the file and + // hand it to the native share sheet, letting the user save it to Files, + // Drive, a password manager, etc. Cache is the directory our FileProvider + // (android/app/src/main/res/xml/file_paths.xml) is configured to serve. + if (Capacitor.isNativePlatform()) { + const {Filesystem, Directory, Encoding} = await import("@capacitor/filesystem") + const {Share} = await import("@capacitor/share") + + const {uri} = await Filesystem.writeFile({ + path: filename, + data: text, + directory: Directory.Cache, + encoding: Encoding.UTF8, + }) + + await Share.share({title: filename, files: [uri]}) + + return + } + const blob = new Blob([text], {type: "text/plain"}) const url = URL.createObjectURL(blob) const a = document.createElement("a")