Fix issues from the 2.0.0 release review (#137)

This commit is contained in:
MrAlders0n authored and GitHub committed 2026-10-02 19:04:46 -04:00
1 parent 4b9e48a35a
commit acf46342a1
100 files changed
+1410 -474

No files matched your search

+13 -7
View File
@@ -183,26 +183,32 @@ export function getScopes(iatas?: string[]): Promise<string[]> {
// Wrap a bare-array endpoint into a CursorPage so it can drive the cursor-paginated hooks. A page that
// fills the limit may have more behind it; the next cursor is the last (boundary) row's sort key.
function toCursorPage<T>(items: T[], limit: number, cursorOf: (last: T) => number): CursorPage<T> {
function toCursorPage<T, C = number>(items: T[], limit: number, cursorOf: (last: T) => C): CursorPage<T, C> {
const hasMore = items.length === limit;
return { items, nextCursor: hasMore ? cursorOf(items[items.length - 1]!) : null, hasMore };
}
// Known routes. /routes returns a bare array ordered last_seen DESC; its cursor pages by last_seen (ms),
// so the last row carries the page's smallest last_seen — the cursor for the next (older) batch. We wrap
// Known routes. /routes returns a bare array ordered last_seen DESC, id DESC; the next (older) batch
// starts after the last row's lastSeen (ms), with its id breaking ties inside that millisecond. We wrap
// it into a CursorPage here so RouteTable can stream pages via useInfinitePages. `iata` is a single code
// ("" = all), unlike the comma-separated `iatas` used elsewhere.
export interface RouteCursor {
lastSeen: number;
id: number;
}
export async function getKnownRoutesPage(
params?: { iata?: string; hopCount?: number; cursor?: number; limit?: number },
): Promise<CursorPage<KnownRoute>> {
params?: { iata?: string; hopCount?: number; cursor?: RouteCursor; limit?: number },
): Promise<CursorPage<KnownRoute, RouteCursor>> {
const limit = params?.limit ?? DEFAULT_PAGE_SIZE;
const items = await request<KnownRoute[]>("/routes", {
iata: params?.iata,
hopCount: params?.hopCount,
cursor: params?.cursor,
cursor: params?.cursor?.lastSeen,
cursorId: params?.cursor?.id,
limit,
});
return toCursorPage(items, limit, (r) => r.lastSeen);
return toCursorPage(items, limit, (r) => ({ lastSeen: r.lastSeen, id: r.id }));
}
export function getRouteEvidence(iata: string, pathKey: string, params: { range?: string; since?: number; until?: number; pageCursor?: string; limit?: number }, signal?: AbortSignal): Promise<RouteEvidence> {
+9 -4
View File
@@ -39,6 +39,8 @@ export class WsManager {
private pingTimer: ReturnType<typeof setInterval> | null = null;
private msgCounter = 0;
private lastEventTimestamp: number = Date.now();
// liveness for the ping check; unlike lastEventTimestamp it restarts with each connection
private lastHeardAt: number = Date.now();
private packetHandlers: PacketHandler[] = [];
private laggedHandlers: LaggedHandler[] = [];
@@ -190,6 +192,7 @@ export class WsManager {
case "hello": {
const isReconnect = this.everConnected;
this.everConnected = true;
this.lastHeardAt = Date.now();
this.setStatus("connected");
this.startPing();
this.sendSubscribe();
@@ -219,11 +222,11 @@ export class WsManager {
case "pong":
// a pong proves the link is alive, so it counts as recent activity
this.lastEventTimestamp = Date.now();
this.lastEventTimestamp = this.lastHeardAt = Date.now();
break;
case "event":
this.lastEventTimestamp = Date.now();
this.lastEventTimestamp = this.lastHeardAt = Date.now();
if (msg.event === "packetObservation") {
for (const handler of this.packetHandlers) {
handler(msg.data);
@@ -245,7 +248,7 @@ export class WsManager {
case "lagged":
// a lag notice is still server traffic, so it counts as recent activity
this.lastEventTimestamp = Date.now();
this.lastEventTimestamp = this.lastHeardAt = Date.now();
for (const handler of this.laggedHandlers) {
handler(msg);
}
@@ -257,6 +260,8 @@ export class WsManager {
}
private sendSubscribe(): void {
// any subscribe still awaiting its ack is superseded, even when nothing replaces it
this.lastSubscribeId = null;
// the server reads an empty IATA list as "all", so a region with no IATAs subscribes to nothing
if (!this.filter || this.filter.iatas?.length === 0) return;
const id = `sub-${this.nextId()}`;
@@ -276,7 +281,7 @@ export class WsManager {
private startPing(): void {
if (this.pingTimer) clearInterval(this.pingTimer); // a second hello must not double the interval
this.pingTimer = setInterval(() => {
if (Date.now() - this.lastEventTimestamp > WS_PING_INTERVAL_MS * 2 + 5_000) {
if (Date.now() - this.lastHeardAt > WS_PING_INTERVAL_MS * 2 + 5_000) {
// pongs stopped coming back — the link is half-open, rebuild it
this.forceReconnect();
return;