Compare commits

...

3 Commits

Author SHA1 Message Date
spinline
0d059cbbd3 fix: push notification permission ve toJSON çağrıları tip güvenli hale getirildi
All checks were successful
Build MIPS Binary / build (push) Successful in 4m14s
2026-02-08 19:30:05 +03:00
spinline
fc83a1cc65 refactor: js_sys::eval kullanımı kaldırıldı, Base64 çözümleme pure Rust ile güncellendi
All checks were successful
Build MIPS Binary / build (push) Successful in 4m14s
2026-02-08 19:25:36 +03:00
spinline
4e81af0599 fix: unused import warning for utoipa::OpenApi in production build
All checks were successful
Build MIPS Binary / build (push) Successful in 4m18s
2026-02-08 18:49:54 +03:00
3 changed files with 79 additions and 138 deletions

View File

@@ -32,6 +32,7 @@ use tower_http::{
cors::CorsLayer, cors::CorsLayer,
trace::TraceLayer, trace::TraceLayer,
}; };
#[cfg(feature = "swagger")]
use utoipa::OpenApi; use utoipa::OpenApi;
#[cfg(feature = "swagger")] #[cfg(feature = "swagger")]
use utoipa_swagger_ui::SwaggerUi; use utoipa_swagger_ui::SwaggerUi;

View File

@@ -51,4 +51,6 @@ web-sys = { version = "0.3", features = [
] } ] }
shared = { path = "../shared" } shared = { path = "../shared" }
tailwind_fuse = "0.3.2" tailwind_fuse = "0.3.2"
js-sys = "0.3.85" js-sys = "0.3.85"
base64 = "0.22.1"
serde-wasm-bindgen = "0.6.5"

View File

