fuzzyworld

north-star / mobile / files / src / features / friends / sync.ts

Branch mobile · default branch · all branches

sync.ts

Blame: the commit that last changed each line · file · history

4e701dc7 2026-03-16 Pavel Safronov1import { ref, onValue } from 'firebase/database';
b4e7f682 2026-03-16 Pavel Safronov2import type { Database } from 'firebase/database';
f32f5e95 2026-03-16 Pavel Safronov3import { decryptJson } from '../../core/crypto';
92f24c4e 2026-03-16 Pavel Safronov4
e790982a 2026-03-16 Pavel Safronov5type FriendRecord = {
92f24c4e 2026-03-16 Pavel Safronov6 id?: string;
b4e7f682 2026-03-16 Pavel Safronov7 name?: string;
8 color?: string;
9 position?: { lat: number; lon: number };
10 timestamp?: number;
11 lastSeen?: number;
92f24c4e 2026-03-16 Pavel Safronov12 encryptedState?: string;
13 encryptedPosition?: string;
14 [key: string]: unknown;
15};
36d64b37 2026-03-15 Pavel Safronov16
17/**
e790982a 2026-03-16 Pavel Safronov18 * Subscribes to the friends node for a room, decrypting each record
19 * as needed. Calls onUpdate(friends) after every snapshot resolves.
36d64b37 2026-03-15 Pavel Safronov20 *
21 * @param {object} opts
22 * @param {object} opts.db - Firebase Database instance
23 * @param {string} opts.roomId
24 * @param {CryptoKey|null} opts.roomKey - AES-GCM key, or null for unencrypted rooms
e790982a 2026-03-16 Pavel Safronov25 * @param {function} opts.onUpdate - (friends: object[]) => void
26 * @returns {{ friends: object[] }} Shared array reference, mutated in place on each update
36d64b37 2026-03-15 Pavel Safronov27 */
b4e7f682 2026-03-16 Pavel Safronov28export function subscribeToFriends({
29 db,
30 roomId,
31 roomKey,
32 onUpdate
33}: {
34 db: Database;
35 roomId: string;
36 roomKey: CryptoKey | null;
37 onUpdate: (friends: FriendRecord[]) => void;
38}) {
e790982a 2026-03-16 Pavel Safronov39 const friendsRef = ref(db, `rooms/${roomId}/friends`);
b4e7f682 2026-03-16 Pavel Safronov40 const friends: FriendRecord[] = [];
36d64b37 2026-03-15 Pavel Safronov41 let snapshotVersion = 0;
42
e790982a 2026-03-16 Pavel Safronov43 onValue(friendsRef, async (snapshot) => {
36d64b37 2026-03-15 Pavel Safronov44 const version = ++snapshotVersion;
45
e790982a 2026-03-16 Pavel Safronov46 const friendsData = snapshot.val();
47 if (friendsData) {
48 const rawFriends = Object.values(friendsData as Record<string, FriendRecord>);
b4e7f682 2026-03-16 Pavel Safronov49 const decrypted = await Promise.all(rawFriends.map(async (p): Promise<FriendRecord | null> => {
36d64b37 2026-03-15 Pavel Safronov50 if (p.encryptedState && roomKey) {
51 try {
b4e7f682 2026-03-16 Pavel Safronov52 const decryptedState = await decryptJson(roomKey, p.encryptedState) as Record<string, unknown>;
cf7471ca 2026-03-16 Pavel Safronov53 return { ...decryptedState, lastSeen: p.lastSeen };
36d64b37 2026-03-15 Pavel Safronov54 } catch (err) {
e790982a 2026-03-16 Pavel Safronov55 console.error('Failed to decrypt friend state', p.id, err);
36d64b37 2026-03-15 Pavel Safronov56 return null;
57 }
58 }
59 if (p.encryptedPosition && roomKey) {
60 try {
b4e7f682 2026-03-16 Pavel Safronov61 const position = await decryptJson(roomKey, p.encryptedPosition) as { lat: number; lon: number };
36d64b37 2026-03-15 Pavel Safronov62 const { encryptedPosition: _stripped, ...rest } = p;
63 return { ...rest, position };
64 } catch (err) {
e790982a 2026-03-16 Pavel Safronov65 console.error('Failed to decrypt legacy friend position', p.id, err);
36d64b37 2026-03-15 Pavel Safronov66 return null;
67 }
68 }
69 return p;
70 }));
71 if (version !== snapshotVersion) return;
e790982a 2026-03-16 Pavel Safronov72 friends.length = 0;
b4e7f682 2026-03-16 Pavel Safronov73 friends.push(...decrypted.filter((item): item is FriendRecord => item !== null));
36d64b37 2026-03-15 Pavel Safronov74 } else {
75 if (version !== snapshotVersion) return;
e790982a 2026-03-16 Pavel Safronov76 friends.length = 0;
36d64b37 2026-03-15 Pavel Safronov77 }
78
e790982a 2026-03-16 Pavel Safronov79 onUpdate(friends);
36d64b37 2026-03-15 Pavel Safronov80 });
81
e790982a 2026-03-16 Pavel Safronov82 return { friends };
36d64b37 2026-03-15 Pavel Safronov83}