|
| 1 | +//go:build darwin |
| 2 | + |
| 3 | +package urlScheme |
| 4 | + |
| 5 | +import ( |
| 6 | + "fmt" |
| 7 | + "os" |
| 8 | + "os/exec" |
| 9 | + "path/filepath" |
| 10 | + "strings" |
| 11 | +) |
| 12 | + |
| 13 | +func RegisterURLScheme(options UrlSchemeRegisterOption) error { |
| 14 | + // On macOS, URL schemes are typically registered through: |
| 15 | + // 1. Creating an app bundle with Info.plist containing URL scheme registration |
| 16 | + // 2. Using Launch Services to register the scheme handler |
| 17 | + |
| 18 | + // For command-line applications, we'll create a minimal app bundle |
| 19 | + // and register it with the system |
| 20 | + |
| 21 | + homeDir, err := os.UserHomeDir() |
| 22 | + if err != nil { |
| 23 | + return fmt.Errorf("failed to get user home directory: %w", err) |
| 24 | + } |
| 25 | + |
| 26 | + // Create app bundle structure in ~/Applications/ |
| 27 | + appName := options.AppName + ".app" |
| 28 | + appBundlePath := filepath.Join(homeDir, "Applications", appName) |
| 29 | + contentsPath := filepath.Join(appBundlePath, "Contents") |
| 30 | + macOSPath := filepath.Join(contentsPath, "MacOS") |
| 31 | + |
| 32 | + // Create directories |
| 33 | + if err := os.MkdirAll(macOSPath, 0755); err != nil { |
| 34 | + return fmt.Errorf("failed to create app bundle directories: %w", err) |
| 35 | + } |
| 36 | + |
| 37 | + // Create Info.plist with URL scheme registration |
| 38 | + infoPlistPath := filepath.Join(contentsPath, "Info.plist") |
| 39 | + infoPlistContent := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?> |
| 40 | +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 41 | +<plist version="1.0"> |
| 42 | +<dict> |
| 43 | + <key>CFBundleExecutable</key> |
| 44 | + <string>%s</string> |
| 45 | + <key>CFBundleIdentifier</key> |
| 46 | + <string>com.%s.%s</string> |
| 47 | + <key>CFBundleName</key> |
| 48 | + <string>%s</string> |
| 49 | + <key>CFBundleVersion</key> |
| 50 | + <string>1.0</string> |
| 51 | + <key>CFBundleShortVersionString</key> |
| 52 | + <string>1.0</string> |
| 53 | + <key>CFBundlePackageType</key> |
| 54 | + <string>APPL</string> |
| 55 | + <key>CFBundleURLTypes</key> |
| 56 | + <array> |
| 57 | + <dict> |
| 58 | + <key>CFBundleURLName</key> |
| 59 | + <string>%s URL Handler</string> |
| 60 | + <key>CFBundleURLSchemes</key> |
| 61 | + <array> |
| 62 | + <string>%s</string> |
| 63 | + </array> |
| 64 | + </dict> |
| 65 | + </array> |
| 66 | +</dict> |
| 67 | +</plist>`, |
| 68 | + options.AppName, |
| 69 | + strings.ToLower(options.AppName), |
| 70 | + strings.ToLower(options.AppName), |
| 71 | + options.AppName, |
| 72 | + options.AppName, |
| 73 | + options.Scheme) |
| 74 | + |
| 75 | + if err := os.WriteFile(infoPlistPath, []byte(infoPlistContent), 0644); err != nil { |
| 76 | + return fmt.Errorf("failed to write Info.plist: %w", err) |
| 77 | + } |
| 78 | + |
| 79 | + // Create executable wrapper script in the app bundle |
| 80 | + executablePath := filepath.Join(macOSPath, options.AppName) |
| 81 | + wrapperScript := fmt.Sprintf(`#!/bin/bash |
| 82 | +# URL scheme handler wrapper for %s |
| 83 | +exec "%s" url "$@" |
| 84 | +`, options.AppName, options.ExecutablePath) |
| 85 | + |
| 86 | + if err := os.WriteFile(executablePath, []byte(wrapperScript), 0755); err != nil { |
| 87 | + return fmt.Errorf("failed to write wrapper executable: %w", err) |
| 88 | + } |
| 89 | + |
| 90 | + // Register the app bundle with Launch Services |
| 91 | + // This makes the system aware of the URL scheme handler |
| 92 | + cmd := exec.Command("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", |
| 93 | + "-f", appBundlePath) |
| 94 | + |
| 95 | + if output, err := cmd.CombinedOutput(); err != nil { |
| 96 | + return fmt.Errorf("failed to register app bundle with Launch Services: %w\nOutput: %s", err, output) |
| 97 | + } |
| 98 | + |
| 99 | + // Optional: Set as default handler for the scheme |
| 100 | + // This requires the user to approve the change, so we'll skip it for now |
| 101 | + // cmd = exec.Command("open", "-a", appBundlePath, fmt.Sprintf("%s://test", options.Scheme)) |
| 102 | + |
| 103 | + return nil |
| 104 | +} |
0 commit comments