@@ -328,67 +328,46 @@ pub async fn subscribe_to_push_notifications() {
// First, request notification permission if not already granted // First, request notification permission if not already granted
let window = web_sys::window().expect("window should exist"); let window = web_sys::window().expect("window should exist");
let permission_granted = if let Ok(notification_class) = js_sys::Reflect::get(&window, &"Notification".into()) {
if notification_class.is_undefined() { // Notification.permission is a static property, but web_sys exposes it via the Notification class instance or we check it manually.
log::error!("Notification API not available"); // Actually, Notification::permission() is a static method in web_sys.
return; match web_sys::Notification::permission() {
} web_sys::NotificationPermission::Granted => {
// Check current permission
let current_permission = js_sys::Reflect::get(&notification_class, &"permission".into())
.ok()
.and_then(|p| p.as_string())
.unwrap_or_default();
if current_permission == "granted" {
log::info!("Notification permission already granted"); log::info!("Notification permission already granted");
true }
} else if current_permission == "denied" { web_sys::NotificationPermission::Denied => {
log::warn!("Notification permission was denied"); log::warn!("Notification permission was denied");
return; return;
} else { }
// Permission is "default" - need to request web_sys::NotificationPermission::Default => {
log::info!("Requesting notification permission..."); log::info!("Requesting notification permission...");
if let Ok(request_fn) = js_sys::Reflect::get(&notification_class, &"requestPermission".into()) { let permission_promise = match web_sys::Notification::request_permission() {
if request_fn.is_function() { Ok(p) => p,
let request_fn_typed = js_sys::Function::from(request_fn); Err(e) => {
match request_fn_typed.call0(&notification_class) { log::error!("Failed to request notification permission: {:?}", e);
Ok(promise_val) => { return;
let request_future = wasm_bindgen_futures::JsFuture::from( }
js_sys::Promise::from(promise_val) };
);
match request_future.await { match wasm_bindgen_futures::JsFuture::from(permission_promise).await {
Ok(result) => { Ok(val) => {
let result_str = result.as_string().unwrap_or_default(); let permission = val.as_string().unwrap_or_default();
log::info!("Permission request result: {}", result_str); if permission != "granted" {
result_str == "granted" log::warn!("Notification permission denied by user");
} return;
Err(e) => { }
log::error!("Failed to request notification permission: {:?}", e); log::info!("Notification permission granted by user");
false }
} Err(e) => {
} log::error!("Failed to await notification permission: {:?}", e);
} return;
Err(e) => {
log::error!("Failed to call requestPermission: {:?}", e);
false
}
}
} else {
false
} }
} else {
false
} }
} }
} else { _ => {
log::error!("Cannot access Notification class"); log::warn!("Unknown notification permission status");
return; return;
}; }
if !permission_granted {
log::warn!("Notification permission not granted, cannot subscribe to push");
return;
} }
log::info!("Notification permission granted! Proceeding with push subscription..."); log::info!("Notification permission granted! Proceeding with push subscription...");
@@ -433,7 +412,6 @@ pub async fn subscribe_to_push_notifications() {
}; };
// Get service worker registration // Get service worker registration
let window = web_sys::window().expect("window should exist");
let navigator = window.navigator(); let navigator = window.navigator();
let service_worker = navigator.service_worker(); let service_worker = navigator.service_worker();
@@ -494,38 +472,45 @@ pub async fn subscribe_to_push_notifications() {
.dyn_into::<web_sys::PushSubscription>() .dyn_into::<web_sys::PushSubscription>()
.expect("should be PushSubscription"); .expect("should be PushSubscription");
// Get subscription JSON using toJSON() method // PushSubscription objects can be serialized directly via JSON.stringify which calls their toJSON method internally.
let json_result = match js_sys::Reflect::get(&push_subscription, &"toJSON".into()) { // Or we can use Reflect to call toJSON if we want the object directly.
// Let's use the robust way: call toJSON via Reflect but handle it gracefully.
let json_val = match js_sys::Reflect::get(&push_subscription, &"toJSON".into()) {
Ok(func) if func.is_function() => { Ok(func) if func.is_function() => {
let json_func = js_sys::Function::from(func); let json_func = js_sys::Function::from(func);
match json_func.call0(&push_subscription) { match json_func.call0(&push_subscription) {
Ok(result) => result, Ok(res) => res,
Err(e) => {
log::error!("Failed to call toJSON: {:?}", e);
return;
}
}
}
_ => {
// Fallback: try to stringify the object directly
// log::warn!("toJSON not found, trying JSON.stringify");
let json_str = match js_sys::JSON::stringify(&push_subscription) {
Ok(s) => s,
Err(e) => { Err(e) => {
log::error!("Failed to call toJSON: {:?}", e); log::error!("Failed to stringify subscription: {:?}", e);
return;
}
};
// Parse back to object to match our expected flow (slightly inefficient but safe)
match js_sys::JSON::parse(&String::from(json_str)) {
Ok(v) => v,
Err(e) => {
log::error!("Failed to parse stringified subscription: {:?}", e);
return; return;
} }
} }
} }
_ => {
log::error!("toJSON method not found on PushSubscription");
return;
}
}; };
let json_value = match js_sys::JSON::stringify(&json_result) { // Convert JsValue (JSON object) to PushSubscriptionJSON struct via serde
Ok(val) => val, // Note: web_sys::PushSubscriptionJSON is not a struct we can directly use with serde_json usually,
Err(e) => { // but we can use serde-wasm-bindgen to convert JsValue -> Rust Struct
log::error!("Failed to stringify subscription: {:?}", e); let subscription_data: PushSubscriptionData = match serde_wasm_bindgen::from_value(json_val) {
return;
}
};
let subscription_json_str = json_value.as_string().expect("should be string");
log::info!("Push subscription: {}", subscription_json_str);
// Parse and send to backend
let subscription_data: serde_json::Value = match serde_json::from_str(&subscription_json_str) {
Ok(data) => data, Ok(data) => data,
Err(e) => { Err(e) => {
log::error!("Failed to parse subscription JSON: {:?}", e); log::error!("Failed to parse subscription JSON: {:?}", e);
@@ -533,37 +518,9 @@ pub async fn subscribe_to_push_notifications() {
} }
}; };
// Extract endpoint and keys // Send to backend (subscription_data is already the struct we need)
let endpoint = subscription_data
.get("endpoint")
.and_then(|v| v.as_str())
.expect("endpoint should exist")
.to_string();
let keys_obj = subscription_data
.get("keys")
.expect("keys should exist");
let p256dh = keys_obj
.get("p256dh")
.and_then(|v| v.as_str())
.expect("p256dh should exist")
.to_string();
let auth = keys_obj
.get("auth")
.and_then(|v| v.as_str())
.expect("auth should exist")
.to_string();
let push_data = PushSubscriptionData {
endpoint,
keys: PushKeys { p256dh, auth },
};
// Send to backend
let response = match Request::post("/api/push/subscribe") let response = match Request::post("/api/push/subscribe")
.json(&push_data) .json(&subscription_data)
.expect("serialization should succeed") .expect("serialization should succeed")
.send() .send()
.await .await
@@ -583,34 +540,15 @@ pub async fn subscribe_to_push_notifications() {
} }
/// Helper to convert URL-safe base64 string to Uint8Array /// Helper to convert URL-safe base64 string to Uint8Array
/// Uses JavaScript to properly decode binary data (avoids UTF-8 encoding issues) /// Uses pure Rust base64 crate for better safety and performance
fn url_base64_to_uint8array(base64_string: &str) -> Result<js_sys::Uint8Array, JsValue> { fn url_base64_to_uint8array(base64_string: &str) -> Result<js_sys::Uint8Array, JsValue> {
// Add padding use base64::{engine::general_purpose, Engine as _};
let padding = (4 - (base64_string.len() % 4)) % 4;
let mut padded = base64_string.to_string();
padded.push_str(&"=".repeat(padding));
// Replace URL-safe characters // VAPID keys are URL-safe base64. Try both NO_PAD and padded for robustness.
let standard_base64 = padded.replace('-', "+").replace('_', "/"); let bytes = general_purpose::URL_SAFE_NO_PAD
.decode(base64_string)
.or_else(|_| general_purpose::URL_SAFE.decode(base64_string))
.map_err(|e| JsValue::from_str(&format!("Base64 decode error: {}", e)))?;
// Decode using JavaScript to avoid UTF-8 encoding issues Ok(js_sys::Uint8Array::from(&bytes[..]))
// Create a JavaScript function to decode the base64 and convert to Uint8Array
let js_code = format!(
r#"
(function() {{
const binaryString = atob('{}');
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {{
bytes[i] = binaryString.charCodeAt(i);
}}
return bytes;
}})()
"#,
standard_base64
);
let result = js_sys::eval(&js_code)?;
let array = result.dyn_into::<js_sys::Uint8Array>()?;
Ok(array)
} }