Hayalimdeki Çok Platformlu Nostr Masaüstü İstemcisini Nasıl Geliştirdim?
Nostr protokolü ile çalışan, hızlı ve güvenli bir masaüstü istemcisi geliştirme sürecimi, mimari kararlarımı ve tüm teknik detayları bu rehberde keşfedin.
Nostr Protokolü Nedir ve Neden Yeni Bir Masaüstü İstemcisine İhtiyaç Duydum?
Geleneksel sosyal medya platformları, verilerimizi merkezi sunucularda toplayarak sansür ve veri mülkiyeti sorunlarını beraberinde getirmektedir. Ancak merkeziyetsiz sosyal ağlar bu tabloyu tamamen değiştiriyor. Bu alanda öne çıkan Nostr (Notes and Other Stuff Transmitted by Relays), açık kaynaklı ve sansüre dayanıklı bir iletişim protokolüdür. Kullanıcılar, kriptografik anahtar çiftleri (açık ve gizli anahtar) aracılığıyla kimliklerini doğrular. Böylece herhangi bir merkezi otoriteye bağlı kalmadan mesaj iletimi gerçekleştirirler.
Piyasada birden fazla web ve mobil Nostr istemcisi (client) bulunmasına rağmen, masaüstü tarafında ciddi bir performans ve deneyim eksikliği mevcuttu. Web istemcileri taranıcı sekmesine sıkıştığı için sistem kaynaklarını verimsiz kullanıyordu. Bununla birlikte mobil uygulamalar da klavye odaklı, çoklu pencere destekleyen ve performanslı bir masaüstü deneyimi sunamıyordu. Tam da bu noktada, kendi ihtiyaçlarımı karşılayacak çok platformlu (cross-platform) bir masaüstü istemcisi inşa etmeye karar verdim.
Projenin temel amacı; düşük bellek tüketimi sağlayan, arka planda röle (relay) bağlantılarını koparmayan ve bildirim entegrasyonu güçlü bir masaüstü yazılımı ortaya çıkarmaktı. Ayrıca platform bağımsız olarak Windows, macOS ve Linux üzerinde tam performans çalışması gerekiyordu.
Teknolojik Altyapıyı Nasıl Seçtim: Tauri, Electron ve Flutter Karşılaştırması
Masaüstü uygulaması geliştirirken mimari bir çerçeve (framework) seçmek en kritik karardır. İlk aşamada önümde üç büyük seçenek vardı: Electron, Flutter ve Tauri. İhtiyaçlarımı doğru belirlemek adına bu üç teknolojiyi detaylı bir şekilde kıyasladım.
Electron, Node.js ve Chromium paketlediği için yüksek bellek kullanır. Dolayısıyla masaüstünde hafif bir istemci hedefiyle uyuşmuyordu. Flutter harika bir kullanıcı arayüzü (UI) sunsa da masaüstü platformlarında yerel sistem entegrasyonları tarafında bazen ek eklentilere ihtiyaç duyuyordu. Sonuç olarak terciğimi Tauri yönünde kullandım.
Tauri, arka planda Rust dilini kullanırken ön yüzde istediğiniz web teknolojisini (React, Vue veya Svelte) çalıştırmanıza imkan tanır. Sistem tarayıcısının yerel görüntüleyicisinden (WebView) yararlandığı için uygulama boyutu oldukça küçüktür (yaklaşık 10-15 MB). Bu durum, bellek kullanımını da minimum seviyeye indirir.
| Kriter | Electron | Flutter | Tauri (Seçilen) |
|---|---|---|---|
| Bellek (RAM) Kullanımı | Yüksek (~200MB+) | Orta (~80MB) | Çok Düşük (~30-50MB) |
| Paket Boyutu | Büyük (80-120MB) | Orta (30-50MB) | Çok Küçük (10-15MB) |
| Güvenlik Mimarisi | Karmaşık IPC Yapısı | Kendi Ekosistemi | Güçlü Rust İzolasyonu |
| Arka Plan Dili | JavaScript / Node.js | Dart | Rust |
Tauri sayesinde hem Rust dilinin sağladığı bellek güvenliğini ve hızını elde ettim hem de ön yüzde TypeScript ve React kullanarak esnek bir arayüz tasarladım. Bu bileşim, projenin performans hedeflerine ulaşmasında anahtar rol oynadı.
Nostr İstemci Mimarisini Sıfırdan Nasıl Kurguladım?
Nostr istemcisi geliştirmek, geleneksel REST API tabanlı uygulamalardan farklı bir düşünce yapısı gerektirir. Çünkü Nostr protokolünde veriler sunucularda saklanmaz; mesajlar “Röle” (Relay) adı verilen WebSocket sunucuları üzerinden dağıtılır. Bu nedenle mimarimi olay odaklı (event-driven) bir model üzerine kurdum.
İstemci mimarisini üç temel katmana ayırdım:
- Bağlantı ve Ağ Katmanı (Rust & WebSockets): Röleler ile sürekli WebSocket bağlantısı kuran, abonelikleri (subscriptions) yöneten katmandır.
- İş Mantığı ve Doğrulama Katmanı (Crypto & State): Gelen verilerin Schorr imzalarını doğrulayan, yerel önbelleğe kaydeden yapıdır.
- Arayüz Katmanı (React & TypeScript): Kullanıcıya akışı gösteren, etkileşimleri alan ve grafiksel bileşenleri barındıran katmandır.
Aşağıdaki diyagram niteliğindeki mantıksal akış, bir mesajın (event) istemciye geliş sürecini özetlemektedir:
[Nostr Rölesi] --(WebSocket JSON)--> [Rust Ağ Katmanı] --(İmza Doğrulama)--> [Yerel SQLite Önbellek] --(IPC Bridge)--> [React Arayüzü]
Bu mimari sayesinde arayüzün kilitlenmesini engelledim. Çünkü tüm veri çekme ve doğrulama işlemleri arka planda Rust bileşenleri üzerinde gerçekleşir. Böylelikle ön yüz her zaman 60 FPS hızında akıcı kalır.
Kriptografik Anahtar Yönetimi ve Güvenliği Nasıl Sağladım?
Nostr ekosisteminde şifreniz veya e-postanız yoktur; sadece gizli anahtarınız (nsec) ve açık anahtarınız (npub) bulunur. Eğer gizli anahtarınızı kaybederseniz hesabınızı kurtaramazsınız. Bu anahtar üçüncü taraf yazılımlar tarafından çalınırsa kimliğiniz tamamen ele geçirilir. Dolayısıyla güvenlik tasarımı projenin en hassas noktasıydı.
Gizli anahtarları asla tarayıcının localStorage veya sessionStorage alanında saklamadım. Bu alanlar zararlı XSS saldırılarına karşı savunmasızdır. Bunun yerine platformların yerel kasa mekanizmalarını entegre ettim:
- Windows: Credential Manager
- macOS: Keychain Access
- Linux: Secret Service API / Freedesktop Keyring
Tauri’nin Rust kanadında yazdığım güvenli eklenti vasıtasıyla, kullanıcının anahtarı işletim sisteminin şifreli kasasında saklanır. Arayüz tarafı bir imzalama işlemi yapacağı zaman Rust tarafına mesaj gönderir (invoke) ve imzalama işlemi bellek üzerinde güvenle yapılır. Böylece anahtar hiçbir zaman JavaScript ortamına sızmaz.
WebSocket ve Röle Bağlantılarını Hızlıca Nasıl Yönetirsiniz?
Nostr istemcilerinin karşılaştığı en büyük teknik zorluk çoklu WebSocket yönetimidir. Bir kullanıcı aynı anda 10 farklı röleye bağlı olabilir. Her röle aynı mesajı gönderebilir veya bağlantı aniden kopabilir. Bu durumu yönetmek için etkin bir bağlantı havuzu (connection pool) oluşturdum.
Aşağıdaki TypeScript örneği, temel bir Nostr olayının (Event – Kind 1) veri yapısını ve imzalanma biçimini göstermektedir:
interface NostrEvent {
id: string;
pubkey: string;
created_at: number;
kind: number;
tags: string[][];
content: string;
sig: string;
}
// Örnek bir Kind 1 (Metin Gönderisi) oluşturma fonksiyonu
function createNostrNote(content: string, pubkey: string): Omit<NostrEvent, 'id' | 'sig'> {
return {
pubkey: pubkey,
created_at: Math.floor(Date.now() / 1000),
kind: 1,
tags: [],
content: content
};
}
console.log("Nostr Olayı Hazırlandı:", createNostrNote("Merhaba Türkiye Nostr Topluluğu!", "npub1..."));
Rölelerden gelen verilerin mükerrer (duplicate) olmasını önlemek adına gelen her olayın id değerini süzgeçten geçiren bir LRU (Least Recently Used) önbellek algoritması kullandım. Böylece aynı gönderi farklı rölelerden gelse dahi ekrana yalnızca bir kez yansıtılır.
Bağlantı koptuğunda ise üssel geri çekilme (exponential backoff) stratejisi uyguladım. Yani kopan bir sunucuya hemen sürekli istek atmak yerine 1sn, 2sn, 4sn, 8sn şeklinde artan aralıklarla yeniden bağlanma denemesi kurguladım. Bu yöntem masaüstü uygulamasının ağ kaynaklarını lüzumsuz tüketmesini önledi.
Yerel Kullanıcı Deneyimini Artıran Gerçek Bir Vaka Analizi
Geliştirme sürecinde karşılaştığım somut bir vaka üzerinden konuyu derinleştirelim. Türkiye’deki Nostr kullanıcılarının oluşturduğu bir grupta, bölgesel internet yavaşlamaları sırasında istemcilerin kilitlendiği ve gönderilerin yüklenmediği bildirildi. Kullanıcılar web istemcilerini yenilediklerinde tüm veriler kayboluyor ve rölelere tekrar bağlanmak uzun zaman alıyordu.
Bu problemi çözmek için “Çevrimdışı Öncelikli” (Offline-First) bir mimariye geçiş yaptım. Senaryoyu şu adımlarla çözüme kavuşturdum:
- SQLite Entegrasyonu: Tauri üzerine gömülü bir SQLite veritabanı ekledim. Ağdan gelen tüm doğrulanan gönderiler anında yerel veritabanına yazıldı.
- Çift Yönlü Önbellekleme: Uygulama açıldığında WebSocket bağlantısının kurulması beklenmeden yerel veritabanındaki son 100 gönderi anında ekrana basıldı.
- Arka Plan Senkronizasyonu: Bağlantı kurulduğu anda
sincefiltresi kullanılarak sadece kalınan zamandan sonraki yeni veriler rölelerden talep edildi.
Bu iyileştirme sonucunda uygulamanın açılış ve veri gösterme süresi 3.5 saniyeden 120 milisaniyeye geriledi. İnternet bağlantısı kopsa dahi kullanıcılar geçmiş akışı okumaya ve taslak gönderi yazmaya devam edebildi. Bağlantı tekrar sağlandığında taslaklar otomatik olarak rölelere fırlatıldı.
İleri Düzey İpuçları: Bellek Sızıntılarını ve Performans Darboğazlarını Nasıl Engelledim?
Uzun süre açık kalan masaüstü uygulamalarında en sık yaşanan sorun bellek sızıntılarıdır (memory leak). Özellikle binlerce gönderinin aktığı canlı bir Nostr akışında DOM elemanlarının kontrolsüz büyümesi uygulamayı birkaç saat içinde kullanılmaz hale getirebilir.
Bu teknik darboğazı aşmak için uyguladığım ileri düzey yöntemler şunlardır:
- Sanal Listeleme (Virtual Scrolling): Ekranda aynı anda 10.000 gönderi olsa dahi yalnızca kullanıcının gördüğü 10-15 bileşeni DOM üzerinde render ettim. Görünmeyen elemanları bellekten düşürerek DOM yükünü sabitledim.
- Rust Web-Worker Benzeri Kanallar: Kriptografik imza doğrulamalarını ana arayüz iş parçacığı (main thread) yerine Rust tarafındaki paralel iş parçacıklarına (tokio async tasks) yükledim. Böylece arayüz donmalarını tamamen engelledim.
- Resim ve Medya Önbellekleme: Gönderilerdeki görselleri doğrudan uzak sunucudan çekmek yerine, yerel disk üzerinde boyutlandırıp önbelleğe alan bir ortam sunucusu katmanı kurguladım.
Bu optimizasyonlar sayesinde uygulama günlerce açık kalsa dahi bellek kullanımı 60 megabayt seviyesini aşmadı. Böylece hayalimdeki hafif ve hızlı masaüstü deneyimini tam anlamıyla yakalamış oldum.
Sıkça Sorulan Sorular
Nostr istemcisini kullanmak için bir hesap açmak paralı mıdır?
Hayır, Nostr protokolü tamamen ücretsizdir. Kendi anahtar çiftinizi (public/private key) oluşturarak herhangi bir kayıt formu doldurmadan istemciyi hemen kullanmaya başlayabilirsiniz.
Tauri ile geliştirilen masaüstü uygulaması güvenli midir?
Evet, Tauri güvenlik odaklı tasarlanmış bir çerçevedir. Varsayılan olarak tüm sistem yetkileri kapalıdır. Ayrıca içerik güvenlik politikaları (CSP) ve Rust dilinin bellek güvenliği sayesinde Electron’a kıyasla çok daha dar bir saldırı yüzeyi sunar.
Gizli anahtarımı (nsec) bu istemciye girmek güvenli mi?
İstemci açık kaynaklı olduğu için kodları inceleyebilirsiniz. Ayrıca anahtarlarınız işletim sisteminizin güvenli kasasında (Keychain/Credential Manager) şifrelenerek saklanır, kesinlikle uzak sunuculara aktarılmaz.
Farklı röleler kapandığında mesajlarım silinir mi?
Mesajlarınız birden fazla röleye aynı anda yayınlandığı için tek bir rölenin kapanması verilerinizin kaybolmasına yol açmaz. İstemci, mesajlarınızı diğer aktif rölelerden çekmeye devam eder.
Masaüstü Nostr Deneyiminde Son Sözler
Kendi ihtiyacımdan yola çıkarak geliştirdiğim çok platformlu Nostr masaüstü istemcisi, merkeziyetsiz sosyal ağların gelecekte ne kadar hızlı ve kullanıcı dostu olabileceğini kanıtlıyor. Tauri ve Rust mimarisinin sağladığı düşük kaynak kullanımı, React’in esnek arayüzü ile birleştiğinde ortaya hayalimdeki akıcı uygulama çıktı. Açık kaynak kodlu olarak geliştirdiğim bu projeyi topluluk geri bildirimleri doğrultusunda güncelliyor ve yeni NIP (Nostr Implementation Possibilities) standartlarına uyumlu hale getirmeye devam ediyorum.
#Nostr #MasaüstüUygulama #WebGeliştirme #AçıkKaynak #Kriptografi
