mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 00:17:02 +02:00
fix(chat): fix TypeScript errors and add GroupLink Communikey fallback
- Fix test errors by making tests async and using rejects.toThrow() - Fix pool.connectedRelays → pool.relays (correct API) - Export isCommunikey and isValidPubkey helpers for reuse - Add Communikey detection to GroupLink component - When clicking a group from kind 10009 list - Checks if group ID is pubkey with kind 10222 - Automatically uses Communikey protocol - Fix unused parameter warning in communikey adapter This enables Communikey fallback both from: 1. Command line: chat relay.com'<pubkey> 2. Group lists: clicking groups stored in kind 10009
This commit is contained in:
@@ -2,6 +2,7 @@ import { MessageSquare } from "lucide-react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getTagValue } from "applesauce-core/helpers";
|
||||
import { isCommunikey } from "@/lib/chat-parser";
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
|
||||
/**
|
||||
@@ -56,16 +57,29 @@ export function GroupLink({
|
||||
? getTagValue(metadata, "picture")
|
||||
: undefined;
|
||||
|
||||
const handleClick = () => {
|
||||
// Open chat with properly structured ProtocolIdentifier
|
||||
addWindow("chat", {
|
||||
protocol: "nip-29",
|
||||
identifier: {
|
||||
type: "group",
|
||||
value: groupId,
|
||||
relays: [relayUrl],
|
||||
},
|
||||
});
|
||||
const handleClick = async () => {
|
||||
// Check if this is a Communikey (group ID is pubkey with kind 10222)
|
||||
if (await isCommunikey(groupId, [relayUrl])) {
|
||||
console.log(`[GroupLink] Detected Communikey: ${groupId.slice(0, 8)}...`);
|
||||
addWindow("chat", {
|
||||
protocol: "communikey",
|
||||
identifier: {
|
||||
type: "communikey",
|
||||
value: groupId,
|
||||
relays: [relayUrl],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Standard NIP-29 group
|
||||
addWindow("chat", {
|
||||
protocol: "nip-29",
|
||||
identifier: {
|
||||
type: "group",
|
||||
value: groupId,
|
||||
relays: [relayUrl],
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,8 +4,8 @@ import { parseChatCommand } from "./chat-parser";
|
||||
|
||||
describe("parseChatCommand", () => {
|
||||
describe("NIP-29 relay groups", () => {
|
||||
it("should parse NIP-29 group ID without protocol (single arg)", () => {
|
||||
const result = parseChatCommand(["groups.0xchat.com'chachi"]);
|
||||
it("should parse NIP-29 group ID without protocol (single arg)", async () => {
|
||||
const result = await parseChatCommand(["groups.0xchat.com'chachi"]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
expect(result.identifier).toEqual({
|
||||
@@ -16,9 +16,9 @@ describe("parseChatCommand", () => {
|
||||
expect(result.adapter.protocol).toBe("nip-29");
|
||||
});
|
||||
|
||||
it("should parse NIP-29 group ID when split by shell-quote", () => {
|
||||
it("should parse NIP-29 group ID when split by shell-quote", async () => {
|
||||
// shell-quote splits on ' so "groups.0xchat.com'chachi" becomes ["groups.0xchat.com", "chachi"]
|
||||
const result = parseChatCommand(["groups.0xchat.com", "chachi"]);
|
||||
const result = await parseChatCommand(["groups.0xchat.com", "chachi"]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
expect(result.identifier).toEqual({
|
||||
@@ -29,8 +29,8 @@ describe("parseChatCommand", () => {
|
||||
expect(result.adapter.protocol).toBe("nip-29");
|
||||
});
|
||||
|
||||
it("should parse NIP-29 group ID with wss:// protocol (single arg)", () => {
|
||||
const result = parseChatCommand(["wss://groups.0xchat.com'chachi"]);
|
||||
it("should parse NIP-29 group ID with wss:// protocol (single arg)", async () => {
|
||||
const result = await parseChatCommand(["wss://groups.0xchat.com'chachi"]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
expect(result.identifier).toEqual({
|
||||
@@ -40,8 +40,11 @@ describe("parseChatCommand", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse NIP-29 group ID with wss:// when split by shell-quote", () => {
|
||||
const result = parseChatCommand(["wss://groups.0xchat.com", "chachi"]);
|
||||
it("should parse NIP-29 group ID with wss:// when split by shell-quote", async () => {
|
||||
const result = await parseChatCommand([
|
||||
"wss://groups.0xchat.com",
|
||||
"chachi",
|
||||
]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
expect(result.identifier).toEqual({
|
||||
@@ -51,24 +54,27 @@ describe("parseChatCommand", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse NIP-29 group with different relay and group-id (single arg)", () => {
|
||||
const result = parseChatCommand(["relay.example.com'bitcoin-dev"]);
|
||||
it("should parse NIP-29 group with different relay and group-id (single arg)", async () => {
|
||||
const result = await parseChatCommand(["relay.example.com'bitcoin-dev"]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
expect(result.identifier.value).toBe("bitcoin-dev");
|
||||
expect(result.identifier.relays).toEqual(["wss://relay.example.com"]);
|
||||
});
|
||||
|
||||
it("should parse NIP-29 group with different relay when split", () => {
|
||||
const result = parseChatCommand(["relay.example.com", "bitcoin-dev"]);
|
||||
it("should parse NIP-29 group with different relay when split", async () => {
|
||||
const result = await parseChatCommand([
|
||||
"relay.example.com",
|
||||
"bitcoin-dev",
|
||||
]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
expect(result.identifier.value).toBe("bitcoin-dev");
|
||||
expect(result.identifier.relays).toEqual(["wss://relay.example.com"]);
|
||||
});
|
||||
|
||||
it("should parse NIP-29 group from nos.lol", () => {
|
||||
const result = parseChatCommand(["nos.lol'welcome"]);
|
||||
it("should parse NIP-29 group from nos.lol", async () => {
|
||||
const result = await parseChatCommand(["nos.lol'welcome"]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
expect(result.identifier.value).toBe("welcome");
|
||||
@@ -77,39 +83,39 @@ describe("parseChatCommand", () => {
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should throw error when no identifier provided", () => {
|
||||
expect(() => parseChatCommand([])).toThrow(
|
||||
it("should throw error when no identifier provided", async () => {
|
||||
await expect(parseChatCommand([])).rejects.toThrow(
|
||||
"Chat identifier required. Usage: chat <identifier>",
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error for unsupported identifier format", () => {
|
||||
expect(() => parseChatCommand(["unsupported-format"])).toThrow(
|
||||
it("should throw error for unsupported identifier format", async () => {
|
||||
await expect(parseChatCommand(["unsupported-format"])).rejects.toThrow(
|
||||
/Unable to determine chat protocol/,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error for npub (NIP-C7 disabled)", () => {
|
||||
expect(() => parseChatCommand(["npub1xyz"])).toThrow(
|
||||
it("should throw error for npub (NIP-C7 disabled)", async () => {
|
||||
await expect(parseChatCommand(["npub1xyz"])).rejects.toThrow(
|
||||
/Unable to determine chat protocol/,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error for note/nevent (NIP-28 not implemented)", () => {
|
||||
expect(() => parseChatCommand(["note1xyz"])).toThrow(
|
||||
it("should throw error for note/nevent (NIP-28 not implemented)", async () => {
|
||||
await expect(parseChatCommand(["note1xyz"])).rejects.toThrow(
|
||||
/Unable to determine chat protocol/,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error for malformed naddr", () => {
|
||||
expect(() => parseChatCommand(["naddr1xyz"])).toThrow(
|
||||
it("should throw error for malformed naddr", async () => {
|
||||
await expect(parseChatCommand(["naddr1xyz"])).rejects.toThrow(
|
||||
/Unable to determine chat protocol/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NIP-53 live activity chat", () => {
|
||||
it("should parse NIP-53 live activity naddr", () => {
|
||||
it("should parse NIP-53 live activity naddr", async () => {
|
||||
const naddr = nip19.naddrEncode({
|
||||
kind: 30311,
|
||||
pubkey:
|
||||
@@ -118,7 +124,7 @@ describe("parseChatCommand", () => {
|
||||
relays: ["wss://relay.example.com"],
|
||||
});
|
||||
|
||||
const result = parseChatCommand([naddr]);
|
||||
const result = await parseChatCommand([naddr]);
|
||||
|
||||
expect(result.protocol).toBe("nip-53");
|
||||
expect(result.identifier).toEqual({
|
||||
@@ -134,7 +140,7 @@ describe("parseChatCommand", () => {
|
||||
expect(result.adapter.protocol).toBe("nip-53");
|
||||
});
|
||||
|
||||
it("should parse NIP-53 live activity naddr with multiple relays", () => {
|
||||
it("should parse NIP-53 live activity naddr with multiple relays", async () => {
|
||||
const naddr = nip19.naddrEncode({
|
||||
kind: 30311,
|
||||
pubkey:
|
||||
@@ -143,7 +149,7 @@ describe("parseChatCommand", () => {
|
||||
relays: ["wss://relay1.example.com", "wss://relay2.example.com"],
|
||||
});
|
||||
|
||||
const result = parseChatCommand([naddr]);
|
||||
const result = await parseChatCommand([naddr]);
|
||||
|
||||
expect(result.protocol).toBe("nip-53");
|
||||
expect(result.identifier.value).toEqual({
|
||||
@@ -158,7 +164,7 @@ describe("parseChatCommand", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not parse NIP-29 group naddr as NIP-53", () => {
|
||||
it("should not parse NIP-29 group naddr as NIP-53", async () => {
|
||||
const naddr = nip19.naddrEncode({
|
||||
kind: 39000,
|
||||
pubkey:
|
||||
@@ -168,7 +174,7 @@ describe("parseChatCommand", () => {
|
||||
});
|
||||
|
||||
// NIP-29 adapter should handle kind 39000
|
||||
const result = parseChatCommand([naddr]);
|
||||
const result = await parseChatCommand([naddr]);
|
||||
|
||||
expect(result.protocol).toBe("nip-29");
|
||||
});
|
||||
|
||||
@@ -17,15 +17,16 @@ import { toArray } from "rxjs/operators";
|
||||
/**
|
||||
* Check if a string is a valid hex pubkey (64 hex characters)
|
||||
*/
|
||||
function isValidPubkey(str: string): boolean {
|
||||
export function isValidPubkey(str: string): boolean {
|
||||
return /^[0-9a-f]{64}$/i.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect if a group ID is actually a Communikey (kind 10222)
|
||||
* Returns true if kind 10222 event found, false otherwise
|
||||
* Exported for use by GroupLink and other components
|
||||
*/
|
||||
async function isCommunikey(
|
||||
export async function isCommunikey(
|
||||
pubkey: string,
|
||||
relayHints: string[],
|
||||
): Promise<boolean> {
|
||||
@@ -45,10 +46,10 @@ async function isCommunikey(
|
||||
|
||||
try {
|
||||
// Use available relays for detection (relay hints + some connected relays)
|
||||
const relays = [
|
||||
...relayHints,
|
||||
...Array.from(pool.connectedRelays.keys()).slice(0, 3),
|
||||
].filter((r) => r);
|
||||
const connectedRelays = Array.from(pool.relays.keys()).slice(0, 3);
|
||||
const relays = [...relayHints, ...connectedRelays].filter(
|
||||
(r): r is string => !!r,
|
||||
);
|
||||
|
||||
if (relays.length === 0) {
|
||||
console.log("[Chat Parser] No relays available for Communikey detection");
|
||||
|
||||
@@ -88,15 +88,14 @@ export class CommunikeyAdapter extends ChatProtocolAdapter {
|
||||
|
||||
// Use user's outbox/general relays for fetching
|
||||
// TODO: Could use more sophisticated relay selection
|
||||
const fallbackRelays =
|
||||
identifier.relays.length > 0
|
||||
? identifier.relays
|
||||
: Array.from(pool.relays.keys()).slice(0, 5);
|
||||
|
||||
const definitionEvents = await firstValueFrom(
|
||||
pool
|
||||
.request(
|
||||
identifier.relays.length > 0
|
||||
? identifier.relays
|
||||
: Array.from(pool.connectedRelays.keys()).slice(0, 5),
|
||||
[definitionFilter],
|
||||
{ eventStore },
|
||||
)
|
||||
.request(fallbackRelays, [definitionFilter], { eventStore })
|
||||
.pipe(toArray()),
|
||||
);
|
||||
|
||||
@@ -441,7 +440,7 @@ export class CommunikeyAdapter extends ChatProtocolAdapter {
|
||||
* Get available actions for Communikey groups
|
||||
* Currently only bookmark/unbookmark (no join/leave - open participation)
|
||||
*/
|
||||
getActions(options?: GetActionsOptions): ChatAction[] {
|
||||
getActions(_options?: GetActionsOptions): ChatAction[] {
|
||||
const actions: ChatAction[] = [];
|
||||
|
||||
// Bookmark/unbookmark actions (same as NIP-29)
|
||||
|
||||
Reference in New Issue
Block a user