Compare commits

...
Author SHA1 Message Date
adminandClaude Opus 4.5 045ab3ffca Fix OAuth deep link redirect closing before permission dialog
The auto-redirect with window.close() was closing the page before
users could accept the browser's permission dialog to open the app.

Changed to a button-based approach where the user clicks to return
to the app, giving them control over when the deep link triggers.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:33:06 -08:00
adminandClaude Opus 4.5 1a9faa1c64 Add golang.org/x/sys dependency for Windows registry access
The Windows installer uses golang.org/x/sys/windows/registry to register
the eagle0:// URL scheme. This adds the required dependency to go.mod,
go.sum, and MODULE.bazel.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:21:28 -08:00
adminandClaude Opus 4.5 b0d7842598 Register eagle0:// URL scheme on Windows for OAuth deep links
The Windows installer now registers the eagle0:// URL scheme in the
registry (HKEY_CURRENT_USER\Software\Classes\eagle0) pointing to the
game executable. This allows OAuth callbacks to redirect back to the
app automatically.

Also updated OAuthManager to check command line arguments for deep
links since Windows passes URL scheme launches as command line args.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:13:58 -08:00
11 changed files with 142 additions and 25 deletions
+1
View File
@@ -122,6 +122,7 @@ use_repo(
"com_github_webview_webview_go",
"org_golang_google_grpc",
"org_golang_google_protobuf",
"org_golang_x_sys",
)
#
+1
View File
@@ -12,6 +12,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
golang.org/x/sys v0.28.0
google.golang.org/grpc v1.68.0
google.golang.org/protobuf v1.36.3
)
+2
View File
@@ -43,6 +43,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -53,6 +53,21 @@ namespace Auth {
if (!string.IsNullOrEmpty(Application.absoluteURL)) {
OnDeepLinkActivated(Application.absoluteURL);
}
// On Windows, URL scheme launches pass the URL as a command line argument
CheckCommandLineForDeepLink();
}
private void CheckCommandLineForDeepLink() {
var args = System.Environment.GetCommandLineArgs();
// Args[0] is the executable path, check remaining args for eagle0:// URLs
for (int i = 1; i < args.Length; i++) {
if (args[i].StartsWith("eagle0://")) {
Debug.Log($"[OAuthManager] Found deep link in command line: {args[i]}");
OnDeepLinkActivated(args[i]);
break;
}
}
}
private void OnDeepLinkActivated(string url) {
@@ -336,16 +336,22 @@ func (s *OAuthService) HandleAppleCallback(w http.ResponseWriter, r *http.Reques
<html>
<head>
<title>Login Successful</title>
<meta http-equiv="refresh" content="0;url=eagle0://auth/complete">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
.container { background: white; padding: 40px; border-radius: 12px; max-width: 400px; margin: 0 auto; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; margin-bottom: 20px; }
.btn { display: inline-block; padding: 14px 28px; background: #007AFF; color: white; text-decoration: none; border-radius: 8px; font-size: 16px; font-weight: 500; }
.btn:hover { background: #0056b3; }
.hint { color: #666; font-size: 0.85em; margin-top: 24px; line-height: 1.5; }
</style>
</head>
<body>
<div class="container">
<h1>Login Successful!</h1>
<p>Returning to Eagle0...</p>
<p style="color: #666; font-size: 0.9em;">If you're not redirected automatically, you can close this window and return to the game.</p>
<script>
window.location.href = "eagle0://auth/complete";
setTimeout(function() { window.close(); }, 1000);
</script>
<p>Click the button below to return to Eagle0:</p>
<a href="eagle0://auth/complete" class="btn">Return to Eagle0</a>
<p class="hint">If your browser asks for permission to open Eagle0, click Allow.<br>You can close this tab after returning to the game.</p>
</div>
</body>
</html>`)
}
+13 -10
View File
@@ -270,24 +270,27 @@ func (s *OAuthService) HandleCallback(w http.ResponseWriter, r *http.Request) {
}
// Redirect to app via deep link (desktop/mobile client)
// The page also shows a fallback message in case the redirect doesn't work
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head>
<title>Login Successful</title>
<meta http-equiv="refresh" content="0;url=eagle0://auth/complete">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
.container { background: white; padding: 40px; border-radius: 12px; max-width: 400px; margin: 0 auto; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; margin-bottom: 20px; }
.btn { display: inline-block; padding: 14px 28px; background: #007AFF; color: white; text-decoration: none; border-radius: 8px; font-size: 16px; font-weight: 500; }
.btn:hover { background: #0056b3; }
.hint { color: #666; font-size: 0.85em; margin-top: 24px; line-height: 1.5; }
</style>
</head>
<body>
<div class="container">
<h1>Login Successful!</h1>
<p>Returning to Eagle0...</p>
<p style="color: #666; font-size: 0.9em;">If you're not redirected automatically, you can close this window and return to the game.</p>
<script>
// Try to redirect via deep link
window.location.href = "eagle0://auth/complete";
// Fallback: try to close window after a short delay
setTimeout(function() { window.close(); }, 1000);
</script>
<p>Click the button below to return to Eagle0:</p>
<a href="eagle0://auth/complete" class="btn">Return to Eagle0</a>
<p class="hint">If your browser asks for permission to open Eagle0, click Allow.<br>You can close this tab after returning to the game.</p>
</div>
</body>
</html>`)
}
@@ -197,16 +197,22 @@ func (s *OAuthService) HandleSteamCallback(w http.ResponseWriter, r *http.Reques
<html>
<head>
<title>Login Successful</title>
<meta http-equiv="refresh" content="0;url=eagle0://auth/complete">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
.container { background: white; padding: 40px; border-radius: 12px; max-width: 400px; margin: 0 auto; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; margin-bottom: 20px; }
.btn { display: inline-block; padding: 14px 28px; background: #007AFF; color: white; text-decoration: none; border-radius: 8px; font-size: 16px; font-weight: 500; }
.btn:hover { background: #0056b3; }
.hint { color: #666; font-size: 0.85em; margin-top: 24px; line-height: 1.5; }
</style>
</head>
<body>
<div class="container">
<h1>Login Successful!</h1>
<p>Returning to Eagle0...</p>
<p style="color: #666; font-size: 0.9em;">If you're not redirected automatically, you can close this window and return to the game.</p>
<script>
window.location.href = "eagle0://auth/complete";
setTimeout(function() { window.close(); }, 1000);
</script>
<p>Click the button below to return to Eagle0:</p>
<a href="eagle0://auth/complete" class="btn">Return to Eagle0</a>
<p class="hint">If your browser asks for permission to open Eagle0, click Allow.<br>You can close this tab after returning to the game.</p>
</div>
</body>
</html>`)
}
@@ -11,12 +11,15 @@ go_library(
"ui_webview_stub.go",
"ui_webview_windows.go",
"updater.go",
"urlscheme_stub.go",
"urlscheme_windows.go",
],
importpath = "github.com/nolen777/eagle0/src/main/go/net/eagle0/clients/win/installer",
visibility = ["//visibility:private"],
deps = select({
"@io_bazel_rules_go//go/platform:windows": [
"@com_github_webview_webview_go//:webview_go",
"@org_golang_x_sys//windows/registry",
],
"//conditions:default": [],
}),
@@ -50,6 +53,7 @@ genrule(
"ui_interface.go",
"ui_webview_windows.go",
"updater.go",
"urlscheme_windows.go",
"resource_windows_amd64.syso",
"@llvm_mingw//:all_files",
"@go_default_sdk//:files",
@@ -130,12 +134,17 @@ module github.com/nolen777/eagle0/src/main/go/net/eagle0/clients/win/installer
go 1.23
require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
require (
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
golang.org/x/sys v0.28.0
)
GOMOD
cat > go.sum << 'GOSUM'
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
GOSUM
# Copy source files from Bazel sandbox (exclude Go SDK and llvm files)
@@ -154,6 +154,12 @@ func runInstallerLogic(invitationCode string) error {
saveInvitationCode(invitationCode)
}
// Register eagle0:// URL scheme for OAuth deep links
if err := RegisterURLScheme(exeLocation); err != nil {
ui.ShowWarning(fmt.Sprintf("Failed to register URL scheme: %v", err))
// Continue anyway - URL scheme is nice to have but not critical
}
ui.UpdateStatus("Launching Eagle0...")
cmd := exec.Command(exeLocation)
if err := cmd.Start(); err != nil {
@@ -0,0 +1,8 @@
//go:build !windows
package main
// RegisterURLScheme is a no-op on non-Windows platforms
func RegisterURLScheme(exePath string) error {
return nil
}
@@ -0,0 +1,60 @@
//go:build windows
package main
import (
"fmt"
"golang.org/x/sys/windows/registry"
)
const urlScheme = "eagle0"
// RegisterURLScheme registers the eagle0:// URL scheme in the Windows registry.
// This allows the browser to open the Eagle0 app when navigating to eagle0:// URLs.
// The URL scheme points to the game executable, which will handle the deep link.
func RegisterURLScheme(exePath string) error {
// Create the URL scheme key under HKEY_CURRENT_USER (doesn't require admin)
// Structure:
// HKEY_CURRENT_USER\Software\Classes\eagle0
// (Default) = "URL:Eagle0 Protocol"
// URL Protocol = ""
// shell\open\command
// (Default) = "\"C:\path\to\eagle0.exe\" \"%1\""
keyPath := `Software\Classes\` + urlScheme
// Create or open the main key
key, _, err := registry.CreateKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS)
if err != nil {
return fmt.Errorf("failed to create registry key %s: %w", keyPath, err)
}
defer key.Close()
// Set the default value (description)
if err := key.SetStringValue("", "URL:Eagle0 Protocol"); err != nil {
return fmt.Errorf("failed to set default value: %w", err)
}
// Set URL Protocol (empty string indicates this is a URL protocol handler)
if err := key.SetStringValue("URL Protocol", ""); err != nil {
return fmt.Errorf("failed to set URL Protocol: %w", err)
}
// Create shell\open\command subkey
cmdKeyPath := keyPath + `\shell\open\command`
cmdKey, _, err := registry.CreateKey(registry.CURRENT_USER, cmdKeyPath, registry.ALL_ACCESS)
if err != nil {
return fmt.Errorf("failed to create registry key %s: %w", cmdKeyPath, err)
}
defer cmdKey.Close()
// Set the command to launch the game with the URL as argument
// The %1 will be replaced with the full URL (e.g., eagle0://auth/complete)
command := fmt.Sprintf(`"%s" "%%1"`, exePath)
if err := cmdKey.SetStringValue("", command); err != nil {
return fmt.Errorf("failed to set command: %w", err)
}
return nil
}