botschat 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/api/src/do/connection-do.ts +103 -19
- package/packages/api/src/env.ts +6 -0
- package/packages/api/src/index.ts +25 -2
- package/packages/api/src/utils/apns.ts +151 -0
- package/packages/api/src/utils/fcm.ts +23 -32
- package/packages/plugin/dist/src/channel.d.ts.map +1 -1
- package/packages/plugin/dist/src/channel.js +25 -2
- package/packages/plugin/dist/src/channel.js.map +1 -1
- package/packages/plugin/dist/src/types.d.ts +5 -0
- package/packages/plugin/dist/src/types.d.ts.map +1 -1
- package/packages/plugin/dist/src/ws-client.d.ts +1 -0
- package/packages/plugin/dist/src/ws-client.d.ts.map +1 -1
- package/packages/plugin/dist/src/ws-client.js +1 -0
- package/packages/plugin/dist/src/ws-client.js.map +1 -1
- package/packages/plugin/package.json +1 -1
- package/packages/web/dist/assets/index-BJye3VHV.js +1516 -0
- package/packages/web/dist/assets/{index-Bd_RDcgO.css → index-CNSCbd7_.css} +1 -1
- package/packages/web/dist/assets/index-CPOiRHa4.js +2 -0
- package/packages/web/dist/assets/{index-lVB82JKU.js → index-CQPXprFz.js} +1 -1
- package/packages/web/dist/assets/{index-Civeg2lm.js → index-CkIgZfHf.js} +1 -1
- package/packages/web/dist/assets/index-DbUyNI4d.js +1 -0
- package/packages/web/dist/assets/index-Dpvhc_dU.js +2 -0
- package/packages/web/dist/assets/{index.esm-CtMkqqqb.js → index.esm-DgcFARs7.js} +1 -1
- package/packages/web/dist/assets/{web-vKLTVUul.js → web-Bfku9Io_.js} +1 -1
- package/packages/web/dist/assets/{web-CUXjh_UA.js → web-CnOlwlZw.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/src/App.tsx +92 -5
- package/packages/web/src/api.ts +2 -2
- package/packages/web/src/components/ChatWindow.tsx +20 -2
- package/packages/web/src/components/E2ESettings.tsx +70 -1
- package/packages/web/src/components/MobileLayout.tsx +7 -0
- package/packages/web/src/foreground.ts +5 -1
- package/packages/web/src/push.ts +27 -3
- package/scripts/mock-openclaw.mjs +13 -3
- package/packages/web/dist/assets/index-B9qN5gs6.js +0 -1
- package/packages/web/dist/assets/index-BQNMGVyU.js +0 -2
- package/packages/web/dist/assets/index-Dk33VSnY.js +0 -2
- package/packages/web/dist/assets/index-Kr85Nj_-.js +0 -1516
package/packages/web/src/App.tsx
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { getToken, setToken, setRefreshToken, agentsApi, channelsApi, tasksApi, jobsApi, authApi, messagesApi, modelsApi, meApi, sessionsApi, type ModelInfo } from "./api";
|
|
14
14
|
import { ModelSelect } from "./components/ModelSelect";
|
|
15
15
|
import { BotsChatWSClient, type WSMessage } from "./ws";
|
|
16
|
-
import { initPushNotifications } from "./push";
|
|
16
|
+
import { initPushNotifications, getPendingPushNav, clearPendingPushNav } from "./push";
|
|
17
17
|
import { setupForegroundDetection } from "./foreground";
|
|
18
18
|
import { IconRail } from "./components/IconRail";
|
|
19
19
|
import { Sidebar } from "./components/Sidebar";
|
|
@@ -51,6 +51,7 @@ export default function App() {
|
|
|
51
51
|
const wsClientRef = useRef<BotsChatWSClient | null>(null);
|
|
52
52
|
const handleWSMessageRef = useRef<(msg: WSMessage) => void>(() => {});
|
|
53
53
|
const creatingGeneralRef = useRef(false);
|
|
54
|
+
const pushNavTargetRef = useRef<string | null>(null);
|
|
54
55
|
|
|
55
56
|
const [showSettings, setShowSettings] = useState(false);
|
|
56
57
|
const [settingsTab, setSettingsTab] = useState<"general" | "connection" | "security">("general");
|
|
@@ -64,6 +65,9 @@ export default function App() {
|
|
|
64
65
|
return E2eService.subscribe(() => setE2eReady(E2eService.hasKey()));
|
|
65
66
|
}, []);
|
|
66
67
|
|
|
68
|
+
// Foreground resume counter — triggers message reload when app returns from background
|
|
69
|
+
const [foregroundResumeCount, setForegroundResumeCount] = useState(0);
|
|
70
|
+
|
|
67
71
|
// Responsive layout hooks (must be called unconditionally)
|
|
68
72
|
const isMobile = useIsMobile();
|
|
69
73
|
const mainLayout = useDefaultLayout({ id: "botschat-main" });
|
|
@@ -259,6 +263,20 @@ export default function App() {
|
|
|
259
263
|
dlog.info("Agents", `Loaded ${agents.length} agents`, agents.map((a) => ({ id: a.id, name: a.name, channelId: a.channelId })));
|
|
260
264
|
dispatch({ type: "SET_AGENTS", agents });
|
|
261
265
|
if (agents.length > 0 && !state.selectedAgentId) {
|
|
266
|
+
// Push notification deep-link takes priority over localStorage
|
|
267
|
+
const pushNav = pushNavTargetRef.current;
|
|
268
|
+
if (pushNav) {
|
|
269
|
+
const m = pushNav.match(/^agent:([^:]+):/);
|
|
270
|
+
if (m) {
|
|
271
|
+
const ta = agents.find((a) => a.sessionKey.startsWith(`agent:${m[1]}:`));
|
|
272
|
+
if (ta) {
|
|
273
|
+
dlog.info("Push", `Agent select from push nav: ${ta.name} (${ta.id})`);
|
|
274
|
+
dispatch({ type: "SET_ACTIVE_VIEW", view: "messages" });
|
|
275
|
+
dispatch({ type: "SELECT_AGENT", agentId: ta.id, sessionKey: ta.sessionKey });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
262
280
|
// Restore last selected channel from localStorage if available
|
|
263
281
|
let target = agents[0];
|
|
264
282
|
try {
|
|
@@ -371,6 +389,18 @@ export default function App() {
|
|
|
371
389
|
dlog.info("Sessions", `Loaded ${sessions.length} sessions for channel ${agent.channelId}`);
|
|
372
390
|
dispatch({ type: "SET_SESSIONS", sessions });
|
|
373
391
|
if (sessions.length > 0) {
|
|
392
|
+
// Push notification deep-link takes priority
|
|
393
|
+
const pushNav = pushNavTargetRef.current;
|
|
394
|
+
if (pushNav) {
|
|
395
|
+
const ts = sessions.find((s) => s.sessionKey === pushNav);
|
|
396
|
+
if (ts) {
|
|
397
|
+
dlog.info("Push", `Session select from push nav: ${ts.name} (${ts.id})`);
|
|
398
|
+
pushNavTargetRef.current = null;
|
|
399
|
+
clearPendingPushNav();
|
|
400
|
+
dispatch({ type: "SELECT_SESSION", sessionId: ts.id, sessionKey: ts.sessionKey });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
374
404
|
// Restore last selected session from localStorage if available
|
|
375
405
|
let target = sessions[0];
|
|
376
406
|
try {
|
|
@@ -498,7 +528,7 @@ export default function App() {
|
|
|
498
528
|
console.error("Failed to load message history:", err);
|
|
499
529
|
});
|
|
500
530
|
return () => { stale = true; };
|
|
501
|
-
}, [state.user, state.selectedSessionKey, e2eReady]);
|
|
531
|
+
}, [state.user, state.selectedSessionKey, e2eReady, foregroundResumeCount]);
|
|
502
532
|
|
|
503
533
|
// Keep a ref to state for use in WS handler (avoids stale closures)
|
|
504
534
|
const stateRef = useRef(state);
|
|
@@ -506,6 +536,56 @@ export default function App() {
|
|
|
506
536
|
stateRef.current = state;
|
|
507
537
|
}, [state]);
|
|
508
538
|
|
|
539
|
+
// ---- Push notification deep-link navigation ----
|
|
540
|
+
const navigateToPushTarget = useCallback((sessionKey: string) => {
|
|
541
|
+
dlog.info("Push", `Navigating to session: ${sessionKey}`);
|
|
542
|
+
const baseKey = sessionKey.replace(/:thread:.+$/, "");
|
|
543
|
+
pushNavTargetRef.current = baseKey;
|
|
544
|
+
|
|
545
|
+
const match = baseKey.match(/^agent:([^:]+):/);
|
|
546
|
+
if (!match) return;
|
|
547
|
+
const openclawAgentId = match[1];
|
|
548
|
+
|
|
549
|
+
const st = stateRef.current;
|
|
550
|
+
const targetAgent = st.agents.find((a) =>
|
|
551
|
+
a.sessionKey.startsWith(`agent:${openclawAgentId}:`),
|
|
552
|
+
);
|
|
553
|
+
if (!targetAgent) return;
|
|
554
|
+
|
|
555
|
+
if (st.activeView !== "messages") {
|
|
556
|
+
dispatch({ type: "SET_ACTIVE_VIEW", view: "messages" });
|
|
557
|
+
}
|
|
558
|
+
if (st.selectedAgentId !== targetAgent.id) {
|
|
559
|
+
dispatch({
|
|
560
|
+
type: "SELECT_AGENT",
|
|
561
|
+
agentId: targetAgent.id,
|
|
562
|
+
sessionKey: targetAgent.sessionKey,
|
|
563
|
+
});
|
|
564
|
+
} else {
|
|
565
|
+
const targetSession = st.sessions.find((s) => s.sessionKey === baseKey);
|
|
566
|
+
if (targetSession) {
|
|
567
|
+
pushNavTargetRef.current = null;
|
|
568
|
+
clearPendingPushNav();
|
|
569
|
+
dispatch({
|
|
570
|
+
type: "SELECT_SESSION",
|
|
571
|
+
sessionId: targetSession.id,
|
|
572
|
+
sessionKey: targetSession.sessionKey,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}, [dispatch]);
|
|
577
|
+
|
|
578
|
+
useEffect(() => {
|
|
579
|
+
function onPushNav(e: Event) {
|
|
580
|
+
const sk = (e as CustomEvent).detail?.sessionKey;
|
|
581
|
+
if (sk) navigateToPushTarget(sk);
|
|
582
|
+
}
|
|
583
|
+
window.addEventListener("botschat:push-nav", onPushNav);
|
|
584
|
+
const pending = getPendingPushNav();
|
|
585
|
+
if (pending) navigateToPushTarget(pending);
|
|
586
|
+
return () => window.removeEventListener("botschat:push-nav", onPushNav);
|
|
587
|
+
}, [navigateToPushTarget]);
|
|
588
|
+
|
|
509
589
|
// ---- WS message handler ----
|
|
510
590
|
const handleWSMessage = useCallback(
|
|
511
591
|
(msg: WSMessage) => {
|
|
@@ -815,10 +895,17 @@ export default function App() {
|
|
|
815
895
|
wsClientRef.current = client;
|
|
816
896
|
|
|
817
897
|
// Initialize push notifications and foreground detection
|
|
818
|
-
initPushNotifications()
|
|
819
|
-
|
|
898
|
+
initPushNotifications()
|
|
899
|
+
.then(() => {
|
|
900
|
+
const pending = getPendingPushNav();
|
|
901
|
+
if (pending) navigateToPushTarget(pending);
|
|
902
|
+
})
|
|
903
|
+
.catch((err) => {
|
|
904
|
+
dlog.warn("Push", `Push init failed: ${err}`);
|
|
905
|
+
});
|
|
906
|
+
const cleanupForeground = setupForegroundDetection(client, () => {
|
|
907
|
+
setForegroundResumeCount((c) => c + 1);
|
|
820
908
|
});
|
|
821
|
-
const cleanupForeground = setupForegroundDetection(client);
|
|
822
909
|
|
|
823
910
|
return () => {
|
|
824
911
|
cleanupForeground();
|
package/packages/web/src/api.ts
CHANGED
|
@@ -122,7 +122,7 @@ async function request<T>(
|
|
|
122
122
|
// ---- Auth ----
|
|
123
123
|
export type AuthResponse = { id: string; email: string; token: string; refreshToken?: string; displayName?: string };
|
|
124
124
|
|
|
125
|
-
export type UserSettings = { defaultModel?: string };
|
|
125
|
+
export type UserSettings = { defaultModel?: string; notifyPreview?: boolean };
|
|
126
126
|
|
|
127
127
|
export type AuthConfig = {
|
|
128
128
|
emailEnabled: boolean;
|
|
@@ -147,7 +147,7 @@ export const authApi = {
|
|
|
147
147
|
|
|
148
148
|
// ---- User settings ----
|
|
149
149
|
export const meApi = {
|
|
150
|
-
updateSettings: (data: { defaultModel?: string }) =>
|
|
150
|
+
updateSettings: (data: { defaultModel?: string; notifyPreview?: boolean }) =>
|
|
151
151
|
request<{ ok: boolean; settings: UserSettings }>("PATCH", "/me", data),
|
|
152
152
|
};
|
|
153
153
|
|
|
@@ -650,8 +650,26 @@ export function ChatWindow({ sendMessage }: ChatWindowProps) {
|
|
|
650
650
|
</div>
|
|
651
651
|
)}
|
|
652
652
|
|
|
653
|
-
{/* Session tabs
|
|
654
|
-
{showSessionTabs &&
|
|
653
|
+
{/* Session tabs + model selector (mobile: inline with tabs) */}
|
|
654
|
+
{showSessionTabs && (
|
|
655
|
+
<div className="flex items-center flex-shrink-0">
|
|
656
|
+
<div className="flex-1 min-w-0">
|
|
657
|
+
<SessionTabs channelId={channelId} />
|
|
658
|
+
</div>
|
|
659
|
+
{isMobile && (
|
|
660
|
+
<div className="flex-shrink-0 pr-2" style={{ borderBottom: "1px solid var(--border)" }}>
|
|
661
|
+
<ModelSelect
|
|
662
|
+
value={currentModel ?? ""}
|
|
663
|
+
onChange={handleModelChange}
|
|
664
|
+
models={state.models}
|
|
665
|
+
disabled={!state.openclawConnected}
|
|
666
|
+
placeholder="No model"
|
|
667
|
+
compact
|
|
668
|
+
/>
|
|
669
|
+
</div>
|
|
670
|
+
)}
|
|
671
|
+
</div>
|
|
672
|
+
)}
|
|
655
673
|
|
|
656
674
|
{/* Messages – flat-row layout (overflow-x-hidden prevents horizontal scroll from long URLs/code) */}
|
|
657
675
|
<div className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { E2eService } from "../e2e";
|
|
3
|
+
import { meApi, authApi } from "../api";
|
|
3
4
|
import { AppStateContext } from "../store";
|
|
4
5
|
|
|
5
6
|
export function E2ESettings() {
|
|
@@ -10,6 +11,8 @@ export function E2ESettings() {
|
|
|
10
11
|
const [busy, setBusy] = useState(false);
|
|
11
12
|
const [error, setError] = useState<string | null>(null);
|
|
12
13
|
const [showPassword, setShowPassword] = useState(false);
|
|
14
|
+
const [notifyPreview, setNotifyPreview] = useState(false);
|
|
15
|
+
const [notifyPreviewLoading, setNotifyPreviewLoading] = useState(false);
|
|
13
16
|
|
|
14
17
|
// Subscribe to E2eService changes
|
|
15
18
|
useEffect(() => {
|
|
@@ -18,6 +21,28 @@ export function E2ESettings() {
|
|
|
18
21
|
});
|
|
19
22
|
}, []);
|
|
20
23
|
|
|
24
|
+
// Load notifyPreview preference from server
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
authApi.me().then((data) => {
|
|
27
|
+
if (data?.settings?.notifyPreview) {
|
|
28
|
+
setNotifyPreview(true);
|
|
29
|
+
}
|
|
30
|
+
}).catch(() => {});
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const handleNotifyPreviewToggle = async (enabled: boolean) => {
|
|
34
|
+
setNotifyPreview(enabled);
|
|
35
|
+
setNotifyPreviewLoading(true);
|
|
36
|
+
try {
|
|
37
|
+
await meApi.updateSettings({ notifyPreview: enabled });
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error("Failed to update notification preview setting:", err);
|
|
40
|
+
setNotifyPreview(!enabled);
|
|
41
|
+
} finally {
|
|
42
|
+
setNotifyPreviewLoading(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
21
46
|
const handleUnlock = async () => {
|
|
22
47
|
if (!password || !user) return;
|
|
23
48
|
setBusy(true);
|
|
@@ -133,9 +158,53 @@ export function E2ESettings() {
|
|
|
133
158
|
)}
|
|
134
159
|
</div>
|
|
135
160
|
|
|
161
|
+
{/* Notification Preview Toggle */}
|
|
162
|
+
<div className="p-4 rounded-md border" style={{ borderColor: "var(--border)", background: "var(--bg-surface)" }}>
|
|
163
|
+
<div className="flex items-center justify-between mb-3">
|
|
164
|
+
<div className="flex items-center gap-2">
|
|
165
|
+
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" style={{ color: "var(--text-secondary)" }}>
|
|
166
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
167
|
+
</svg>
|
|
168
|
+
<span className="text-caption font-bold" style={{ color: "var(--text-primary)" }}>
|
|
169
|
+
Notification Preview
|
|
170
|
+
</span>
|
|
171
|
+
</div>
|
|
172
|
+
<button
|
|
173
|
+
role="switch"
|
|
174
|
+
aria-checked={notifyPreview}
|
|
175
|
+
onClick={() => handleNotifyPreviewToggle(!notifyPreview)}
|
|
176
|
+
disabled={notifyPreviewLoading}
|
|
177
|
+
className="relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
|
|
178
|
+
style={{
|
|
179
|
+
background: notifyPreview ? "var(--bg-active, #6366f1)" : "var(--border)",
|
|
180
|
+
opacity: notifyPreviewLoading ? 0.5 : 1,
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
<span
|
|
184
|
+
className="inline-block h-4 w-4 rounded-full bg-white transition-transform"
|
|
185
|
+
style={{ transform: notifyPreview ? "translateX(1.375rem)" : "translateX(0.25rem)" }}
|
|
186
|
+
/>
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
<p className="text-caption mb-2" style={{ color: "var(--text-muted)" }}>
|
|
190
|
+
Show message text in push notifications on iOS and Android.
|
|
191
|
+
</p>
|
|
192
|
+
{notifyPreview && (
|
|
193
|
+
<div className="p-3 rounded border text-caption" style={{ borderColor: "var(--accent-yellow, #d69e2e)", background: "rgba(214, 158, 46, 0.08)", color: "var(--text-secondary)" }}>
|
|
194
|
+
<p className="font-bold mb-1" style={{ color: "var(--accent-yellow, #d69e2e)" }}>Security trade-off</p>
|
|
195
|
+
<ul className="list-disc ml-4 space-y-0.5">
|
|
196
|
+
<li>Message previews will briefly pass through our server to deliver notifications.</li>
|
|
197
|
+
<li>Previews are <strong>never stored</strong> — they exist only in memory during delivery.</li>
|
|
198
|
+
<li>Your full message history remains end-to-end encrypted.</li>
|
|
199
|
+
<li>Web browser notifications are not affected (they decrypt locally).</li>
|
|
200
|
+
</ul>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
136
205
|
<div className="text-caption" style={{ color: "var(--text-muted)" }}>
|
|
137
206
|
<p className="font-bold text-red-400 mb-1">Warning:</p>
|
|
138
|
-
<ul className="list-disc ml-
|
|
207
|
+
<ul className="list-disc ml-4 space-y-1">
|
|
139
208
|
<li>If you lose this password, your encrypted history is lost forever.</li>
|
|
140
209
|
<li>We do not store this password on our servers.</li>
|
|
141
210
|
<li>You must use the same password on all devices to access your history.</li>
|
|
@@ -60,6 +60,13 @@ export function MobileLayout({
|
|
|
60
60
|
// App.tsx auto-selects an agent on mount — that would navigate to an empty
|
|
61
61
|
// chat screen before sessions have loaded (issue #4a / #4b).
|
|
62
62
|
|
|
63
|
+
// Push notification tap → navigate to chat
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
function onPushNav() { setScreen("chat"); }
|
|
66
|
+
window.addEventListener("botschat:push-nav", onPushNav);
|
|
67
|
+
return () => window.removeEventListener("botschat:push-nav", onPushNav);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
63
70
|
// Navigate to thread when thread opens
|
|
64
71
|
React.useEffect(() => {
|
|
65
72
|
if (state.activeThreadId && screen === "chat") {
|
|
@@ -7,9 +7,13 @@ import { Capacitor } from "@capacitor/core";
|
|
|
7
7
|
import type { BotsChatWSClient } from "./ws";
|
|
8
8
|
import { dlog } from "./debug-log";
|
|
9
9
|
|
|
10
|
-
export function setupForegroundDetection(
|
|
10
|
+
export function setupForegroundDetection(
|
|
11
|
+
wsClient: BotsChatWSClient,
|
|
12
|
+
onResume?: () => void,
|
|
13
|
+
): () => void {
|
|
11
14
|
const notifyForeground = () => {
|
|
12
15
|
wsClient.send({ type: "foreground.enter" });
|
|
16
|
+
onResume?.();
|
|
13
17
|
dlog.info("Foreground", "Entered foreground");
|
|
14
18
|
};
|
|
15
19
|
|
package/packages/web/src/push.ts
CHANGED
|
@@ -15,6 +15,25 @@ import { E2eService } from "./e2e";
|
|
|
15
15
|
|
|
16
16
|
let initialized = false;
|
|
17
17
|
|
|
18
|
+
// ---- Push navigation (deep-link on notification tap) ----
|
|
19
|
+
|
|
20
|
+
let pendingNavSessionKey: string | null = null;
|
|
21
|
+
|
|
22
|
+
export function getPendingPushNav(): string | null {
|
|
23
|
+
return pendingNavSessionKey;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function clearPendingPushNav(): void {
|
|
27
|
+
pendingNavSessionKey = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function firePushNav(sessionKey: string): void {
|
|
31
|
+
pendingNavSessionKey = sessionKey;
|
|
32
|
+
window.dispatchEvent(
|
|
33
|
+
new CustomEvent("botschat:push-nav", { detail: { sessionKey } }),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
// ---- IndexedDB helpers for SW E2E key sync ----
|
|
19
38
|
|
|
20
39
|
const IDB_NAME = "botschat-sw";
|
|
@@ -183,15 +202,20 @@ async function initNativePush(): Promise<void> {
|
|
|
183
202
|
dlog.error("Push", "Native push registration failed", error);
|
|
184
203
|
});
|
|
185
204
|
|
|
186
|
-
// Data-only messages arrive here in foreground — suppress (WS handles it)
|
|
187
205
|
PushNotifications.addListener("pushNotificationReceived", (_notification) => {
|
|
188
206
|
dlog.info("Push", "Foreground native notification (suppressed)");
|
|
189
207
|
});
|
|
190
208
|
|
|
191
|
-
// User tapped a notification (app was in background)
|
|
192
209
|
PushNotifications.addListener("pushNotificationActionPerformed", (action) => {
|
|
193
210
|
dlog.info("Push", "Notification tapped", action);
|
|
194
|
-
//
|
|
211
|
+
// iOS: custom data nested under "custom" key; Android: at root level
|
|
212
|
+
const data = action.notification?.data;
|
|
213
|
+
const sessionKey: string | undefined =
|
|
214
|
+
data?.custom?.sessionKey || data?.sessionKey;
|
|
215
|
+
if (sessionKey) {
|
|
216
|
+
dlog.info("Push", `Push nav target: ${sessionKey}`);
|
|
217
|
+
firePushNav(sessionKey);
|
|
218
|
+
}
|
|
195
219
|
});
|
|
196
220
|
} catch (err) {
|
|
197
221
|
dlog.error("Push", "Native push init failed", err);
|
|
@@ -99,6 +99,7 @@ let ws = null;
|
|
|
99
99
|
let pingTimer = null;
|
|
100
100
|
let intentionalClose = false;
|
|
101
101
|
let userId = null;
|
|
102
|
+
let notifyPreview = false;
|
|
102
103
|
|
|
103
104
|
function buildWsUrl() {
|
|
104
105
|
let host = SERVER_URL.replace(/^https?:\/\//, "");
|
|
@@ -266,6 +267,11 @@ function handleMessage(msg) {
|
|
|
266
267
|
logSend(`[defaultModel.updated] ${msg.defaultModel}`);
|
|
267
268
|
break;
|
|
268
269
|
|
|
270
|
+
case "settings.notifyPreview":
|
|
271
|
+
notifyPreview = msg.enabled === true;
|
|
272
|
+
logRecv(`[settings.notifyPreview] enabled=${notifyPreview}`);
|
|
273
|
+
break;
|
|
274
|
+
|
|
269
275
|
default:
|
|
270
276
|
logWarn(`Unhandled message type: ${msg.type}`);
|
|
271
277
|
}
|
|
@@ -299,13 +305,17 @@ async function handleUserMessage(msg) {
|
|
|
299
305
|
messageId: randomUUID(),
|
|
300
306
|
});
|
|
301
307
|
} else {
|
|
302
|
-
|
|
308
|
+
const msgPayload = {
|
|
303
309
|
type: "agent.text",
|
|
304
310
|
sessionKey: msg.sessionKey,
|
|
305
311
|
text: replyText,
|
|
306
312
|
messageId: randomUUID(),
|
|
307
|
-
}
|
|
308
|
-
|
|
313
|
+
};
|
|
314
|
+
if (notifyPreview) {
|
|
315
|
+
msgPayload.notifyPreview = truncate(replyText, 100);
|
|
316
|
+
}
|
|
317
|
+
send(msgPayload);
|
|
318
|
+
logSend(`[agent.text] "${truncate(replyText, 60)}"${notifyPreview ? " +preview" : ""}`);
|
|
309
319
|
}
|
|
310
320
|
}
|
|
311
321
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as i}from"./index-Kr85Nj_-.js";const t=i("PushNotifications",{});export{t as PushNotifications};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/web-vKLTVUul.js","assets/index-Kr85Nj_-.js","assets/index-Bd_RDcgO.css"])))=>i.map(i=>d[i]);
|
|
2
|
-
import{r as p,f as r}from"./index-Kr85Nj_-.js";const o=p("App",{web:()=>r(()=>import("./web-vKLTVUul.js"),__vite__mapDeps([0,1,2])).then(e=>new e.AppWeb)});export{o as App};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/web-CUXjh_UA.js","assets/index-Kr85Nj_-.js","assets/index-Bd_RDcgO.css"])))=>i.map(i=>d[i]);
|
|
2
|
-
import{r as o,f as t}from"./index-Kr85Nj_-.js";const n=o("SocialLogin",{web:()=>t(()=>import("./web-CUXjh_UA.js"),__vite__mapDeps([0,1,2])).then(e=>new e.SocialLoginWeb)});export{n as SocialLogin};
|