Lewati ke konten

Protokol URL Kustom

Protokol URL kustom (juga disebut skema URL) memungkinkan aplikasi Anda diluncurkan saat pengguna mengklik tautan dengan protokol kustom Anda, seperti myapp://action atau myapp://open/document.

Protokol kustom memungkinkan:

  • Deep linking: Meluncurkan aplikasi Anda dengan data spesifik
  • Integrasi browser: Menangani tautan dari halaman web
  • Tautan email: Membuka aplikasi Anda dari klien email
  • Komunikasi antar aplikasi: Meluncurkan dari aplikasi lain

Contoh: myapp://open/document?id=123 meluncurkan aplikasi Anda dan membuka dokumen 123.

Definisikan protokol kustom di opsi aplikasi Anda:

Protokol kustom dideklarasikan di build/config.yml (yang dikonsumsi oleh platform packager — macro NSIS di Windows, manifest MSIX, CFBundleURLTypes macOS, .desktop/xdg-mime Linux — saat waktu package). Tidak ada tipe application.Protocol dan tidak ada field Protocols pada application.Options.

build/config.yml
protocols:
- scheme: myapp
description: "My Application Protocol"

Di kode Go, dengarkan peluncuran dengan URL melalui event ApplicationLaunchedWithUrl:

package main
import (
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
func main() {
app := application.New(application.Options{
Name: "My Application",
Description: "My awesome application",
})
app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) {
handleCustomURL(e.Context().URL())
})
app.Run()
}
func handleCustomURL(url string) {
// Parse dan tangani URL kustom
// Contoh: myapp://open/document?id=123
println("Received URL:", url)
}

Dengarkan event protokol untuk menangani URL masuk:

app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) {
url := e.Context().URL()
// Parse URL
parsedURL, err := parseCustomURL(url)
if err != nil {
app.Logger.Error("Failed to parse URL:", err)
return
}
// Tangani aksi berbeda
switch parsedURL.Action {
case "open":
openDocument(parsedURL.DocumentID)
case "settings":
showSettings()
case "user":
showUser Profile(parsedURL.UserID)
default:
app.Logger.Warn("Unknown action:", parsedURL.Action)
}
})

Rancang struktur URL yang jelas dan hierarkis:

myapp://action/resource?param=value
Contoh:
myapp://open/document?id=123
myapp://settings/theme?mode=dark
myapp://user/profile?username=john

Praktik terbaik:

  • Gunakan nama skema huruf kecil
  • Buat skema pendek dan mudah diingat
  • Gunakan path hierarkis untuk resource
  • Sertakan parameter query untuk data opsional
  • Encode karakter khusus dalam URL

Protokol kustom didaftarkan secara berbeda di setiap platform.

Wails v3 secara otomatis mendaftarkan protokol kustom saat menggunakan installer NSIS.

Saat Anda membangun aplikasi dengan wails3 build, installer NSIS:

  1. Secara otomatis mendaftarkan semua protokol yang dideklarasikan di build/config.yml di bawah kunci protocols:
  2. Mengasosiasikan protokol dengan executable aplikasi Anda
  3. Menyiapkan entri registry yang benar
  4. Menghapus asosiasi protokol saat uninstall

Tidak diperlukan konfigurasi tambahan!

Template NSIS menyertakan macro bawaan:

  • wails.associateCustomProtocols - Mendaftarkan protokol saat instalasi
  • wails.unassociateCustomProtocols - Menghapus protokol saat uninstall

Macro ini secara otomatis dipanggil berdasarkan konfigurasi Protocols Anda.

Jika Anda memerlukan registrasi manual (di luar NSIS):

Terminal window
@echo off
REM Register custom protocol
REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /ve /d "URL:My Application Protocol" /f
REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /v "URL Protocol" /t REG_SZ /d "" /f
REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp\shell\open\command" /ve /d "\"%1\"" /f

Uji registrasi protokol Anda:

Terminal window
# Buka URL protokol dari PowerShell
Start-Process "myapp://test/action"
# Atau dari command prompt
start myapp://test/action

Protokol kustom juga secara otomatis didaftarkan saat menggunakan packaging MSIX.

Saat Anda membangun aplikasi dengan MSIX, manifest secara otomatis menyertakan registrasi protokol dari konfigurasi protokol build/config.yml Anda.

Manifest yang di-generate menyertakan:

