Add in-app + real-time notifications (SignalR/mock, Iran-friendly)
- AppNotification + OnlineService.onNotification (hub event + mock periodic) — no FCM/APNs (blocked in Iran); uses the existing realtime channel - notification-store + pushNotification(); 🔔 bell with unread badge in TopBar, notifications screen, global toaster (plays notify sfx) - Wired events: daily reward, post-match achievements, friend requests - Closed-app push (Pushe/Najva/Chabok) noted as a later step (needs provider keys) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
} from "./service";
|
||||
import {
|
||||
AVATARS,
|
||||
AppNotification,
|
||||
AuthSession,
|
||||
ChatMessage,
|
||||
Conversation,
|
||||
@@ -463,6 +464,40 @@ export class MockOnlineService implements OnlineService {
|
||||
for (const cb of this.reactionCbs) cb(0, reaction);
|
||||
}
|
||||
|
||||
/* --------------------------- notifications ------------------------- */
|
||||
|
||||
private notifCbs = new Set<(n: AppNotification) => void>();
|
||||
private notifTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
onNotification(cb: (n: AppNotification) => void): Unsubscribe {
|
||||
this.notifCbs.add(cb);
|
||||
if (this.notifTimer == null) {
|
||||
const samples: Array<Pick<AppNotification, "kind" | "titleFa" | "titleEn" | "icon">> = [
|
||||
{ kind: "system", titleFa: "یک دوست آنلاین شد", titleEn: "A friend is online", icon: "👋" },
|
||||
{ kind: "system", titleFa: "مسابقهی امروز شروع شد", titleEn: "Today's event is live", icon: "🏆" },
|
||||
{ kind: "invite", titleFa: "یک نفر دنبال همبازیه", titleEn: "Someone is looking for a partner", icon: "🎴" },
|
||||
];
|
||||
this.notifTimer = setInterval(() => {
|
||||
if (this.notifCbs.size === 0) return;
|
||||
const s = pick(samples);
|
||||
const n: AppNotification = {
|
||||
id: rid("ntf"),
|
||||
ts: Date.now(),
|
||||
read: false,
|
||||
...s,
|
||||
};
|
||||
for (const c of this.notifCbs) c(n);
|
||||
}, 35000);
|
||||
}
|
||||
return () => {
|
||||
this.notifCbs.delete(cb);
|
||||
if (this.notifCbs.size === 0 && this.notifTimer) {
|
||||
clearInterval(this.notifTimer);
|
||||
this.notifTimer = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// The mock drives the game locally (game-store), so these are no-ops.
|
||||
readonly live = false;
|
||||
onState(): Unsubscribe { return () => {}; }
|
||||
|
||||
Reference in New Issue
Block a user