Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions electron-builder.json5
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@
},
files: ["dist", "dist-electron"],
afterSign: "scripts/notarize.cjs",

// 🔹 Enable deep linking across platforms
protocols: [
{
name: "Outerbase Protocol",
schemes: [
"outerbase",
"sqlite",
"mysql",
"postgres",
"turso",
"starbase",
"dolt",
"cloudflare",
],
role: "Editor",
},
],

mac: {
notarize: false,
target: [
Expand All @@ -22,6 +41,25 @@
},
],
artifactName: "outerbase-mac-${version}.${ext}",
entitlements: "entitlements.mac.plist",
entitlementsInherit: "entitlements.mac.plist",
extendInfo: {
CFBundleURLTypes: [
{
CFBundleURLName: "Outerbase",
CFBundleURLSchemes: [
"outerbase",
"sqlite",
"mysql",
"postgres",
"turso",
"starbase",
"dolt",
"cloudflare",
],
},
],
},
},
win: {
target: [
Expand Down
11 changes: 11 additions & 0 deletions electron/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,14 @@ export const STUDIO_ENDPOINT = "https://studio.outerbase.com/embed";
export const OUTERBASE_WEBSITE = "https://outerbase.com";
export const OUTERBASE_GITHUB =
"https://github.com/outerbase/studio-desktop/issues";

export const OuterbaseProtocols = [
"outerbase",
"sqlite",
"mysql",
"postgres",
"turso",
"starbase",
"dolt",
"cloudflare",
];
64 changes: 64 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { type ConnectionStoreItem } from "@/lib/conn-manager-store";
import { createDatabaseWindow } from "./window/create-database";
import { bindMenuIpc, bindDockerIpc, bindSavedDocIpc } from "./ipc";
import { bindAnalyticIpc } from "./ipc/analytics";
import { OuterbaseProtocols } from "./constants";

export function getAutoUpdater(): AppUpdater {
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
Expand Down Expand Up @@ -55,6 +56,17 @@ settings.load();

const mainWindow = new MainWindow();

OuterbaseProtocols.forEach((protocol) => {
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient(protocol, process.execPath, [
path.resolve(process.argv[1]),
]);
}
} else {
app.setAsDefaultProtocolClient(protocol);
}
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
Expand Down Expand Up @@ -138,6 +150,58 @@ ipcMain.handle("set-setting", (_, key, value) => {
ipcMain.on("navigate", (event, route: string) => {
event.sender.send("navigate-to", route);
});
// Handle deep links
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
app.quit();
} else {
app.on("second-instance", (_, commandLine) => {
// Process deep link when app is already running
const url = commandLine.find((arg) =>
OuterbaseProtocols.some((protocol) => arg.startsWith(`${protocol}://`)),
);

if (url) {
handleDeepLink(url);
}
});

app.on("open-url", (event, url) => {
event.preventDefault();
handleDeepLink(url);
});
}

function handleDeepLink(url: string) {
const win = mainWindow.getWindow();
// Someone tried to run a second instance, we should focus our window.
if (win) {
if (win.isMinimized()) {
win.restore();
} else {
win.focus();
}
try {
const urlObj = new URL(url);
const protocol = urlObj.protocol.replace(":", "");
const host = urlObj.hostname;
const port = urlObj.port || (protocol === "mysql" ? 3306 : 5432);
const database = urlObj.pathname.replace("/", "");

// Send deep link data to the React frontend
win.webContents.send("deep-link", {
protocol,
host,
port,
database,
});
} catch (error) {
console.error("Invalid deep link:", url);
}
} else {
mainWindow.init();
}
}
bindSavedDocIpc();
bindAnalyticIpc();
2 changes: 2 additions & 0 deletions src/database/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { useMemo, useState } from "react";
import ImportConnectionStringRoute from "./import-connection-string";
import useNavigateToRoute from "@/hooks/useNavigateToRoute";
import ConnectionList from "@/components/database/connection-list";
import useDeeplink from "@/hooks/useDeeplink";
import Header from "./header";

function ConnectionListRoute() {
useDeeplink();
useNavigateToRoute();
const [search, setSearch] = useState("");

Expand Down
29 changes: 29 additions & 0 deletions src/hooks/useDeeplink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { OuterbaseProtocols } from "../../electron/constants";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

interface Args {
protocol: string;
host: string;
port: string;
database: string;
}
export default function useDeeplink() {
const navigate = useNavigate();

useEffect(() => {
const handleDeepLink = (_event: unknown, { database }: Args) => {
const matchRoute =
OuterbaseProtocols.findIndex((protocol) => protocol === database) > -1;
// currently handle only create connection route
if (matchRoute) {
navigate(`/connection/create/${database}`);
}
};

window.outerbaseIpc.on("deep-link", handleDeepLink);
return () => {
window.outerbaseIpc.off("deep-link", handleDeepLink);
};
}, [navigate]);
}