Belajar Desktop Developer - Plugins & Extensibility
Episode 23 of 28

Belajar Desktop Developer - Plugins & Extensibility

Membuka CatatanKu untuk ekosistem: boundary antara inti dan ekstensi, manifest sebagai kontrak, lifecycle plugin, SDK ber-typed yang dikontrol, serta keamanan plugin agar ekosistem tidak merusak aplikasi

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Di episode 22 kita menutup lapisan data dengan local-first & sync — CatatanKu kini punya fondasi yang sehat. Namun aplikasi hebat tidak berhenti di fitur internal: ia membuka dirinya untuk ekosistem. Obsidian tumbuh besar karena plugin-nya, VS Code menjadi raksasa karena extension marketplace-nya, dan Joplin tetap relevan karena API plugin yang rapi.

Episode ini mengubah CatatanKu dari "aplikasi" menjadi "platform": arsitektur plugin, manifest sebagai kontrak, lifecycle, SDK yang dikontrol, dan — yang paling penting — keamanan plugin agar ekosistem tidak merusak inti aplikasi.

Dari Aplikasi Menjadi Platform

Analogi: Rumah dengan Pintu

Bayangkan CatatanKu sebagai rumah. Plugin adalah penghuni tambahan yang hanya boleh masuk lewat pintu yang kalian sediakan — bukan menembus tembok. Kalian memegang kunci setiap pintu, menentukan kamar mana yang boleh dimasuki, dan bisa mengusir penghuni yang melanggar aturan.

Tanpa desain ini, plugin berubah menjadi malware: kode pihak ketiga yang menjalankan akses penuh ke file system, jaringan, dan database pengguna. Maka langkah pertama bukan menulis kode plugin, tapi menggambar boundary.

Arsitektur Plugin

Boundary Inti vs Ekstensi

Keputusan arsitektur pertama: apa yang menjadi inti (core) dan apa yang boleh menjadi plugin. Rule of thumb-nya:

  • Fitur yang dipakai hampir semua pengguna dan menyentuh stabilitas inti (editor, pencarian, sinkronisasi) → core.
  • Fitur niche yang berdiri sendiri dan bisa dinonaktifkan (exporter, tema, highlighter) → plugin.
100%

Plugin hanya berkomunikasi dengan host lewat SDK — tidak pernah mengakses komponen internal secara langsung. Ini pola Inversion of Dependency yang sama seperti abstraksi storage di episode 8: antarmuka dipegang oleh host, implementasi disuntikkan.

Manifest: Kontrak antara Host dan Plugin

Setiap plugin adalah folder berisi plugin.json (manifest) plus kode. Manifest mendeskripsikan identitas, versi minimum aplikasi, dan — ini yang krusial — permission yang diminta:

plugin.json — manifest plugin CatatanKu
{
  "name": "catatanku-highlighter",
  "version": "1.2.0",
  "description": "Sorot kata kunci di catatan",
  "minAppVersion": "1.0.0",
  "permissions": [
    "notes:read",
    "search:run"
  ],
  "contributes": {
    "commands": [
      {
        "id": "highlighter.toggle",
        "title": "Toggle Highlight"
      }
    ]
  }
}

Manifest adalah kontrak. Tidak ada akses implisit: apa pun yang tidak tercantum di permissions tidak bisa diakses plugin. minAppVersion menjaga plugin tidak berjalan di host yang belum punya API yang dibutuhkannya — mencegah runtime error misterius di sisi pengguna.

Lifecycle Plugin

Host mengelola plugin lewat lifecycle yang jelas:

Lifecycle plugin CatatanKu
install   → folder plugin disalin, manifest divalidasi
enable    → host membaca manifest, permission di-resolve
activate  → kode plugin dimuat, SDK disuntikkan, commands didaftarkan
run       → plugin merespons event / commands yang diregistrasi
disable   → commands dicabut, state disimpan, SDK dilepas
uninstall → folder dihapus, state dibersihkan

Setiap transisi memakai pola background task dari episode 9 — memuat plugin yang lambat tidak boleh membekukan UI.

SDK: Permukaan API yang Dikontrol

Host tidak pernah memberi akses penuh. Ia menyuntikkan SDK ber-typed — daftar capability yang disetujui, tidak lebih:

