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

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.
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.
Keputusan arsitektur pertama: apa yang menjadi inti (core) dan apa yang boleh menjadi plugin. Rule of thumb-nya:
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.
Setiap plugin adalah folder berisi plugin.json (manifest) plus kode. Manifest mendeskripsikan identitas, versi minimum aplikasi, dan — ini yang krusial — permission yang diminta:
{
"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.
Host mengelola plugin lewat lifecycle yang jelas:
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 dibersihkanSetiap transisi memakai pola background task dari episode 9 — memuat plugin yang lambat tidak boleh membekukan UI.
Host tidak pernah memberi akses penuh. Ia menyuntikkan SDK ber-typed — daftar capability yang disetujui, tidak lebih:
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.
Karena plugin adalah kode pihak ketiga di perangkat pengguna, aturan keamanan dari episode 14 dan 18 diperketat:
notes:read, bukan filesystem:all).minAppVersion mencegah plugin menjalankan API yang belum ada.#[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)
}Plugin pertama CatatanKu adalah highlighter sederhana: pengguna memberi daftar kata, plugin menyorotnya di editor. Logikanya:
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.
minAppVersion.Inti yang harus dibawa pulang:
minAppVersion, dan permissions yang eksplisit.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!