authorgravatar for Noojuno@users.noreply.github.comJono Kemball <Noojuno@users.noreply.github.com> 2026-03-09 17:43:35+13:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-08 21:43:35-07:00
log651084e8c05bf6da329108b098e4fd81322ee785
tree4cce2e760ac02bc8e45507de4eaa73bf27fcfd1c
parent812bb99f5c182351d0d899c9a88033d61fd7370a
signature Signed by PGP key B5690EEEBB952194

fix: fix external link opening in desktop (#599)

fixes #434

1 files changed, 30 insertions(+), 14 deletions(-)

apps/desktop/src/main.ts+30-14
...@@ -112,6 +112,25 @@ function formatErrorMessage(error: unknown): string {...@@ -112,6 +112,25 @@ function formatErrorMessage(error: unknown): string {
112 return String(error);112 return String(error);
113}113}
114114
115function getSafeExternalUrl(rawUrl: unknown): string | null {
116 if (typeof rawUrl !== "string" || rawUrl.length === 0) {
117 return null;
118 }
119
120 let parsedUrl: URL;
121 try {
122 parsedUrl = new URL(rawUrl);
123 } catch {
124 return null;
125 }
126
127 if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
128 return null;
129 }
130
131 return parsedUrl.toString();
132}
133
115function writeDesktopStreamChunk(134function writeDesktopStreamChunk(
116 streamName: "stdout" | "stderr",135 streamName: "stdout" | "stderr",
117 chunk: unknown,136 chunk: unknown,
...@@ -1033,23 +1052,13 @@ function registerIpcHandlers(): void {...@@ -1033,23 +1052,13 @@ function registerIpcHandlers(): void {
10331052
1034 ipcMain.removeHandler(OPEN_EXTERNAL_CHANNEL);1053 ipcMain.removeHandler(OPEN_EXTERNAL_CHANNEL);
1035 ipcMain.handle(OPEN_EXTERNAL_CHANNEL, async (_event, rawUrl: unknown) => {1054 ipcMain.handle(OPEN_EXTERNAL_CHANNEL, async (_event, rawUrl: unknown) => {
1036 if (typeof rawUrl !== "string" || rawUrl.length === 0) {1055 const externalUrl = getSafeExternalUrl(rawUrl);
1037 return false;1056 if (!externalUrl) {
1038 }
1039
1040 let parsedUrl: URL;
1041 try {
1042 parsedUrl = new URL(rawUrl);
1043 } catch {
1044 return false;
1045 }
1046
1047 if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
1048 return false;1057 return false;
1049 }1058 }
10501059
1051 try {1060 try {
1052 await shell.openExternal(parsedUrl.toString());1061 await shell.openExternal(externalUrl);
1053 return true;1062 return true;
1054 } catch {1063 } catch {
1055 return false;1064 return false;
...@@ -1142,7 +1151,14 @@ function createWindow(): BrowserWindow {...@@ -1142,7 +1151,14 @@ function createWindow(): BrowserWindow {
1142 Menu.buildFromTemplate(menuTemplate).popup({ window });1151 Menu.buildFromTemplate(menuTemplate).popup({ window });
1143 });1152 });
11441153
1145 window.webContents.setWindowOpenHandler(() => ({ action: "deny" }));1154 window.webContents.setWindowOpenHandler(({ url }) => {
1155 const externalUrl = getSafeExternalUrl(url);
1156 if (externalUrl) {
1157 void shell.openExternal(externalUrl);
1158 }
1159 return { action: "deny" };
1160 });
1161
1146 window.on("page-title-updated", (event) => {1162 window.on("page-title-updated", (event) => {
1147 event.preventDefault();1163 event.preventDefault();
1148 window.setTitle(APP_DISPLAY_NAME);1164 window.setTitle(APP_DISPLAY_NAME);