kunkun/apps/desktop/src/lib/utils/deeplink.ts
Huakun Shen 605a7844f2
Feature: Deep Link + Supabase OAuth + open extension in store with deep link (#16)
* feat(auth): add deep link and supabase auth

* fix(deep-link): fix some routing and reactive page rendering

* feat: implement supabase auth with pkce auth flow
2024-11-05 09:27:52 -05:00

116 lines
3.3 KiB
TypeScript

import { emitRefreshDevExt } from "@/utils/tauri-events"
import {
DEEP_LINK_PATH_AUTH_CONFIRM,
DEEP_LINK_PATH_OPEN,
DEEP_LINK_PATH_REFRESH_DEV_EXTENSION,
DEEP_LINK_PATH_STORE
} from "@kksh/api"
import type { UnlistenFn } from "@tauri-apps/api/event"
import { extname } from "@tauri-apps/api/path"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import * as deepLink from "@tauri-apps/plugin-deep-link"
import { error } from "@tauri-apps/plugin-log"
import { goto } from "$app/navigation"
import { toast } from "svelte-sonner"
import * as v from "valibot"
import { isInMainWindow } from "./window"
const StorePathSearchParams = v.object({
identifier: v.optional(v.string())
})
export function initDeeplink(): Promise<UnlistenFn> {
console.log("init deeplink")
if (!isInMainWindow()) {
return Promise.resolve(() => {})
}
// deepLink.getCurrent()
return deepLink.onOpenUrl((urls) => {
console.log("deep link:", urls)
urls.forEach(handleDeepLink)
})
}
/**
* Show and focus on the main window
*/
function openMainWindow() {
const appWindow = getCurrentWebviewWindow()
return appWindow
.show()
.then(() => appWindow.setFocus())
.catch((err) => {
console.error(err)
error(`Failed to show window upon deep link: ${err.message}`)
toast.error("Failed to show window upon deep link", {
description: err.message
})
})
}
export async function handleKunkunProtocol(parsedUrl: URL) {
const params = Object.fromEntries(parsedUrl.searchParams)
const { host, pathname, href } = parsedUrl
if (href.startsWith(DEEP_LINK_PATH_OPEN)) {
openMainWindow()
} else if (href.startsWith(DEEP_LINK_PATH_STORE)) {
const parsed = v.parse(StorePathSearchParams, params)
openMainWindow()
if (parsed.identifier) {
goto(`/extension/store/${parsed.identifier}`)
} else {
goto("/extension/store")
}
} else if (href.startsWith(DEEP_LINK_PATH_REFRESH_DEV_EXTENSION)) {
emitRefreshDevExt()
} else if (href.startsWith(DEEP_LINK_PATH_AUTH_CONFIRM)) {
openMainWindow()
goto(`/auth/confirm?${parsedUrl.searchParams.toString()}`)
} else {
console.error("Invalid path:", pathname)
toast.error("Invalid path", {
description: parsedUrl.href
})
}
}
export async function handleFileProtocol(parsedUrl: URL) {
console.log("File protocol:", parsedUrl)
const filePath = parsedUrl.pathname // Remove the leading '//' kunkun://open?identifier=qrcode gives "open"
console.log("File path:", filePath)
// from file absolute path, get file extension
const fileExt = await extname(filePath)
console.log("File extension:", fileExt)
switch (fileExt) {
case "kunkun":
// TODO: Handle file protocol, install extension from file (essentially a .tgz file)
break
default:
console.error("Unknown file extension:", fileExt)
toast.error("Unknown file extension", {
description: fileExt
})
break
}
}
/**
*
* @param url Deep Link URl, e.g. kunkun://open
*/
export async function handleDeepLink(url: string) {
const parsedUrl = new URL(url)
switch (parsedUrl.protocol) {
case "kunkun:":
return handleKunkunProtocol(parsedUrl)
case "file:":
return handleFileProtocol(parsedUrl)
default:
console.error("Invalid Protocol:", parsedUrl.protocol)
toast.error("Invalid Protocol", {
description: parsedUrl.protocol
})
break
}
}