import { NETWORK_ACTIVITY_PROTOCOL_ORDER, orderNetworkActivityProtocolNames, } from './network-activity-visuals.js'; export interface IChartPoint { x: string | number; y: number; } export interface IProtocolConnectionHistoryPointLike { timestamp: number; total: number; protocols: Record; } export type TProtocolChartSeries = Array<{ name: string; data: IChartPoint[]; }>; export const PROTOCOL_ORDER = NETWORK_ACTIVITY_PROTOCOL_ORDER; export const PROTOCOL_SERIES_ORDER = ['Total Active', ...PROTOCOL_ORDER] as const; const getProtocolSeriesOrder = ( protocolNamesArg: string[], ): string[] => [ 'Total Active', ...orderNetworkActivityProtocolNames([ ...PROTOCOL_ORDER, ...protocolNamesArg, ]), ]; const toEpochSecond = (timestampArg: string | number): number => { const timestampMs = typeof timestampArg === 'number' ? timestampArg : new Date(timestampArg).getTime(); return Math.floor(timestampMs / 1000); }; export const getLatestHistoryEpochSecond = ( historyArg: Array<{ timestamp: number }>, ): number => historyArg.reduce( (latest, point) => Math.max(latest, Math.floor(point.timestamp / 1000)), 0, ); export const mergeChartPoints = ( localPointsArg: IChartPoint[], canonicalPointsArg: IChartPoint[], maximumPointsArg: number, ): IChartPoint[] => { const pointsBySecond = new Map(); for (const point of localPointsArg) { const epochSecond = toEpochSecond(point.x); if (Number.isFinite(epochSecond)) { pointsBySecond.set(epochSecond, point); } } // Canonical engine samples win when a locally repeated cached sample used the same second. for (const point of canonicalPointsArg) { const epochSecond = toEpochSecond(point.x); if (Number.isFinite(epochSecond)) { pointsBySecond.set(epochSecond, point); } } return [...pointsBySecond.entries()] .sort(([secondA], [secondB]) => secondA - secondB) .map(([, point]) => point) .slice(-maximumPointsArg); }; export const createProtocolConnectionSeries = ( historyArg: IProtocolConnectionHistoryPointLike[] = [], ): TProtocolChartSeries => getProtocolSeriesOrder( historyArg.flatMap((point) => Object.keys(point.protocols)), ).map((seriesName) => ({ name: seriesName, data: historyArg.map((point) => ({ x: new Date(point.timestamp).toISOString(), y: seriesName === 'Total Active' ? point.total : (point.protocols[seriesName] ?? 0), })), })); export const mergeProtocolConnectionHistory = ( localSeriesArg: TProtocolChartSeries, canonicalHistoryArg: IProtocolConnectionHistoryPointLike[], maximumPointsArg: number, ): TProtocolChartSeries => { const localSeriesByName = new Map(localSeriesArg.map((series) => [series.name, series])); const canonicalSeriesByName = new Map( createProtocolConnectionSeries(canonicalHistoryArg) .map((series) => [series.name, series]), ); const seriesOrder = getProtocolSeriesOrder([ ...localSeriesArg.map((series) => series.name), ...canonicalHistoryArg.flatMap((point) => Object.keys(point.protocols)), ]); return seriesOrder.map((seriesName) => ({ name: seriesName, data: mergeChartPoints( localSeriesByName.get(seriesName)?.data ?? [], canonicalSeriesByName.get(seriesName)?.data ?? [], maximumPointsArg, ), })); }; export const getProtocolConnectionTotal = ( protocolsArg: Record, ): number => Object.entries(protocolsArg) .filter(([name]) => name !== 'WebSocket') .reduce((total, [, count]) => total + count, 0); export const appendProtocolConnectionSample = ( localSeriesArg: TProtocolChartSeries, timestampArg: string | number, protocolsArg: Record, maximumPointsArg: number, ): TProtocolChartSeries => { const localSeriesByName = new Map(localSeriesArg.map((series) => [series.name, series])); const seriesOrder = getProtocolSeriesOrder([ ...localSeriesArg.map((series) => series.name), ...Object.keys(protocolsArg), ]); return seriesOrder.map((seriesName) => ({ name: seriesName, data: mergeChartPoints( localSeriesByName.get(seriesName)?.data ?? [], [{ x: timestampArg, y: seriesName === 'Total Active' ? getProtocolConnectionTotal(protocolsArg) : (protocolsArg[seriesName] ?? 0), }], maximumPointsArg, ), })); }; export const shouldRestartNetworkActivityTimer = ( isConnectedArg: boolean, documentHiddenArg: boolean, ): boolean => isConnectedArg && !documentHiddenArg;