eleven / ocho Public

web: native-gated hook to drive Eleven desktop notifications (#65)

chat.js posts a decrypted message preview to the Mac app via window.webkit.messageHandlers.lchatNotify when running inside the Eleven wrapper. Completely inert on the web and mobile (the bridge only exists in the native app), so no behaviour change for browser/PWA users. Pairs with the native handler in PR #47. Suppresses your own messages and the thread you're actively reading.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Paul Campbell 65492c52caac975ee2509c5b7f04c303926f29b4 parent 0a22fbb
1 file changed, +18 −1
  • web/static/chat.js +18 −1
diff --git a/web/static/chat.js b/web/static/chat.js
index a18aa95..a79289b 100644
--- a/web/static/chat.js
+++ b/web/static/chat.js
@@ -747,6 +747,20 @@ function previewText(t, data) {
return t.kind === "group" && data.name ? `${data.name}: ${body}` : body;
}
+// Native (Eleven) local notification: inside the Mac wrapper, hand a decrypted
+// preview to the app so it posts a system notification. No-op on the web (there the
+// service worker handles push). Suppresses your own messages and the thread you're
+// actively reading in a focused window.
+function maybeNativeNotify(tid, data, preview) {
+ const bridge = window.webkit?.messageHandlers?.lchatNotify;
+ if (!bridge) return;
+ if (data.name === session.name) return;
+ if (tid === activeId && document.hasFocus()) return;
+ try {
+ bridge.postMessage({ title: threadName[tid] || "New message", body: preview, thread: tid });
+ } catch (e) {}
+}
+
async function displayName(t) {
if (t.kind === "self") return "Notes";
if (t.kind === "dm") {
@@ -2960,7 +2974,10 @@ function connectWS() {
if (t) {
t.last_at = msg.created_at || Math.floor(Date.now() / 1000);
try {
- threadPreview[tid] = previewText(t, await decrypt(threadKeys[tid], msg.blob));
+ const nd = await decrypt(threadKeys[tid], msg.blob);
+ const preview = previewText(t, nd);
+ threadPreview[tid] = preview;
+ maybeNativeNotify(tid, nd, preview);
} catch {}
}
renderThreadList();