Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "internal-desktop",
"private": true,
"version": "0.1.7",
"version": "0.1.8",
"description": "Tauri shell wrapping internal.automators.com — desktop app for the Automators internal hub.",
"scripts": {
"tauri": "tauri",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "internal-desktop"
version = "0.1.7"
version = "0.1.8"
Comment on lines 1 to +3
description = "Automators Internal — Tauri shell"
authors = ["Automators GmbH"]
license = "UNLICENSED"
Expand Down
88 changes: 67 additions & 21 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,27 +116,62 @@ const SHELL_CSS_INJECT: &str = r#"
}
}, true); // capture phase, before React handlers

// ─── Keep target="_blank" links inside the Tauri window ────
// ─── Route target="_blank" by host ────────────────────────
//
// Default Tauri behavior for <a target="_blank"> and
// window.open() is to route to the system browser — fine for
// a generic browser, wrong for a "feels like a native app"
// shell. Override both:
// 1. Capture-phase click handler intercepts target="_blank"
// anchors before the WebView's default new-window
// handling. Navigates in the current window instead.
// 2. window.open is replaced to do the same — covers SDK
// code (e.g., a third-party widget that calls
// window.open() rather than rendering an anchor).
// v0.1.6 forced every target="_blank" click to navigate
// in-window so the shell would feel like an app. That worked
// for cross-internal links but trapped users when they
// clicked an *external* link (e.g. Capacities in the
// sidebar): the entire shell window was hijacked off to
// capacities.app, the user lost their place inside internal,
// and the only way back was ⌘←. Wrong UX for "this is a
// different app" links — those should pop a real browser
// tab.
//
// Same-window navigation means the user loses the current
// page when clicking out (⌘← to go back, like in a browser).
// If the dashboard ever wants to keep the current page open
// when opening a reference (a SharePoint URL, etc.), the
// upgrade path is to swap window.location.href for an IPC
// call that creates a new Tauri WebviewWindow. Skipping that
// for v0.1.6 because same-window behavior covers the most
// common complaint.
// v0.1.8 splits the rule by destination host:
// - Host ∈ INTERNAL_HOSTS → in-window navigation. Same as
// v0.1.6 — keeps the auth-redirect dance and any
// internal cross-link inside the shell.
// - Anything else → routed to the OS default browser via
// the shell:open IPC. The internal window stays put;
// the external destination opens in a real browser tab.
//
// INTERNAL_HOSTS deliberately mirrors the capability's
// remote.urls allowlist (capabilities/default.json) — those
// are the origins we trust to make IPC calls and they're
// exactly the surfaces that should keep the in-window
// experience. Adding a new internal host means updating both
// here AND the capability JSON.
//
// Modifier-clicks (⌘-click) and middle-click are left alone
// — those go through the WebView's default handling, which
// on macOS opens in the OS browser anyway, so the user's
// "force-new-tab" intent is preserved.
//
// The shell:open IPC requires `shell:allow-open`, already in
// the default capability. If for some reason the IPC bridge
// isn't reachable (origin not in remote.urls, plugin
// mis-configured) we fall through to in-window navigation as
// a safety net so the user isn't left with a dead click.
var INTERNAL_HOSTS = ['internal.automators.com', 'auth.automators.com'];
function isInternalHost(href) {
try {
return INTERNAL_HOSTS.indexOf(new URL(href, window.location.href).hostname) !== -1;
} catch (e) {
return true; // unparseable URL → safest is in-window
}
}
function openExternal(href) {
if (window.__TAURI_INTERNALS__ && window.__TAURI_INTERNALS__.invoke) {
try {
window.__TAURI_INTERNALS__.invoke('plugin:shell|open', { path: href });
return true;
} catch (e) {
return false;
}
}
return false;
Comment on lines +164 to +173
}
window.addEventListener('click', function(e) {
var a = e.target && e.target.closest && e.target.closest('a[target="_blank"]');
if (!a || !a.href) return;
Expand All @@ -145,13 +180,24 @@ const SHELL_CSS_INJECT: &str = r#"
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; // modifier-clicks
e.preventDefault();
e.stopPropagation();
window.location.href = a.href;
if (isInternalHost(a.href)) {
window.location.href = a.href;
} else if (!openExternal(a.href)) {
window.location.href = a.href; // safety-net fallback
}
}, true);

var origOpen = window.open;
window.open = function(url) {
if (typeof url === 'string' && url) {
window.location.href = url;
if (isInternalHost(url)) {
window.location.href = url;
return null;
}
if (openExternal(url)) {
return null;
}
window.location.href = url; // safety-net fallback
return null;
Comment on lines 190 to 201
}
return origOpen.apply(window, arguments);
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Automators Internal",
"version": "0.1.7",
"version": "0.1.8",
"identifier": "com.automators.internal",
"build": {
"frontendDist": "../dist"
Expand Down
Loading