Merge pull request #7 from offendingcommit/feat/deep-linking
feat: deep linking for hosted URLs and openconcho:// scheme
This commit is contained in:
117
packages/desktop/src-tauri/Cargo.lock
generated
117
packages/desktop/src-tauri/Cargo.lock
generated
@@ -328,6 +328,26 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@@ -446,6 +466,12 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -667,6 +693,15 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.12"
|
||||
@@ -1301,6 +1336,12 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@@ -1454,7 +1495,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
"windows-registry 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2231,6 +2272,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-shell",
|
||||
]
|
||||
@@ -2241,6 +2283,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
@@ -3021,6 +3073,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
@@ -3862,6 +3924,27 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ee75bc5627f77bfdf40c913255ebc258117b10ebe2b2239a1a1cf40b0b58aa"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"plist",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry 0.5.3",
|
||||
"windows-result 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.5.0"
|
||||
@@ -4123,6 +4206,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
@@ -4354,9 +4446,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
@@ -4973,6 +5077,17 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
|
||||
@@ -14,5 +14,6 @@ tauri-build = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-http = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-deep-link = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Default capabilities for the OpenConcho desktop window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
{ "url": "http://*" },
|
||||
{ "url": "http://*:*" },
|
||||
{ "url": "https://*" },
|
||||
{ "url": "https://*:*" }
|
||||
]
|
||||
},
|
||||
"shell:allow-open"
|
||||
]
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Default capabilities for the OpenConcho desktop window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
{ "url": "http://*" },
|
||||
{ "url": "http://*:*" },
|
||||
{ "url": "https://*" },
|
||||
{ "url": "https://*:*" }
|
||||
]
|
||||
},
|
||||
"shell:allow-open",
|
||||
"deep-link:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -32,5 +32,13 @@
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
"mobile": [],
|
||||
"desktop": {
|
||||
"schemes": ["openconcho"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-router": "^1.120.3",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.9",
|
||||
"@tauri-apps/plugin-http": "^2",
|
||||
"@tauri-apps/plugin-shell": "^2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
40
packages/web/src/lib/deep-link.ts
Normal file
40
packages/web/src/lib/deep-link.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { Router } from "@tanstack/react-router";
|
||||
|
||||
const SCHEME = "openconcho:";
|
||||
|
||||
function isTauri(): boolean {
|
||||
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
||||
}
|
||||
|
||||
function navigateFromUrl(router: Router<never, never>, raw: string): void {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(raw);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (parsed.protocol !== SCHEME) return;
|
||||
|
||||
const host = parsed.hostname || parsed.pathname.replace(/^\/+/, "").split("/")[0];
|
||||
const search = parsed.search;
|
||||
|
||||
if (host === "explore") {
|
||||
router.navigate({ to: `/explore${search}` as never });
|
||||
return;
|
||||
}
|
||||
|
||||
const path = parsed.pathname.startsWith("/") ? parsed.pathname : `/${parsed.pathname}`;
|
||||
router.navigate({ to: `${path}${search}` as never });
|
||||
}
|
||||
|
||||
export async function initDeepLinks(router: Router<never, never>): Promise<void> {
|
||||
if (!isTauri()) return;
|
||||
const { onOpenUrl, getCurrent } = await import("@tauri-apps/plugin-deep-link");
|
||||
|
||||
const initial = await getCurrent();
|
||||
if (initial?.length) navigateFromUrl(router, initial[0]);
|
||||
|
||||
await onOpenUrl((urls) => {
|
||||
if (urls[0]) navigateFromUrl(router, urls[0]);
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { createRouter, RouterProvider } from "@tanstack/react-router";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { DemoProvider } from "./context/DemoContext";
|
||||
import { initDeepLinks } from "./lib/deep-link";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import "./index.css";
|
||||
|
||||
@@ -27,6 +28,8 @@ declare module "@tanstack/react-router" {
|
||||
}
|
||||
}
|
||||
|
||||
void initDeepLinks(router as never);
|
||||
|
||||
const root = document.getElementById("root");
|
||||
if (!root) throw new Error("Missing #root element");
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as WorkspacesRouteImport } from './routes/workspaces'
|
||||
import { Route as SettingsRouteImport } from './routes/settings'
|
||||
import { Route as ExploreRouteImport } from './routes/explore'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as WorkspacesWorkspaceIdRouteImport } from './routes/workspaces_.$workspaceId'
|
||||
import { Route as WorkspacesWorkspaceIdWebhooksRouteImport } from './routes/workspaces_.$workspaceId_.webhooks'
|
||||
@@ -31,6 +32,11 @@ const SettingsRoute = SettingsRouteImport.update({
|
||||
path: '/settings',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ExploreRoute = ExploreRouteImport.update({
|
||||
id: '/explore',
|
||||
path: '/explore',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
@@ -86,6 +92,7 @@ const WorkspacesWorkspaceIdPeersPeerIdChatRoute =
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/explore': typeof ExploreRoute
|
||||
'/settings': typeof SettingsRoute
|
||||
'/workspaces': typeof WorkspacesRoute
|
||||
'/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdRoute
|
||||
@@ -99,6 +106,7 @@ export interface FileRoutesByFullPath {
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/explore': typeof ExploreRoute
|
||||
'/settings': typeof SettingsRoute
|
||||
'/workspaces': typeof WorkspacesRoute
|
||||
'/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdRoute
|
||||
@@ -113,6 +121,7 @@ export interface FileRoutesByTo {
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/explore': typeof ExploreRoute
|
||||
'/settings': typeof SettingsRoute
|
||||
'/workspaces': typeof WorkspacesRoute
|
||||
'/workspaces_/$workspaceId': typeof WorkspacesWorkspaceIdRoute
|
||||
@@ -128,6 +137,7 @@ export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/explore'
|
||||
| '/settings'
|
||||
| '/workspaces'
|
||||
| '/workspaces/$workspaceId'
|
||||
@@ -141,6 +151,7 @@ export interface FileRouteTypes {
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/'
|
||||
| '/explore'
|
||||
| '/settings'
|
||||
| '/workspaces'
|
||||
| '/workspaces/$workspaceId'
|
||||
@@ -154,6 +165,7 @@ export interface FileRouteTypes {
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/explore'
|
||||
| '/settings'
|
||||
| '/workspaces'
|
||||
| '/workspaces_/$workspaceId'
|
||||
@@ -168,6 +180,7 @@ export interface FileRouteTypes {
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
ExploreRoute: typeof ExploreRoute
|
||||
SettingsRoute: typeof SettingsRoute
|
||||
WorkspacesRoute: typeof WorkspacesRoute
|
||||
WorkspacesWorkspaceIdRoute: typeof WorkspacesWorkspaceIdRoute
|
||||
@@ -196,6 +209,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof SettingsRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/explore': {
|
||||
id: '/explore'
|
||||
path: '/explore'
|
||||
fullPath: '/explore'
|
||||
preLoaderRoute: typeof ExploreRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
@@ -264,6 +284,7 @@ declare module '@tanstack/react-router' {
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
ExploreRoute: ExploreRoute,
|
||||
SettingsRoute: SettingsRoute,
|
||||
WorkspacesRoute: WorkspacesRoute,
|
||||
WorkspacesWorkspaceIdRoute: WorkspacesWorkspaceIdRoute,
|
||||
|
||||
73
packages/web/src/routes/explore.tsx
Normal file
73
packages/web/src/routes/explore.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
|
||||
type ExploreSearch = {
|
||||
workspace?: string;
|
||||
view?: "sessions" | "peers" | "conclusions" | "webhooks";
|
||||
session?: string;
|
||||
peer?: string;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/explore")({
|
||||
validateSearch: (search: Record<string, unknown>): ExploreSearch => ({
|
||||
workspace: typeof search.workspace === "string" ? search.workspace : undefined,
|
||||
view:
|
||||
search.view === "sessions" ||
|
||||
search.view === "peers" ||
|
||||
search.view === "conclusions" ||
|
||||
search.view === "webhooks"
|
||||
? search.view
|
||||
: undefined,
|
||||
session: typeof search.session === "string" ? search.session : undefined,
|
||||
peer: typeof search.peer === "string" ? search.peer : undefined,
|
||||
}),
|
||||
loaderDeps: ({ search }) => search,
|
||||
loader: ({ deps }) => {
|
||||
const { workspace, view, session, peer } = deps;
|
||||
|
||||
if (!workspace) {
|
||||
throw redirect({ to: "/workspaces" as never });
|
||||
}
|
||||
|
||||
if (view === "sessions" && session) {
|
||||
throw redirect({
|
||||
to: "/workspaces/$workspaceId/sessions/$sessionId" as never,
|
||||
params: { workspaceId: workspace, sessionId: session } as never,
|
||||
});
|
||||
}
|
||||
if (view === "sessions") {
|
||||
throw redirect({
|
||||
to: "/workspaces/$workspaceId/sessions" as never,
|
||||
params: { workspaceId: workspace } as never,
|
||||
});
|
||||
}
|
||||
if (view === "peers" && peer) {
|
||||
throw redirect({
|
||||
to: "/workspaces/$workspaceId/peers/$peerId" as never,
|
||||
params: { workspaceId: workspace, peerId: peer } as never,
|
||||
});
|
||||
}
|
||||
if (view === "peers") {
|
||||
throw redirect({
|
||||
to: "/workspaces/$workspaceId/peers" as never,
|
||||
params: { workspaceId: workspace } as never,
|
||||
});
|
||||
}
|
||||
if (view === "conclusions") {
|
||||
throw redirect({
|
||||
to: "/workspaces/$workspaceId/conclusions" as never,
|
||||
params: { workspaceId: workspace } as never,
|
||||
});
|
||||
}
|
||||
if (view === "webhooks") {
|
||||
throw redirect({
|
||||
to: "/workspaces/$workspaceId/webhooks" as never,
|
||||
params: { workspaceId: workspace } as never,
|
||||
});
|
||||
}
|
||||
|
||||
throw redirect({
|
||||
to: "/workspaces/$workspaceId" as never,
|
||||
params: { workspaceId: workspace } as never,
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user