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.

Ringkasan

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.

Konfigurasi

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)
}

Protocol Handler

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)
    }
})

Struktur URL

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

Registrasi Platform

Protokol kustom didaftarkan secara berbeda di setiap platform.

Installer NSIS Windows

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

Registrasi Otomatis

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!

Cara Kerjanya

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.

Registry Manual (Lanjutan)

Jika Anda memerlukan registrasi manual (di luar NSIS):

@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

Pengujian

Uji registrasi protokol Anda:

PowerShell
# Buka URL protokol dari PowerShell
Start-Process "myapp://test/action"

# Atau dari command prompt
start myapp://test/action

Paket MSIX Windows

Protokol kustom juga secara otomatis didaftarkan saat menggunakan packaging MSIX.

Registrasi Otomatis

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>
  1. 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.

Contoh Lengkap

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,
    })
}

Integrasi Frontend

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}`)
    }
})

Pertimbangan Keamanan

Validasi Semua Input

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
}

Cegah Serangan Injeksi

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)
    }
}

Pengujian

Pengujian Manual

Uji protocol handler selama pengembangan:

Windows:

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

macOS:

bash
open "myapp://test/action?id=123"

Linux:

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

Pengujian HTML

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>

Pemecahan Masalah

Protokol Tidak Terdaftar

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

Aplikasi Tidak Meluncur

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

Praktik Terbaik

✅ Lakukan

  • 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

  • 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

Langkah Selanjutnya


Ada pertanyaan? Tanyakan di Discord atau lihat contoh.

Edit page

Last updated: