| 1 | import type { TimelineItem } from './types'; |
| 2 | |
| 3 | export const FEDERATION_LAG_MS = 60 * 1000; |
| 4 | |
| 5 | const decorated = new WeakMap<TimelineItem, { lag: number; item: TimelineItem }>(); |
| 6 | |
| 7 | function withLag(item: TimelineItem & { kind: 'event' }, lag: number): TimelineItem { |
| 8 | const hit = decorated.get(item); |
| 9 | if (hit && hit.lag === lag) return hit.item; |
| 10 | const next: TimelineItem = { ...item, event: { ...item.event, delivery_lag_ms: lag } }; |
| 11 | decorated.set(item, { lag, item: next }); |
| 12 | return next; |
| 13 | } |
| 14 | |
| 15 | /** |
| 16 | * Carries delivery lag forward from the previous list and measures it for events appended |
| 17 | * after the last one already known. `since` is when the room last became able to receive |
| 18 | * events, so catch-up after being offline or closed doesn't count as federation delay. |
| 19 | * Returns null when nothing in `items` was known before, meaning the list was replaced. |
| 20 | */ |
| 21 | export function observeArrivals(previous: TimelineItem[], items: TimelineItem[], since: number, now: number): TimelineItem[] | null { |
| 22 | const lags = new Map<string, number | undefined>(); |
| 23 | for (const item of previous) if (item.kind === 'event') lags.set(item.event.item_id, item.event.delivery_lag_ms); |
| 24 | let lastKnown = -1; |
| 25 | items.forEach((item, index) => { |
| 26 | if (item.kind === 'event' && lags.has(item.event.item_id)) lastKnown = index; |
| 27 | }); |
| 28 | if (lastKnown === -1) return null; |
| 29 | const next = items.map((item, index) => { |
| 30 | if (item.kind !== 'event') return item; |
| 31 | const event = item.event; |
| 32 | if (lags.has(event.item_id)) { |
| 33 | const carried = lags.get(event.item_id); |
| 34 | return carried === undefined ? item : withLag(item, carried); |
| 35 | } |
| 36 | if (index < lastKnown || event.own) return item; |
| 37 | const lag = now - Math.max(event.timestamp_ms, since); |
| 38 | return lag < FEDERATION_LAG_MS ? item : withLag(item, lag); |
| 39 | }); |
| 40 | return previous.length === next.length && next.every((item, index) => item === previous[index]) ? previous : next; |
| 41 | } |