<uap:Extension Category="windows.protocol">
<uap:Protocol Name="myapp">
<uap:DisplayName>My Application Protocol</uap:DisplayName>
</uap:Protocol>
</uap:Extension>

Windows mendukung Web-to-App linking, yang bekerja serupa dengan Universal Links di macOS. Saat mendeploy aplikasi Anda sebagai paket MSIX, Anda dapat mengaktifkan tautan HTTPS untuk meluncurkan aplikasi Anda secara langsung.

Untuk mengaktifkan Web-to-App linking, ikuti panduan Microsoft tentang web-to-app linking. Anda perlu:

  1. Tambahkan App URI Handler secara manual ke manifest MSIX Anda (build/windows/msix/app_manifest.xml):

    <uap3:Extension Category="windows.appUriHandler">
    <uap3:AppUriHandler>
    <uap3:Host Name="myawesomeapp.com"/>
    </uap3:AppUriHandler>
    </uap3:Extension>
  2. Konfigurasi windows-app-web-link di website Anda: Host file windows-app-web-link di https://myawesomeapp.com/.well-known/windows-app-web-link. File ini harus berisi informasi paket aplikasi Anda dan path yang ditangani.

Saat tautan Web-to-App meluncurkan aplikasi Anda, Anda akan menerima event ApplicationLaunchedWithUrl yang sama seperti dengan skema protokol kustom.

Berikut contoh lengkap yang menangani beberapa aksi protokol:

package main
import (
"fmt"
"net/url"
"strings"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
type App struct {
app *application.App
window *application.WebviewWindow
}
func main() {
// Registrasi protokol ada di build/config.yml (platform packager
// mengonsumsinya); kode aplikasi hanya mendengarkan event peluncuran.
app := application.New(application.Options{
Name: "DeepLink Demo",
Description: "Custom protocol demonstration",
})
myApp := &App{app: app}
myApp.setup()
app.Run()
}
func (a *App) setup() {
// Buat window
a.window = a.app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "DeepLink Demo",
Width: 800,
Height: 600,
URL: "http://wails.localhost/",
})
// Tangani URL protokol kustom
a.app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) {
a.handleDeepLink(e.Context().URL())
})
}
func (a *App) handleDeepLink(rawURL string) {
// Parse URL
parsedURL, err := url.Parse(rawURL)
if err != nil {
a.app.Logger.Error("Failed to parse URL:", err)
return
}
// Bawa window ke depan
a.window.Show()
a.window.Focus()
// Ekstrak path dan query
path := strings.Trim(parsedURL.Path, "/")
query := parsedURL.Query()
// Tangani aksi berbeda
parts := strings.Split(path, "/")
if len(parts) == 0 {
return
}
action := parts[0]
switch action {
case "open":
if len(parts) >= 2 {
resource := parts[1]
id := query.Get("id")
a.openResource(resource, id)
}
case "settings":
section := ""
if len(parts) >= 2 {
section = parts[1]
}
a.openSettings(section)
case "user":
if len(parts) >= 2 {
username := parts[1]
a.openUserProfile(username)
}
default:
a.app.Logger.Warn("Unknown action:", action)
}
}
func (a *App) openResource(resourceType, id string) {
fmt.Printf("Opening %s with ID: %s\n", resourceType, id)
// Emit event ke frontend
a.app.Event.Emit("navigate", map[string]string{
"type": resourceType,
"id": id,
})
}
func (a *App) openSettings(section string) {
fmt.Printf("Opening settings section: %s\n", section)
a.app.Event.Emit("navigate", map[string]string{
"page": "settings",
"section": section,
})
}
func (a *App) openUserProfile(username string) {
fmt.Printf("Opening user profile: %s\n", username)
a.app.Event.Emit("navigate", map[string]string{
"page": "user",
"user": username,
})
}

Tangani event navigasi di frontend Anda:

import { Events } from '@wailsio/runtime'
// Dengarkan event navigasi dari protocol handler
Events.On('navigate', (event) => {
const { type, id, page, section, user } = event.data
if (type === 'document') {
// Buka dokumen dengan ID
router.push(`/document/${id}`)
} else if (page === 'settings') {
// Buka pengaturan
router.push(`/settings/${section}`)
} else if (page === 'user') {
// Buka profil pengguna
router.push(`/user/${user}`)
}
})

Selalu validasi dan sanitasi URL dari sumber eksternal:

