use js_sys::{Object, Reflect};
use wasm_bindgen::prelude::*;
async fn feed_token_id() -> Option<u64> {
let name = crate::app::tenant::current_name()?;
match crate::app::registry::id_of_name(&name).await {
Ok(id) if id != 0 => Some(id),
_ => None,
}
}
fn post_agent_context(
worker: &web_sys::Worker,
has_identity: Option<bool>,
is_subscribed: Option<bool>,
subscriber_count: Option<u32>,
) {
let msg = Object::new();
let _ = Reflect::set(&msg, &JsValue::from_str("type"), &JsValue::from_str("agent_context"));
if let Some(h) = has_identity {
let _ = Reflect::set(&msg, &JsValue::from_str("viewerHasIdentity"), &JsValue::from_f64(if h { 1.0 } else { 0.0 }));
}
if let Some(sub) = is_subscribed {
let _ = Reflect::set(&msg, &JsValue::from_str("feedIsSubscribed"), &JsValue::from_f64(if sub { 1.0 } else { 0.0 }));
}
if let Some(c) = subscriber_count {
let _ = Reflect::set(&msg, &JsValue::from_str("feedSubscriberCount"), &JsValue::from_f64(c as f64));
}
let _ = worker.post_message(&msg);
}
pub(crate) async fn refresh_feed_context(worker: web_sys::Worker) {
let addr = crate::app::chat::credit_address_existing().await;
if addr.is_none() {
return;
}
let Some(feed_id) = feed_token_id().await else { return };
let has_identity = addr.is_some();
let is_sub = match &addr {
Some(a) => crate::app::registry::is_subscribed(feed_id, a).await.unwrap_or(false),
None => false,
};
let count = crate::app::registry::subscriber_count(feed_id).await.unwrap_or(0) as u32;
post_agent_context(&worker, Some(has_identity), Some(is_sub), Some(count));
}
pub(crate) async fn do_feed_subscribe(worker: web_sys::Worker, subscribe: bool) {
let Some(feed_id) = feed_token_id().await else { return };
let Some((signer, _)) = crate::app::chat::credit_signer().await else { return };
let Ok(sponsor) = crate::app::sponsor::signer() else { return };
let token = crate::app::registry::ALPHA_USD_ADDRESS();
let res = if subscribe {
crate::app::registry::subscribe_sponsored(&signer, &sponsor, feed_id, token).await
} else {
crate::app::registry::unsubscribe_sponsored(&signer, &sponsor, feed_id, token).await
};
if let Err(e) = res {
web_sys::console::warn_1(&JsValue::from_str(&format!("feed subscribe: {e}")));
} else if subscribe {
if crate::app::notifications::ensure_permission().await.unwrap_or(false) {
publish_viewer_push_sub().await;
}
}
refresh_feed_context(worker).await;
}
async fn publish_viewer_push_sub() {
let Ok(sub_json) = crate::app::notifications::subscribe_push().await else { return };
let Some((signer, _)) = crate::app::chat::credit_signer().await else { return };
let Ok(sponsor) = crate::app::sponsor::signer() else { return };
let token = crate::app::registry::ALPHA_USD_ADDRESS();
if let Err(e) = crate::registry::set_push_sub_sponsored(
&signer,
&sponsor,
sub_json.as_bytes(),
token,
)
.await
{
web_sys::console::warn_1(&JsValue::from_str(&format!("publish push_sub: {e}")));
}
}
pub(crate) async fn do_feed_broadcast(title: String, body: String) {
let Some(feed_id) = feed_token_id().await else { return };
let Some((signer, _)) = crate::app::chat::credit_signer().await else { return };
let now = (js_sys::Date::now() / 1000.0) as u64;
let token = crate::registry::proxy_auth_token(&signer, now, "broadcast");
let url = format!(
"{}api/broadcast",
crate::registry::CREDIT_PROXY_URL
);
let payload = serde_json::json!({ "targetId": feed_id, "title": title, "body": body });
let send = async {
reqwest::Client::new()
.post(&url)
.header("content-type", "application/json")
.header("x-goog-api-key", token)
.json(&payload)
.send()
.await
.map_err(|e| format!("broadcast request: {e}"))
};
match crate::app::net::with_timeout(20_000, send).await {
Ok(Ok(_resp)) => {}
Ok(Err(e)) => web_sys::console::warn_1(&JsValue::from_str(&format!("broadcast: {e}"))),
Err(e) => web_sys::console::warn_1(&JsValue::from_str(&format!("broadcast timeout: {e}"))),
}
crate::app::notifications::push_to_bell(&title, &body);
if crate::app::notifications::ensure_permission().await.unwrap_or(false) {
let _ = crate::app::notifications::show(&title, &body).await;
}
crate::app::notifications::vibrate(120);
}
pub(crate) async fn do_feed_request_identity(worker: web_sys::Worker) {
let _ = crate::app::chat::credit_signer().await;
refresh_feed_context(worker).await;
}
thread_local! {
static FEED_CARTRIDGE_ACTIVE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
static FEED_PRIMED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub(crate) fn set_feed_cartridge_active(on: bool) {
FEED_CARTRIDGE_ACTIVE.with(|c| c.set(on));
if on {
FEED_PRIMED.with(|c| c.set(false));
}
}
pub(crate) fn prime_feed_permission_on_gesture() {
if !FEED_CARTRIDGE_ACTIVE.with(|c| c.get()) || FEED_PRIMED.with(|c| c.get()) {
return;
}
if matches!(
web_sys::Notification::permission(),
web_sys::NotificationPermission::Denied
) {
return; }
FEED_PRIMED.with(|c| c.set(true));
wasm_bindgen_futures::spawn_local(async {
if crate::app::notifications::ensure_permission().await.unwrap_or(false) {
publish_viewer_push_sub().await;
} else {
FEED_PRIMED.with(|c| c.set(false));
}
});
}