mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 06:32:24 +00:00
* wip(notifications): core module skeleton + controller registration Phase 7 PR #714 work-in-progress. Shipped: - src/openhuman/webview_notifications/ — mod, types, dispatch, bus, schemas - pub mod declaration in src/openhuman/mod.rs - controllers entry in src/core/all.rs Remaining: schemas line in all.rs, Tauri shell edits (OpenHuman: prefix, feature flag gate, click event emit), new shell module for feature flag state + commands, frontend app/src/lib/webviewNotifications/ IPC wrapper, Redux click handler. * feat(notifications): register webview_notifications schemas in core registry Adds the schemas line next to the already-registered controllers entry so the domain participates in declared-schema validation. v1 controller list is empty (settings live shell-side), but the wiring is in place for future additions. * feat(notifications): add OpenHuman: prefix, feature flag, and click-routing event Updates the CEF notification hook in webview_accounts to: - Prefix OS toast titles with "OpenHuman:" so they visually disambiguate from natively installed apps (Slack, Gmail, Discord desktop) firing the same DM twice. - Gate delivery on a new shell-side feature flag (off by default). - Mirror each fire to the React frontend via a `webview-notification:fired` Tauri event carrying {account_id, provider, title, body, tag}, so the UI can route click-to-focus back to the originating webview. Adds a new notification_settings shell module holding the runtime toggle as an AtomicBool (lock-free read from the CEF callback thread) plus notification_settings_{get,set} Tauri commands for the settings UI to flip the flag. State lives shell-side rather than in the core sidecar so the toggle doesn't require a JSON-RPC round-trip. * feat(notifications): frontend IPC wrapper + Redux click routing Mirrors the Rust-side `webview-notification:fired` Tauri event into Redux so the UI can: - bump an unread badge on the originating account (sidebar surface reads `accounts.unread[accountId]`) - route a subsequent click intent back to the right embedded webview via `focusAccountFromNotification`, which sets `activeAccountId` and clears the unread counter in one action Plumbing: - `app/src/lib/webviewNotifications/` — typed subscribe/unsubscribe, idempotent `started` guard mirroring `webviewAccountService.ts`, `handleNotificationClick(accountId)` as the public click entrypoint so in-app toast UI or a future OS-notification click hook share one Redux intent - `accountsSlice`: `noteWebviewNotificationFired` and `focusAccountFromNotification` reducers (both no-op for unknown account ids — guards against stale payloads from a closed webview) - `App.tsx`: start the service at boot, right next to the existing `startWebviewAccountService()` call Tests: - `accountsSlice.webviewNotifications.test.ts` — reducer behavior - `service.test.ts` — fired dispatches + click focuses via real store Also: `cargo fmt` noise fixups on `src/core/all.rs` and `src/openhuman/webview_notifications/mod.rs` so the branch passes `cargo fmt --check` in CI. --------- Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
83 lines
2.4 KiB
TypeScript
83 lines
2.4 KiB
TypeScript
import { isTauri } from '@tauri-apps/api/core';
|
|
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
|
import debug from 'debug';
|
|
|
|
import { store } from '../../store';
|
|
import {
|
|
focusAccountFromNotification,
|
|
noteWebviewNotificationFired,
|
|
} from '../../store/accountsSlice';
|
|
import { WEBVIEW_NOTIFICATION_FIRED_EVENT, type WebviewNotificationFired } from './types';
|
|
|
|
const log = debug('webview-notifications');
|
|
const errLog = debug('webview-notifications:error');
|
|
|
|
let started = false;
|
|
let unlisten: UnlistenFn | null = null;
|
|
|
|
/**
|
|
* Subscribe to `webview-notification:fired` events from the Tauri shell and
|
|
* mirror each fire into Redux so the sidebar can bump an unread badge on
|
|
* the originating account. Idempotent — subsequent calls are no-ops.
|
|
*/
|
|
export function startWebviewNotificationsService(): void {
|
|
if (started) return;
|
|
if (!isTauri()) {
|
|
log('not running in tauri, skipping subscription');
|
|
return;
|
|
}
|
|
started = true;
|
|
|
|
listen<WebviewNotificationFired>(WEBVIEW_NOTIFICATION_FIRED_EVENT, event => {
|
|
handleFired(event.payload);
|
|
})
|
|
.then(fn => {
|
|
unlisten = fn;
|
|
log('subscribed to %s', WEBVIEW_NOTIFICATION_FIRED_EVENT);
|
|
})
|
|
.catch(err => {
|
|
errLog('failed to subscribe: %O', err);
|
|
started = false;
|
|
});
|
|
}
|
|
|
|
export function stopWebviewNotificationsService(): void {
|
|
if (unlisten) {
|
|
unlisten();
|
|
unlisten = null;
|
|
}
|
|
started = false;
|
|
}
|
|
|
|
/**
|
|
* Route a user-visible "click this notification" intent back to the
|
|
* originating account — focuses it and clears the unread count. Safe to
|
|
* call from in-app toast UIs or a future OS-notification click hook.
|
|
*/
|
|
export function handleNotificationClick(accountId: string): void {
|
|
store.dispatch(focusAccountFromNotification({ accountId }));
|
|
}
|
|
|
|
function handleFired(payload: WebviewNotificationFired): void {
|
|
const { account_id: accountId, provider, title, body } = payload;
|
|
log(
|
|
'fired account=%s provider=%s title_chars=%d body_chars=%d',
|
|
accountId,
|
|
provider,
|
|
title.length,
|
|
body.length
|
|
);
|
|
store.dispatch(noteWebviewNotificationFired({ accountId }));
|
|
}
|
|
|
|
/** Exposed for tests — resets module singletons between runs. */
|
|
export function __resetForTests(): void {
|
|
started = false;
|
|
unlisten = null;
|
|
}
|
|
|
|
/** Exposed for tests — dispatches as if a fired event arrived. */
|
|
export function __handleFiredForTests(payload: WebviewNotificationFired): void {
|
|
handleFired(payload);
|
|
}
|