func (a *App) handleDeepLink(rawURL string) {
// Parse URL
parsedURL, err := url.Parse(rawURL)
if err != nil {
a.app.Logger.Error("Invalid URL:", err)
return
}
// Validasi skema
if parsedURL.Scheme != "myapp" {
a.app.Logger.Warn("Invalid scheme:", parsedURL.Scheme)
return
}
// Validasi path
path := strings.Trim(parsedURL.Path, "/")
if !isValidPath(path) {
a.app.Logger.Warn("Invalid path:", path)
return
}
// Sanitasi parameter
params := sanitizeQueryParams(parsedURL.Query())
// Proses URL yang sudah divalidasi
a.processDeepLink(path, params)
}
func isValidPath(path string) bool {
// Hanya izinkan alfanumerik dan forward slash
validPath := regexp.MustCompile(`^[a-zA-Z0-9/]+$`)
return validPath.MatchString(path)
}
func sanitizeQueryParams(query url.Values) map[string]string {
sanitized := make(map[string]string)
for key, values := range query {
if len(values) > 0 {
// Ambil nilai pertama dan sanitasi
sanitized[key] = sanitizeString(values[0])
}
}
return sanitized
}

Jangan pernah mengeksekusi URL secara langsung sebagai kode atau SQL:

// ❌ JANGAN: Eksekusi konten URL
func badHandler(url string) {
exec.Command("sh", "-c", url).Run() // BERBAHAYA!
}
// ✅ LAKUKAN: Parse dan validasi
func goodHandler(url string) {
parsed, _ := url.Parse(url)
action := parsed.Query().Get("action")
// Whitelist aksi yang diizinkan
allowed := map[string]bool{
"open": true,
"settings": true,
"help": true,
}
if allowed[action] {
handleAction(action)
}
}

Uji protocol handler selama pengembangan:

Windows:

Terminal window
Start-Process "myapp://test/action?id=123"

macOS:

Terminal window
open "myapp://test/action?id=123"

Linux:

Terminal window
xdg-open "myapp://test/action?id=123"

Buat halaman HTML untuk pengujian:

<!DOCTYPE html>
<html>
<head>
<title>Protocol Test</title>
</head>
<body>
<h1>Custom Protocol Test Links</h1>
<ul>
<li><a href="myapp://open/document?id=123">Open Document 123</a></li>
<li><a href="myapp://settings/theme?mode=dark">Dark Mode Settings</a></li>
<li><a href="myapp://user/profile?username=john">User Profile</a></li>
</ul>
</body>
</html>

Windows:

  • Periksa registry: HKEY_CURRENT_USER\SOFTWARE\Classes\<scheme>
  • Instal ulang dengan installer NSIS
  • Verifikasi installer dijalankan dengan izin yang benar

macOS:

  • Build ulang aplikasi dengan wails3 build
  • Periksa Info.plist di app bundle: MyApp.app/Contents/Info.plist
  • Reset Launch Services: /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill

Linux:

  • Periksa file desktop: ~/.local/share/applications/myapp.desktop
  • Perbarui database: update-desktop-database ~/.local/share/applications/
  • Verifikasi handler: xdg-mime query default x-scheme-handler/myapp

Periksa log:

app := application.New(application.Options{
LogLevel: slog.LevelDebug, // requires `import "log/slog"`
// ...
})

Masalah umum:

  • Aplikasi tidak terinstal di lokasi yang diharapkan
  • Path executable di registrasi tidak cocok dengan lokasi aktual
  • Masalah izin
  • Gunakan nama skema yang deskriptif - mycompany-myapp alih-alih mca
  • Validasi semua input - Jangan pernah percaya URL dari sumber eksternal
  • Tangani error dengan baik - Log URL yang tidak valid, jangan crash
  • Berikan umpan balik ke pengguna - Tampilkan aksi apa yang dipicu
  • Uji di semua platform - Penanganan protokol bervariasi
  • Dokumentasikan struktur URL Anda - Bantu pengguna dan integrator
  • Jangan gunakan nama skema umum - Hindari http, file, app, dll.
  • Jangan eksekusi URL sebagai kode - Risiko keamanan besar
  • Jangan ekspos operasi sensitif - Wajibkan konfirmasi untuk aksi destruktif
  • Jangan asumsikan protokol bekerja di mana-mana - Siapkan mekanisme fallback
  • Jangan lupa URL encoding - Tangani karakter khusus dengan benar

Ada pertanyaan? Tanyakan di Discord atau lihat contoh.