Skip to content

Commit b28da57

Browse files
committed
ndk-mobile: nip55 support -- thanks to @chebizarro
1 parent 900f76a commit b28da57

File tree

17 files changed

+219
-593
lines changed

17 files changed

+219
-593
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "packages/expo-nip55"]
2+
path = packages/expo-nip55
3+
url = https://github.com/pablof7z/expo-nip55

ndk-mobile/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@bacons/text-decoder": "^0.0.0",
2222
"@nostr-dev-kit/ndk": "workspace:*",
2323
"@nostr-dev-kit/ndk-wallet": "workspace:*",
24+
"expo-nip55": "workspace:*",
2425
"react-native-get-random-values": "~1.11.0",
2526
"typescript-lru-cache": "^2.0.0",
2627
"zustand": "^5.0.2"

ndk-mobile/src/cache-adapter/sqlite.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ export class NDKCacheAdapterSqlite implements NDKCacheAdapter {
232232
this.bufferFlushTimer = setTimeout(() => this.flushWriteBuffer(), this.bufferFlushTimeout);
233233
}
234234
}
235-
236235

237236
async setEvent(event: NDKEvent, filters: NDKFilter[], relay?: NDKRelay): Promise<void> {
238237
this.onReady(async () => {

ndk-mobile/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './session.js';
33
export * from './wallet.js';
44
export * from './ndk.js';
55
export * from './user-profile.js';
6+
export * from './nip55.js';

ndk-mobile/src/hooks/nip55.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { SignerAppInfo } from "expo-nip55";
2+
import { getInstalledSignerApps } from "expo-nip55";
3+
import { useState, useEffect } from "react";
4+
5+
export function useNip55() {
6+
const [signerApps, setSignerApps] = useState<SignerAppInfo[]>([]);
7+
8+
useEffect(() => {
9+
getInstalledSignerApps().then(setSignerApps);
10+
}, [])
11+
12+
return {
13+
isAvailable: signerApps.length > 0,
14+
apps: signerApps,
15+
}
16+
}

ndk-mobile/src/hooks/session.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@ const useNDKSessionInit = () => {
3030

3131
const useFollows = () => useNDKSession(s => s.follows);
3232
const useMuteList = () => {
33-
const muteList = useNDKSession(s => s.muteList);
34-
const mutePubkey = useNDKSession(s => s.mutePubkey);
35-
return { muteList, mutePubkey };
33+
const muteListEvent = useNDKSession(s => s.muteListEvent);
34+
const mutedPubkeys = useNDKSession(s => s.mutedPubkeys);
35+
const mutedHashtags = useNDKSession(s => s.mutedHashtags);
36+
const mutedWords = useNDKSession(s => s.mutedWords);
37+
const mutedEventIds = useNDKSession(s => s.mutedEventIds);
38+
const mute = useNDKSession(s => s.mute);
39+
40+
return { mutedPubkeys, mutedHashtags, mutedWords, mutedEventIds, mute, muteListEvent };
3641
}
3742
const useSessionEvents = () => useNDKSession(s => s.events);
3843
const useWOT = () => useNDKSession(s => s.wot);

ndk-mobile/src/hooks/subscribe.ts

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import '@bacons/text-decoder/install';
22
import { createStore } from 'zustand/vanilla';
33
import { useStore } from 'zustand';
4-
import NDK, { NDKEvent, NDKFilter, NDKKind, NDKRelaySet, NDKSubscription, NDKSubscriptionOptions, wrapEvent } from '@nostr-dev-kit/ndk';
4+
import { NDKEvent, NDKFilter, NDKRelaySet, NDKSubscription, NDKSubscriptionOptions, wrapEvent } from '@nostr-dev-kit/ndk';
55
import { useCallback, useEffect, useMemo, useRef } from 'react';
66
import { useNDK } from './ndk.js';
77
import { useNDKSession } from '../stores/session/index.js';
@@ -173,6 +173,7 @@ const createSubscribeStore = <T extends NDKEvent>(bufferMs: number | false = 30)
173173
* @returns {boolean} eose - End of stored events flag
174174
* @returns {boolean} isSubscribed - Subscription status
175175
*/
176+
176177
export const useSubscribe = <T extends NDKEvent>(
177178
filters: NDKFilter[] | false,
178179
opts: UseSubscribeOptions = {},
@@ -181,7 +182,6 @@ export const useSubscribe = <T extends NDKEvent>(
181182
dependencies.push(!!filters);
182183

183184
const { ndk } = useNDK();
184-
const muteList = useNDKSession(s => s.muteList);
185185
const store = useMemo(() => createSubscribeStore<T>(opts?.bufferMs), dependencies);
186186
const storeInstance = useStore(store);
187187

@@ -202,22 +202,23 @@ export const useSubscribe = <T extends NDKEvent>(
202202
return undefined;
203203
}, [ndk, opts.relays]);
204204

205+
const isMutedEvent = useMuteFilter();
206+
205207
useEffect(() => {
206208
// go through the events and remove any that are from muted pubkeys
207209
storeInstance.events.forEach((event) => {
208-
if (muteList.has(event.pubkey)) {
210+
if (isMutedEvent(event)) {
209211
storeInstance.removeEventId(event.id);
210212
}
211213
});
212-
213-
}, [ muteList.size ])
214+
}, [ isMutedEvent ])
214215

215216
const handleEvent = useCallback(
216217
(event: NDKEvent) => {
217218
const id = event.tagId();
218219

219220
// if it's from a muted pubkey, we don't accept it
220-
if (opts?.includeMuted !== true && muteList.has(event.pubkey)) return false;
221+
if (opts?.includeMuted !== true && isMutedEvent(event)) return false;
221222

222223
if (opts?.includeDeleted !== true && event.isParamReplaceable() && event.hasTag('deleted')) {
223224
// We mark the event but we don't add the actual event, since
@@ -240,7 +241,7 @@ export const useSubscribe = <T extends NDKEvent>(
240241
storeInstance.addEvent(event as T);
241242
eventIds.current.set(id, event.created_at!);
242243
},
243-
[muteList, ...dependencies]
244+
[isMutedEvent, ...dependencies]
244245
);
245246

246247
const handleEose = () => {
@@ -285,3 +286,50 @@ export const useSubscribe = <T extends NDKEvent>(
285286
subscription: storeInstance.subscriptionRef,
286287
};
287288
};
289+
290+
291+
/**
292+
* Provides a function that filters events, according to the user's mute list.
293+
*/
294+
export function useMuteFilter() {
295+
const mutedPubkeys = useNDKSession(s => s.mutedPubkeys);
296+
const mutedHashtags = useNDKSession(s => s.mutedHashtags);
297+
const mutedWords = useNDKSession(s => s.mutedWords);
298+
const mutedEventIds = useNDKSession(s => s.mutedEventIds);
299+
300+
const isMutedEvent = useMemo(() => {
301+
const mutedWordsRegex = mutedWords.size > 0 ? new RegExp(Array.from(mutedWords).join('|'), 'i') : null;
302+
const _mutedHashtags = new Set<string>();
303+
mutedHashtags.forEach(h => _mutedHashtags.add(h.toLowerCase()));
304+
305+
return (event: NDKEvent) => {
306+
const start = performance.now();
307+
const tags = new Set(event.getMatchingTags('t').map(tag => tag[1].toLowerCase()));
308+
const taggedEvents = new Set(event.getMatchingTags('e').map(tag => tag[1]));
309+
taggedEvents.add(event.id);
310+
const hasMutedHashtag = setHasAnyIntersection(_mutedHashtags, tags);
311+
let res = false;
312+
313+
if (
314+
hasMutedHashtag ||
315+
mutedPubkeys.has(event.pubkey) ||
316+
(mutedWordsRegex && event.content.match(mutedWordsRegex))
317+
) res = true;
318+
319+
if (!res && setHasAnyIntersection(mutedEventIds, taggedEvents)) {
320+
res = true;
321+
}
322+
323+
return res;
324+
}
325+
}, [mutedPubkeys, mutedHashtags, mutedWords, mutedEventIds]);
326+
327+
return isMutedEvent;
328+
}
329+
330+
const setHasAnyIntersection = (set1: Set<string>, set2: Set<string>) => {
331+
for (const item of set1) {
332+
if (set2.has(item)) return true;
333+
}
334+
return false;
335+
}

0 commit comments

Comments
 (0)