Fix mobile web gestures, compact chat layout and notification diagnostics

This commit is contained in:
2026-11-02 18:34:55 -08:00
parent cb9d5a787b
commit 7c1f82c805
17 changed files with 683 additions and 66 deletions
+10 -11
View File
@@ -1,5 +1,6 @@
import { getApiBaseUrl, getAuthToken } from "./api";
import { ensureServiceWorker } from "../pwa";
import { browserPushOperation } from "./agent-push-errors.ts";
import {
AGENT_PUSH_STORAGE_KEY, AgentPushClient, agentPushAuthorityScope, agentPushCursor, agentPushSourceId,
createAgentPresenceReporter, createAgentPushController, parseAgentPushConsent, type AgentPushView, type PushSubscriptionHandle,
@@ -24,17 +25,12 @@ async function consentScope() {
return digest;
}
const listeners = new Set<(state: AgentPushView) => void>();
function bounded<T>(promise: Promise<T>, timeoutMs = 10000): Promise<T> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("Browser notification operation timed out.")), timeoutMs);
promise.then(value => { clearTimeout(timeout); resolve(value); }, () => { clearTimeout(timeout); reject(new Error("Browser notification operation failed.")); });
});
}
const subscriptionHandle = (subscription: PushSubscription): PushSubscriptionHandle => ({
toJSON: () => subscription.toJSON(), unsubscribe: () => bounded(subscription.unsubscribe()),
toJSON: () => subscription.toJSON(), unsubscribe: () => browserPushOperation(() => subscription.unsubscribe(), "subscription"),
});
const readConsent = () => { try { return parseAgentPushConsent(localStorage.getItem(AGENT_PUSH_STORAGE_KEY)); } catch { return null; } };
const support = () => {
if (!window.isSecureContext) return "insecure" as const;
const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
const standalone = window.matchMedia("(display-mode: standalone)").matches || window.matchMedia("(display-mode: fullscreen)").matches
|| (navigator as Navigator & { standalone?: boolean }).standalone === true;
@@ -79,11 +75,14 @@ const controller = createAgentPushController({
requestPermission: () => Notification.requestPermission(),
subscription: async () => {
if (!("serviceWorker" in navigator)) return null;
const registration = await bounded(navigator.serviceWorker.getRegistration("/"));
const subscription = registration ? await bounded(registration.pushManager.getSubscription()) : null;
const registration = await browserPushOperation(() => navigator.serviceWorker.getRegistration("/"), "worker");
const subscription = registration ? await browserPushOperation(() => registration.pushManager.getSubscription(), "subscription") : null;
return subscription ? subscriptionHandle(subscription) : null;
},
subscribe: async key => subscriptionHandle(await bounded((await ensureServiceWorker()).pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: decodePublicKey(key) }))),
subscribe: async key => {
const registration = await browserPushOperation(ensureServiceWorker, "worker", 20000);
return subscriptionHandle(await browserPushOperation(() => registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: decodePublicKey(key) }), "subscription"));
},
clearNotifications: () => postWorker({ type: "sybil-agent-logout", version: 1 }),
},
});
@@ -91,7 +90,7 @@ export const getAgentPushState = () => controller.state();
export function observeAgentPush(listener: (state: AgentPushView) => void) { listeners.add(listener); listener(controller.state()); return () => { listeners.delete(listener); }; }
export const enableAgentBrowserPush = () => controller.enable();
export const disableAgentBrowserPush = () => controller.disable();
export const refreshAgentBrowserPush = () => controller.refresh();
export const refreshAgentBrowserPush = () => controller.refresh(true);
const presence = createAgentPresenceReporter({
snapshot: () => ({ visibility: !document.hidden && document.visibilityState === "visible" ? "visible" : "hidden", focused: document.hasFocus() }),