diff options
| author | Adam Soltys <[email protected]> | 2026-03-06 10:06:53 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-06 10:06:53 -0800 |
| commit | 47316e28150953909c71adfd1e9100c4e08024b1 (patch) | |
| tree | 5f383db13a8145fac312b53199d01174386aed61 /src/lib | |
| parent | 7e45e1b48c382f29c2d3bd114a4751a0f6b89d51 (diff) | |
| download | coinos-ui-47316e28150953909c71adfd1e9100c4e08024b1.tar.gz coinos-ui-47316e28150953909c71adfd1e9100c4e08024b1.zip | |
chat fixes
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/marmot.ts | 37 | ||||
| -rw-r--r-- | src/lib/messaging.ts | 7 | ||||
| -rw-r--r-- | src/lib/mls-relay.ts | 66 |
3 files changed, 73 insertions, 37 deletions
diff --git a/src/lib/marmot.ts b/src/lib/marmot.ts index ed2143c3..2eecbdb1 100644 --- a/src/lib/marmot.ts +++ b/src/lib/marmot.ts @@ -164,31 +164,30 @@ class CoinosNostrNetwork implements NostrNetworkInterface { // IndexedDB-backed KeyValueStoreBackend // --------------------------------------------------------------------------- -const IDB_NAME = "coinos-marmot"; const IDB_VERSION = 1; +const STORE_NAMES = ["group-state", "key-packages", "invites-received", "invites-unread", "invites-seen"]; + +let _idbPubkey: string | null = null; +let _idbCache: IDBDatabase | null = null; + +function openMarmotDb(pubkey?: string): Promise<IDBDatabase> { + const pk = pubkey || _idbPubkey || "default"; + if (_idbCache && _idbPubkey === pk) return Promise.resolve(_idbCache); -function openMarmotDb(): Promise<IDBDatabase> { return new Promise((resolve, reject) => { - const req = indexedDB.open(IDB_NAME, IDB_VERSION); + const dbName = `coinos-marmot-${pk.slice(0, 16)}`; + const req = indexedDB.open(dbName, IDB_VERSION); req.onupgradeneeded = () => { const db = req.result; - if (!db.objectStoreNames.contains("group-state")) { - db.createObjectStore("group-state"); - } - if (!db.objectStoreNames.contains("key-packages")) { - db.createObjectStore("key-packages"); - } - if (!db.objectStoreNames.contains("invites-received")) { - db.createObjectStore("invites-received"); - } - if (!db.objectStoreNames.contains("invites-unread")) { - db.createObjectStore("invites-unread"); - } - if (!db.objectStoreNames.contains("invites-seen")) { - db.createObjectStore("invites-seen"); + for (const name of STORE_NAMES) { + if (!db.objectStoreNames.contains(name)) db.createObjectStore(name); } }; - req.onsuccess = () => resolve(req.result); + req.onsuccess = () => { + _idbPubkey = pk; + _idbCache = req.result; + resolve(req.result); + }; req.onerror = () => reject(req.error); }); } @@ -316,6 +315,8 @@ export function getMarmotClient(pubkey: string): MarmotClient { _client = null; _inviteReader = null; _currentPubkey = pubkey; + _idbPubkey = pubkey; + _idbCache = null; const signer = new CoinosEventSigner(pubkey); const groupStateBackend = new IDBGroupStateBackend(); diff --git a/src/lib/messaging.ts b/src/lib/messaging.ts index 37a4c22e..ae91709c 100644 --- a/src/lib/messaging.ts +++ b/src/lib/messaging.ts @@ -68,10 +68,5 @@ export async function resolveUser(pubkey: string): Promise<any> { /** Get a display name for a user info object. */ export function displayName(info: any): string { - return info?.coinosUsername || info?.nostrName || shortPubkey(info?.pubkey || ""); -} - -function shortPubkey(pk: string): string { - if (pk.length <= 16) return pk; - return pk.slice(0, 8) + "…" + pk.slice(-4); + return info?.coinosUsername || info?.nostrName || "Anonymous"; } diff --git a/src/lib/mls-relay.ts b/src/lib/mls-relay.ts index 6a280a3e..c8ac2241 100644 --- a/src/lib/mls-relay.ts +++ b/src/lib/mls-relay.ts @@ -11,22 +11,19 @@ import { getMarmotClient, getInviteReader, DM_RELAYS } from "$lib/marmot"; const pool = new SimplePool(); -/** Extract member pubkeys from an MLS group's ratchet tree. */ -function getGroupMembers(group: any): string[] { - const state = group.state; +/** Extract member pubkeys from an MLS group's ratchet tree (pass group.state). */ +function getGroupMembers(state: any): string[] { if (!state?.ratchetTree) return []; const pubkeys = new Set<string>(); for (const node of state.ratchetTree) { - if (node?.nodeType === 1 && node.leaf?.credential?.credentialType === 0) { - // Basic credential: identity is the pubkey bytes - const identity = node.leaf.credential.identity; - if (identity) { - const hex = typeof identity === "string" - ? identity - : bytesToHex(new Uint8Array(identity)); - if (hex.length === 64) pubkeys.add(hex); - } - } + if (!node?.leaf?.credential?.identity) continue; + const identity = node.leaf.credential.identity; + const hex = identity instanceof Uint8Array + ? bytesToHex(identity) + : typeof identity === "string" ? identity + : bytesToHex(new Uint8Array(identity)); + // identity may be 32 raw bytes (-> 64 hex) or already 64 hex chars + if (hex.length === 64) pubkeys.add(hex); } return Array.from(pubkeys); } @@ -307,6 +304,30 @@ export async function subscribeToMessages( } }); + // Fetch historical welcomes so we join groups created while we were offline + try { + const historicalWraps = await pool.querySync(relays, { + kinds: [1059], + "#p": [user.pubkey], + limit: 100, + }); + for (const event of historicalWraps) { + try { + const ingested = await inviteReader.ingestEvent(event as NostrEvent); + if (ingested) await inviteReader.decryptGiftWraps(); + } catch {} + } + // Re-load groups after processing welcomes + await client.loadAllGroups(); + for (const g of client.groups) { + registerGroupIds(g); + const r = g.relays; + if (r) r.forEach((url: string) => allRelayUrls.add(url)); + } + } catch (e) { + console.error("[mls] Failed to fetch historical welcomes:", e); + } + for (const url of allRelayUrls) { try { const relay = await Relay.connect(url); @@ -444,6 +465,25 @@ async function handleGroupMessageEvent( export async function fetchGroupHistory(user: any, relays: string[] = DM_RELAYS): Promise<void> { const client = getMarmotClient(user.pubkey); + const inviteReader = getInviteReader(user.pubkey); + + // Process any pending welcomes first so we know about all our groups + try { + const wraps = await pool.querySync(relays, { + kinds: [1059], + "#p": [user.pubkey], + limit: 100, + }); + for (const event of wraps) { + try { + const ingested = await inviteReader.ingestEvent(event as NostrEvent); + if (ingested) await inviteReader.decryptGiftWraps(); + } catch {} + } + } catch (e) { + console.error("[mls] Failed to fetch welcomes in history:", e); + } + await client.loadAllGroups(); const groups = client.groups; |