SDK host yang disuntikkan ke plugin
export interface CatatanKuSDK {
  notes: {
    read(id: string): Promise<Note>;
    update(id: string, patch: Partial<Note>): Promise<void>;
  };
  events: {
    on<T>(event: string, handler: (payload: T) => void): () => void;
  };
  commands: {
    register(def: CommandDef): void;
  };
  settings: {
    get(key: string): unknown;
    set(key: string, value: unknown): Promise<void>;
  };
}

Plugin berjalan di sandbox dan hanya bisa menyentuh apa yang ada di SDK. Di Tauri, bagian backend plugin adalah crate Rust terpisah yang di-resolve lewat permissions; di Electron, plugin dirender dalam konteks webview yang dibatasi — keduanya memperpanjang pola secure IPC dari episode 14 dan 18.

Note

SDK adalah API publik kalian — begitu dirilis, sulit ditarik kembali. Versikan SDK (seperti minAppVersion) dan tandai fungsi yang masih eksperimental sebelum API itu dikonsumsi banyak plugin. API yang berubah seenaknya adalah cara tercepat membunuh ekosistem.

Keamanan Plugin

Karena plugin adalah kode pihak ketiga di perangkat pengguna, aturan keamanan dari episode 14 dan 18 diperketat:

  • Least privilege — permission serinci mungkin (notes:read, bukan filesystem:all).
  • Trusted registry & signature — plugin ditandatangani; host memverifikasi tanda tangan sebelum enable, mengikuti pola signing episode 12.
  • Version pinningminAppVersion mencegah plugin menjalankan API yang belum ada.
  • Resource quota — plugin dibatasi CPU/memori/jaringan (koneksi dari episode 16 dan 18); plugin yang melanggar di-disable otomatis.
  • Jangan pernah download & eksekusi kode saat runtime — plugin harus diinstal dari folder yang tervalidasi, bukan dari string yang didownload.
Tauri: verifikasi tanda tangan plugin sebelum activate
#[tauri::command]
fn enable_plugin(app: AppHandle, plugin_path: PathBuf) -> Result<(), String> {
    let manifest = read_manifest(&plugin_path)?;
    verify_signature(&plugin_path, &public_key)?;   // episode 12
    check_min_version(&manifest)?;                  // minAppVersion
    let granted = resolve_permissions(&manifest)?;  // least privilege
    activate_plugin(app, plugin_path, granted)
}

Praktik: Plugin "Sorot Kata Kunci"

Plugin pertama CatatanKu adalah highlighter sederhana: pengguna memberi daftar kata, plugin menyorotnya di editor. Logikanya:

main.ts — inti plugin highlighter
import type { CatatanKuSDK } from "@catatanku/sdk";
 
export function activate(sdk: CatatanKuSDK) {
  sdk.commands.register({
    id: "highlighter.toggle",
    title: "Toggle Highlight",
    handler: () => {
      sdk.settings.get("highlighter.keywords") as string[];
    },
  });
}

Tanpa SDK yang ber-typed dan permission system, plugin ini akan menggoda kita memberi akses notes:read-write dan network:all — justru yang kita hindari. Permission paling sempit yang cukup selalu lebih baik.

Common Pitfalls

  • Mengeksekusi plugin di main process — satu plugin crash menjatuhkan seluruh aplikasi; jalankan di sandbox/isolate.
  • Akses file system penuh — plugin jahat bisa membaca semua catatan; selalu permission serinci mungkin.
  • API tanpa versi — perubahan SDK merusak semua plugin; versikan SDK dan hormati minAppVersion.
  • Tanpa resource quota — plugin rakus memori memperlambat seluruh aplikasi; ukur dan batasi (episode 16).
  • Mengunduh kode saat runtime — ini membuka pintu untuk supply-chain attack; plugin harus diinstal dan diverifikasi lebih dulu.

Penutup

Inti yang harus dibawa pulang:

  • Boundary inti vs ekstensi memisahkan kode stabil dari fitur niche; plugin hanya bicara via SDK.
  • Manifest adalah kontrak: versi, minAppVersion, dan permissions yang eksplisit.
  • Lifecycle (install → enable → activate → disable) memberi host kendali penuh atas plugin.
  • SDK ber-typed + sandbox + signing menjaga plugin tetap aman di perangkat pengguna — perpanjangan langsung pola keamanan episode 12, 14, dan 18.

Di episode 24 selanjutnya kita memperluas CatatanKu secara arsitektural: multi-window & multi-process — banyak jendela, isolasi proses antar window, dan komunikasi lintas window tanpa berbagi memori. Sampai jumpa di episode 24!

Belajar Desktop Developer - Plugins & Extensibility | Belajar Desktop Developer