117 lines
4.8 KiB
TypeScript
117 lines
4.8 KiB
TypeScript
/* Running as a desktop app.
|
|
|
|
One bundle answers both a browser and an app window (Tauri — see
|
|
src-tauri/), so nothing in `app/` is desktop code: it is the wiki asking at
|
|
run time whether it happens to be sitting in a window. The answer comes from
|
|
the shell, which marks the page it loads and marks no other — so the same
|
|
dist-desktop/ files opened in an ordinary browser behave as a web page, and
|
|
a web page that somehow met Tauri would only gain the shell's habits.
|
|
|
|
What a window does differently, then, is small and all about being a window
|
|
with one page and no browser chrome: a link leads out to the system's own
|
|
browser rather than replacing the wiki in the only view there is, and the
|
|
few bytes the app remembers of a person live where a window can be certain
|
|
of keeping them. */
|
|
|
|
import type { Ref } from 'vue'
|
|
import { watch } from 'vue'
|
|
|
|
/** The handle every Tauri webview injects into its page — present before the
|
|
* npm API is imported, and without it. Nothing else sets this name. */
|
|
export function isDesktop(): boolean {
|
|
if (!import.meta.client) return false
|
|
return (window as Window & { __TAURI_INTERNALS__?: unknown }).__TAURI_INTERNALS__ != null
|
|
}
|
|
|
|
/* A window is expected to have localStorage. A private browser may throw on
|
|
being asked for it at all, and a setting that cannot be remembered is worth
|
|
far less than a page that still draws — so the three doors below are the
|
|
only place any of it is touched, and none of them lets a throw through. */
|
|
|
|
export function storeGet(key: string): string | null {
|
|
try {
|
|
return localStorage.getItem(key)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function storeSet(key: string, value: string): void {
|
|
try {
|
|
localStorage.setItem(key, value)
|
|
} catch {
|
|
/* nothing remembered; nothing else depends on it */
|
|
}
|
|
}
|
|
|
|
export function storeClear(key: string): void {
|
|
try {
|
|
localStorage.removeItem(key)
|
|
} catch {
|
|
/* as good as cleared, for as long as this page lives */
|
|
}
|
|
}
|
|
|
|
/** Whether an address leads outside this app — to a document on some other
|
|
* site, or to a mail client, or to a phone dialler. The app's own pages are
|
|
* the relative `/wiki/…` the router already knows; an absolute address that
|
|
* happens to name the wiki's public site counts as outward too, and opens
|
|
* there in the browser, which is the honest reading of a link written out in
|
|
* full. */
|
|
export function leadsOutward(href: string): boolean {
|
|
if (!import.meta.client || !href) return false
|
|
let url: URL
|
|
try {
|
|
url = new URL(href, window.location.href)
|
|
} catch {
|
|
return false
|
|
}
|
|
if (url.protocol === 'mailto:' || url.protocol === 'tel:') return true
|
|
return (url.protocol === 'http:' || url.protocol === 'https:') && url.origin !== window.location.origin
|
|
}
|
|
|
|
/** Hand a document to whatever machine-level application owns it: the browser
|
|
* for a page, the mail client for `mailto:`. In a browser a link already does
|
|
* this, so the fallback is the browser's own new tab; in a window there is no
|
|
* other tab, and following the link in place would leave the wiki's page
|
|
* gone from the only view there is. */
|
|
export async function openExternal(url: string): Promise<void> {
|
|
if (!import.meta.client || !url) return
|
|
if (isDesktop()) {
|
|
try {
|
|
const { openUrl } = await import('@tauri-apps/plugin-opener')
|
|
await openUrl(url)
|
|
return
|
|
} catch {
|
|
/* no shell to hand it to, or this window is not allowed this one — the
|
|
least worse thing left is the browser's way out */
|
|
}
|
|
}
|
|
window.open(url, '_blank', 'noopener')
|
|
}
|
|
|
|
/** Keep a session token where a window cannot lose it.
|
|
|
|
On the web a cookie is the whole of it: the server reads it, and the browser
|
|
keeps it as long as the API said the token lasts. A window's own address
|
|
(`tauri://localhost`) is one a webview is not everywhere obliged to keep
|
|
cookies for, and a session dying with the window would mean signing in again
|
|
every time the app was opened — for a token the API hands out for a week. So
|
|
here it is mirrored into localStorage and taken back from there whenever the
|
|
cookie comes up empty, whichever of the two this webview chose to keep.
|
|
|
|
The cookie still leads: it is the one already at hand, the one a web build
|
|
would have read, and the one the sign-out line clears. A browser asked this
|
|
question of the same files ignores all of it — there is nothing to mirror
|
|
where cookies work — so the wiki's behavior there is untouched. */
|
|
export function keepTokenRemembered(token: Ref<string | null>, key: string): void {
|
|
if (!import.meta.client || !isDesktop()) return
|
|
|
|
if (!token.value) {
|
|
const kept = storeGet(key)
|
|
if (kept) token.value = kept
|
|
}
|
|
// sync, so signing out clears the mirror in the same breath, not on a tick
|
|
watch(token, (value) => (value ? storeSet(key, value) : storeClear(key)), { flush: 'sync' })
|
|
}
|