refactor: add stronger types and optimize message sorting

- Add discriminated union types for ProtocolIdentifier (GroupIdentifier,
  LiveActivityIdentifier, DMIdentifier, NIP05Identifier, ChannelIdentifier)
- Optimize message sorting using reverse() instead of full sort (O(n) vs O(n log n))
- Add type narrowing in adapter resolveConversation methods
- Remove unused Observable import from ChatViewer
This commit is contained in:
Claude
2026-01-12 20:02:42 +00:00
parent 9d6cda2b6a
commit 74502b813e
5 changed files with 102 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
import { useMemo, useState, memo, useCallback, useRef, useEffect } from "react";
import { use$ } from "applesauce-react/hooks";
import { from, catchError, of, map, Observable } from "rxjs";
import { from, catchError, of, map } from "rxjs";
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
import { Loader2, Reply, Zap, AlertTriangle, RefreshCw } from "lucide-react";
import { getZapRequest } from "applesauce-common/helpers/zap";

View File

@@ -101,6 +101,12 @@ export class Nip29Adapter extends ChatProtocolAdapter {
async resolveConversation(
identifier: ProtocolIdentifier,
): Promise<Conversation> {
// This adapter only handles group identifiers
if (identifier.type !== "group") {
throw new Error(
`NIP-29 adapter cannot handle identifier type: ${identifier.type}`,
);
}
const groupId = identifier.value;
const relayUrl = identifier.relays?.[0];
@@ -367,7 +373,10 @@ export class Nip29Adapter extends ChatProtocolAdapter {
});
console.log(`[NIP-29] Timeline has ${messages.length} events`);
return messages.sort((a, b) => a.timestamp - b.timestamp);
// EventStore timeline returns events sorted by created_at desc,
// we need ascending order for chat. Since it's already sorted,
// just reverse instead of full sort (O(n) vs O(n log n))
return messages.reverse();
}),
);
}
@@ -413,7 +422,9 @@ export class Nip29Adapter extends ChatProtocolAdapter {
return this.eventToMessage(event, conversation.id);
});
return messages.sort((a, b) => a.timestamp - b.timestamp);
// loadMoreMessages returns events in desc order from relay,
// reverse for ascending chronological order
return messages.reverse();
}
/**

View File

@@ -84,11 +84,13 @@ export class Nip53Adapter extends ChatProtocolAdapter {
async resolveConversation(
identifier: ProtocolIdentifier,
): Promise<Conversation> {
const { pubkey, identifier: dTag } = identifier.value as {
kind: number;
pubkey: string;
identifier: string;
};
// This adapter only handles live-activity identifiers
if (identifier.type !== "live-activity") {
throw new Error(
`NIP-53 adapter cannot handle identifier type: ${identifier.type}`,
);
}
const { pubkey, identifier: dTag } = identifier.value;
const relayHints = identifier.relays || [];
const activePubkey = accountManager.active$.value?.pubkey;
@@ -311,7 +313,10 @@ export class Nip53Adapter extends ChatProtocolAdapter {
.filter((msg): msg is Message => msg !== null);
console.log(`[NIP-53] Timeline has ${messages.length} events`);
return messages.sort((a, b) => a.timestamp - b.timestamp);
// EventStore timeline returns events sorted by created_at desc,
// we need ascending order for chat. Since it's already sorted,
// just reverse instead of full sort (O(n) vs O(n log n))
return messages.reverse();
}),
);
}
@@ -380,7 +385,9 @@ export class Nip53Adapter extends ChatProtocolAdapter {
})
.filter((msg): msg is Message => msg !== null);
return messages.sort((a, b) => a.timestamp - b.timestamp);
// loadMoreMessages returns events in desc order from relay,
// reverse for ascending chronological order
return messages.reverse();
}
/**

View File

@@ -93,8 +93,15 @@ export class NipC7Adapter extends ChatProtocolAdapter {
throw new Error(`Failed to resolve NIP-05: ${identifier.value}`);
}
pubkey = resolved;
} else {
} else if (
identifier.type === "chat-partner" ||
identifier.type === "dm-recipient"
) {
pubkey = identifier.value;
} else {
throw new Error(
`NIP-C7 adapter cannot handle identifier type: ${identifier.type}`,
);
}
const activePubkey = accountManager.active$.value?.pubkey;

View File

@@ -120,14 +120,74 @@ export interface Message {
}
/**
* Protocol-specific identifier
* NIP-29 group identifier
*/
export interface GroupIdentifier {
type: "group";
/** Group ID (e.g., "bitcoin-dev") */
value: string;
/** Relay URL where the group is hosted (required for NIP-29) */
relays: string[];
}
/**
* NIP-53 live activity identifier
*/
export interface LiveActivityIdentifier {
type: "live-activity";
/** Address pointer for the live activity */
value: {
kind: 30311;
pubkey: string;
identifier: string;
};
/** Relay hints from naddr encoding */
relays?: string[];
}
/**
* NIP-C7/NIP-17 direct message identifier (resolved pubkey)
*/
export interface DMIdentifier {
type: "dm-recipient" | "chat-partner";
/** Recipient pubkey (hex) */
value: string;
/** Relay hints */
relays?: string[];
}
/**
* NIP-C7 NIP-05 identifier (needs resolution)
*/
export interface NIP05Identifier {
type: "chat-partner-nip05";
/** NIP-05 address to resolve */
value: string;
/** Relay hints */
relays?: string[];
}
/**
* NIP-28 channel identifier (future)
*/
export interface ChannelIdentifier {
type: "channel";
/** Channel creation event ID or address */
value: string;
/** Relay hints */
relays?: string[];
}
/**
* Protocol-specific identifier - discriminated union
* Returned by adapter parseIdentifier()
*/
export interface ProtocolIdentifier {
type: string; // e.g., 'dm-recipient', 'channel-event', 'group-id'
value: any; // Protocol-specific value
relays?: string[]; // Relay hints from bech32 encoding
}
export type ProtocolIdentifier =
| GroupIdentifier
| LiveActivityIdentifier
| DMIdentifier
| NIP05Identifier
| ChannelIdentifier;
/**
* Chat command parsing result