Vibecoded CI/CD
This commit is contained in:
116
frontend/app/utils/desktop.ts
Normal file
116
frontend/app/utils/desktop.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/* 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' })
|
||||
}
|
||||
Reference in New Issue
Block a user