app: add Apps deep link (#18497)

This commit is contained in:
Eva H
2026-09-16 18:18:29 -07:00
committed by GitHub
parent 6b8bef2caa
commit 5ed8dde3a7
5 changed files with 72 additions and 12 deletions
+18 -11
View File
@@ -505,44 +505,51 @@ func openInBrowser(url string) {
}
// parseURLScheme parses an ollama:// URL and validates it
// Supports: ollama:// (open app) and ollama://connect (OAuth)
func parseURLScheme(urlSchemeRequest string) (isConnect bool, err error) {
// Supports: ollama:// (open app), ollama://apps, and ollama://connect (OAuth).
func parseURLScheme(urlSchemeRequest string) (action string, err error) {
parsedURL, err := url.Parse(urlSchemeRequest)
if err != nil {
return false, fmt.Errorf("invalid URL: %w", err)
return "", fmt.Errorf("invalid URL: %w", err)
}
// Check if this is a connect URL
if parsedURL.Host == "connect" || strings.TrimPrefix(parsedURL.Path, "/") == "connect" {
return true, nil
return "connect", nil
}
if parsedURL.Host == "apps" || strings.TrimPrefix(parsedURL.Path, "/") == "apps" {
return "apps", nil
}
// Allow bare ollama:// or ollama:/// to open the app
if (parsedURL.Host == "" && parsedURL.Path == "") || parsedURL.Path == "/" {
return false, nil
return "", nil
}
return false, fmt.Errorf("unsupported ollama:// URL path: %s", urlSchemeRequest)
return "", fmt.Errorf("unsupported ollama:// URL path: %s", urlSchemeRequest)
}
// handleURLSchemeInCurrentInstance processes URL scheme requests in the current instance
func handleURLSchemeInCurrentInstance(urlSchemeRequest string) {
err := dispatchURLSchemeRequest(urlSchemeRequest, handleConnectURLScheme, func() {
openUI("/")
})
}, openAppsUI)
if err != nil {
slog.Error("failed to parse URL scheme request", "url", urlSchemeRequest, "error", err)
}
}
func dispatchURLSchemeRequest(urlSchemeRequest string, connect, open func()) error {
isConnect, err := parseURLScheme(urlSchemeRequest)
func dispatchURLSchemeRequest(urlSchemeRequest string, connect, open, apps func()) error {
action, err := parseURLScheme(urlSchemeRequest)
if err != nil {
return err
}
if isConnect {
switch action {
case "connect":
connect()
} else {
case "apps":
apps()
default:
open()
}
return nil
+6
View File
@@ -152,6 +152,12 @@ func openUI(path string) {
StartUI(p)
}
func openAppsUI() {
p := C.CString("/connect")
defer C.free(unsafe.Pointer(p))
C.uiRequest(p)
}
//export StopUI
func StopUI() {
wv.Terminate()
+2
View File
@@ -441,6 +441,8 @@ static NSImage *ollamaApplicationIcon(void) {
if (path && ([path isEqualToString:@"/connect"] || [url.host isEqualToString:@"connect"])) {
// Special case: handle connect by opening browser instead of app
handleConnectURL();
} else if (path && ([path isEqualToString:@"/apps"] || [url.host isEqualToString:@"apps"])) {
[self appsUI];
} else {
[self openUI];
}
+42 -1
View File
@@ -48,21 +48,30 @@ func TestDispatchURLSchemeRequest(t *testing.T) {
request string
wantConnect bool
wantOpen bool
wantApps bool
wantErr bool
}{
{name: "bare URL opens app", request: "ollama://", wantOpen: true},
{name: "root URL opens app", request: "ollama:///", wantOpen: true},
{name: "apps URL opens Apps", request: "ollama://apps", wantApps: true},
{name: "apps path opens Apps", request: "ollama:///apps", wantApps: true},
{name: "apps trailing slash opens Apps", request: "ollama://apps/", wantApps: true},
{name: "connect URL starts connection", request: "ollama://connect", wantConnect: true},
{name: "connect path starts connection", request: "ollama:///connect", wantConnect: true},
{name: "unsupported URL", request: "ollama://unsupported", wantErr: true},
{name: "invalid URL", request: "ollama://%", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
connected := false
opened := false
openedApps := false
err := dispatchURLSchemeRequest(
tt.request,
func() { connected = true },
func() { opened = true },
func() { openedApps = true },
)
if (err != nil) != tt.wantErr {
t.Fatalf("dispatchURLSchemeRequest() error = %v, wantErr %v", err, tt.wantErr)
@@ -73,6 +82,9 @@ func TestDispatchURLSchemeRequest(t *testing.T) {
if opened != tt.wantOpen {
t.Errorf("open called = %v, want %v", opened, tt.wantOpen)
}
if openedApps != tt.wantApps {
t.Errorf("open Apps called = %v, want %v", openedApps, tt.wantApps)
}
})
}
}
@@ -90,7 +102,12 @@ func TestRunInitialWindowsUIWithBareURL(t *testing.T) {
func() { hiddenCalls++ },
func(request string) {
urlCalls++
if err := dispatchURLSchemeRequest(request, func() {}, func() { openCalls++ }); err != nil {
err := dispatchURLSchemeRequest(request,
func() { t.Fatal("unexpected sign-in") },
func() { openCalls++ },
func() { t.Fatal("unexpected Apps navigation") },
)
if err != nil {
t.Fatalf("dispatchURLSchemeRequest() error = %v", err)
}
},
@@ -113,6 +130,30 @@ func TestRunInitialWindowsUIWithBareURL(t *testing.T) {
}
}
func TestRunInitialWindowsUIWithAppsURL(t *testing.T) {
appsCalls := 0
runInitialWindowsUI(
false,
true,
"ollama://apps",
func() { t.Fatal("unexpected hidden startup") },
func(request string) {
err := dispatchURLSchemeRequest(request,
func() { t.Fatal("unexpected sign-in") },
func() { t.Fatal("unexpected home navigation") },
func() { appsCalls++ },
)
if err != nil {
t.Fatalf("dispatchURLSchemeRequest() error = %v", err)
}
},
func(string) { t.Fatal("unexpected onboarding") },
)
if appsCalls != 1 {
t.Fatalf("Apps opened %d times, want 1", appsCalls)
}
}
func TestRunInitialWindowsUIRoutesInteractiveLaunch(t *testing.T) {
for _, tt := range []struct {
name string
+4
View File
@@ -107,6 +107,10 @@ func openUI(path string) {
wv.Run(path)
}
func openAppsUI() {
wv.Run("/connect")
}
func (*appCallbacks) UITerminate() {
wv.Terminate()
}