connect to blastr and my relays or multiple default relays

This commit is contained in:
=
2023-03-08 23:31:44 +00:00
parent 3960f8f418
commit ae0e494098
5 changed files with 200 additions and 68 deletions

View File

@@ -6,7 +6,7 @@ Only javascript dependancy is [nostr-tools](https://github.com/nbd-wtf/nostr-too
## Features ## Features
Supported profile events: kind `0`, `2`, `10002` and `3`. Supported profile events: kind `0`, `10002` and `3`.
##### Backup and Restore ##### Backup and Restore
@@ -44,7 +44,6 @@ Supported profile events: kind `0`, `2`, `10002` and `3`.
##### Lightweight and Efficent ##### Lightweight and Efficent
- [ ] only javascript dependancy is nostr-tools (TODO: remove timeago) - [ ] only javascript dependancy is nostr-tools (TODO: remove timeago)
- [x] connects to the minimum number of relays - [x] connects to the minimum number of relays
- [ ] connect relays specified in `10002` or `2` - [x] connect relays specified in `10002` or 3 default relays
- [ ] if no `10002` or `2` events are found it crawls through a number of popular relays to ensure it has your latest profile events. (currently it just connects to damus)
- [x] minimises the number of open websockets - [x] minimises the number of open websockets
- [ ] use blastr relay to send profile events far and wide - [x] use blastr relay to send profile events far and wide

View File

@@ -1,23 +1,25 @@
import { Event, Relay, relayInit } from 'nostr-tools'; import { Event, SimplePool } from 'nostr-tools';
let drelay: Relay; const pool = new SimplePool();
export const setupDefaultRelays = async ():Promise<void> => { let currentrelays = [
if (typeof drelay !== 'undefined') return new Promise((r) => { r(); }); 'wss://relay.damus.io',
drelay = relayInit('wss://relay.damus.io'); 'wss://nostr-pub.wellorder.net',
return drelay.connect(); 'wss://nostr-relay.wlvs.space',
}; ];
/** setupMyRelays TODO */
export const setupMyRelays = async () => setupDefaultRelays();
export const requestMyProfileFromRelays = async ( export const requestMyProfileFromRelays = async (
pubkey:string, pubkey:string,
eventProcesser: (event: Event) => void, eventProcesser: (event: Event) => void,
relays?:string[],
) => { ) => {
await setupDefaultRelays(); if (relays) currentrelays = relays;
const sub = drelay.sub([{ const sub = pool.sub(
currentrelays,
[{
kinds: [0, 2, 10002, 3], kinds: [0, 2, 10002, 3],
authors: [pubkey], authors: [pubkey],
}]); }],
);
return new Promise<void>((r) => { return new Promise<void>((r) => {
sub.on('event', (event:Event) => { sub.on('event', (event:Event) => {
if ( if (
@@ -28,15 +30,13 @@ export const requestMyProfileFromRelays = async (
} }
}); });
sub.on('eose', () => { sub.on('eose', () => {
// sub.unsub();
r(); r();
}); });
}); });
}; };
export const publishEventToRelay = async (event:Event):Promise<boolean> => { export const publishEventToRelay = async (event:Event):Promise<boolean> => {
await setupDefaultRelays(); const pub = pool.publish(currentrelays, event);
const pub = drelay.publish(event);
return new Promise((r) => { return new Promise((r) => {
pub.on('ok', () => r(true)); pub.on('ok', () => r(true));
pub.on('failed', () => r(false)); pub.on('failed', () => r(false));

View File

@@ -205,6 +205,122 @@ describe('', () => {
}); });
}); });
describe('when isUptodate returns false', () => { describe('when isUptodate returns false', () => {
describe('and when cached 10002 event is present', () => {
const doBefore = async () => {
mockisUptodate.mockReturnValue(false);
mockrequestMyProfileFromRelays.mockReset()
.mockImplementation(async (_pubkey, eventProcessor) => {
eventProcessor({ ...SampleEvents.kind0 });
eventProcessor({ ...SampleEvents.kind3 });
});
await fetchMyProfileEvents(SampleEvents.kind0.pubkey, mockEventProcessor);
};
test('1 write relays, function called with custom relay and 2 default relays + blaster', async () => {
storeMyProfileEvent({
...SampleEvents.kind10002,
tags: [
['r', 'wss://alicerelay.example.com'],
],
});
await doBefore();
expect(mockrequestMyProfileFromRelays).toBeCalledWith(
expect.anything(),
expect.anything(),
[
'wss://alicerelay.example.com',
'wss://relay.damus.io',
'wss://nostr-pub.wellorder.net',
'wss://nostr.mutinywallet.com',
],
);
});
test('2 write relays function called with custom relays and 1 default relays + blaster', async () => {
storeMyProfileEvent({
...SampleEvents.kind10002,
tags: [
['r', 'wss://alicerelay.example.com'],
['r', 'wss://expensive-relay.example2.com', 'write'],
],
});
await doBefore();
expect(mockrequestMyProfileFromRelays).toBeCalledWith(
expect.anything(),
expect.anything(),
[
'wss://alicerelay.example.com',
'wss://expensive-relay.example2.com',
'wss://relay.damus.io',
'wss://nostr.mutinywallet.com',
],
);
});
test('2 write relays including first defauly relay. function called with custom relays and 1 different default relays + blaster', async () => {
storeMyProfileEvent({
...SampleEvents.kind10002,
tags: [
['r', 'wss://relay.damus.io'],
['r', 'wss://expensive-relay.example2.com', 'write'],
],
});
await doBefore();
expect(mockrequestMyProfileFromRelays).toBeCalledWith(
expect.anything(),
expect.anything(),
[
'wss://relay.damus.io',
'wss://expensive-relay.example2.com',
'wss://nostr-pub.wellorder.net',
'wss://nostr.mutinywallet.com',
],
);
});
test('with 4 write relays function called with all custom relays + blaster', async () => {
storeMyProfileEvent({
...SampleEvents.kind10002,
tags: [
['r', 'wss://alicerelay.example.com'],
['r', 'wss://brando-relay.com'],
['r', 'wss://expensive-relay.example2.com', 'write'],
['r', 'wss://alicerelay.example3.com'],
],
});
await doBefore();
expect(mockrequestMyProfileFromRelays).toBeCalledWith(
expect.anything(),
expect.anything(),
[
'wss://alicerelay.example.com',
'wss://brando-relay.com',
'wss://expensive-relay.example2.com',
'wss://alicerelay.example3.com',
'wss://nostr.mutinywallet.com',
],
);
});
test('custom read relays ignored', async () => {
storeMyProfileEvent({
...SampleEvents.kind10002,
tags: [
['r', 'wss://alicerelay.example.com'],
['r', 'wss://brando-relay.com'],
['r', 'wss://expensive-relay.example2.com', 'write'],
['r', 'wss://nostr-relay.example.com', 'read'],
],
});
await doBefore();
expect(mockrequestMyProfileFromRelays).toBeCalledWith(
expect.anything(),
expect.anything(),
[
'wss://alicerelay.example.com',
'wss://brando-relay.com',
'wss://expensive-relay.example2.com',
'wss://nostr.mutinywallet.com',
],
);
});
});
describe('and when no cached 10002 events are present', () => {
const mockstoreMyProfileEvent = jest.spyOn(FetchEvents, 'storeMyProfileEvent'); const mockstoreMyProfileEvent = jest.spyOn(FetchEvents, 'storeMyProfileEvent');
beforeEach(async () => { beforeEach(async () => {
mockisUptodate.mockReturnValue(false); mockisUptodate.mockReturnValue(false);
@@ -218,8 +334,8 @@ describe('', () => {
test('updateLastFetchDate called once', () => { test('updateLastFetchDate called once', () => {
expect(mockupdateLastFetchDate).toBeCalledTimes(1); expect(mockupdateLastFetchDate).toBeCalledTimes(1);
}); });
test('fetchCachedProfileEvent never called', () => { test('fetchCachedProfileEvent only to be called once to getRelays', () => {
expect(fetchCachedProfileEventSpy).toBeCalledTimes(0); expect(fetchCachedProfileEventSpy).toBeCalledTimes(1);
}); });
test('requestMyProfileFromRelays called', () => { test('requestMyProfileFromRelays called', () => {
expect(mockrequestMyProfileFromRelays).toBeCalledTimes(1); expect(mockrequestMyProfileFromRelays).toBeCalledTimes(1);
@@ -228,6 +344,19 @@ describe('', () => {
expect(mockrequestMyProfileFromRelays).toBeCalledWith( expect(mockrequestMyProfileFromRelays).toBeCalledWith(
SampleEvents.kind0.pubkey, SampleEvents.kind0.pubkey,
expect.anything(), expect.anything(),
expect.anything(),
);
});
test('mockrequestMyProfileFromRelays called with correct default relays', () => {
expect(mockrequestMyProfileFromRelays).toBeCalledWith(
expect.anything(),
expect.anything(),
[
'wss://relay.damus.io',
'wss://nostr-pub.wellorder.net',
'wss://nostr-relay.wlvs.space',
'wss://nostr.mutinywallet.com',
],
); );
}); });
test('eventProcessor called with events passed through by requestMyProfileFromRelays\'s event processor', async () => { test('eventProcessor called with events passed through by requestMyProfileFromRelays\'s event processor', async () => {
@@ -244,3 +373,4 @@ describe('', () => {
}); });
}); });
}); });
});

View File

@@ -92,6 +92,21 @@ export const fetchCachedProfileEvent = (kind: 0 | 2 | 10002 | 3): null | Event =
return a[0]; return a[0];
}; };
const getRelays = () => {
const e = fetchCachedProfileEvent(10002);
const mywriterelays = !e ? [] : e.tags.filter((r) => !r[2] || r[2] === 'write').map((r) => r[1]);
// return minimum of 3 relays + blastr, filling in with default relays (removing duplicates)
return [
...(mywriterelays.length > 3 ? mywriterelays : [...new Set([
...mywriterelays,
'wss://relay.damus.io',
'wss://nostr-pub.wellorder.net',
'wss://nostr-relay.wlvs.space',
])].slice(0, 3)),
'wss://nostr.mutinywallet.com', // blastr
];
};
/** get my latest profile events either from cache (if isUptodate) or from relays */ /** get my latest profile events either from cache (if isUptodate) or from relays */
export const fetchMyProfileEvents = async ( export const fetchMyProfileEvents = async (
pubkey:string, pubkey:string,
@@ -99,14 +114,10 @@ export const fetchMyProfileEvents = async (
): Promise<void> => { ): Promise<void> => {
// get events from relays, store them and run profileEventProcesser // get events from relays, store them and run profileEventProcesser
if (!isUptodate()) { if (!isUptodate()) {
/**
* TODO also run this if we havn't checked for x minutes and we aren't already
* listening on my write relays
*/
await requestMyProfileFromRelays(pubkey, (event: Event) => { await requestMyProfileFromRelays(pubkey, (event: Event) => {
storeMyProfileEvent(event); storeMyProfileEvent(event);
profileEventProcesser(event); profileEventProcesser(event);
}); }, getRelays());
// update last-fetch-from-relays date // update last-fetch-from-relays date
updateLastFetchDate(); updateLastFetchDate();
} else { } else {

View File

@@ -1,7 +1,6 @@
import { Event, UnsignedEvent } from 'nostr-tools'; import { Event, UnsignedEvent } from 'nostr-tools';
import { generateLogoHero, LoadProfileHome } from './LoadProfileHome'; import { generateLogoHero, LoadProfileHome } from './LoadProfileHome';
import { setupDefaultRelays, setupMyRelays } from './RelayManagement'; import { fetchMyProfileEvents } from './fetchEvents';
import { fetchCachedProfileEvent, fetchMyProfileEvents } from './fetchEvents';
import { localStorageGetItem, localStorageSetItem } from './LocalStorage'; import { localStorageGetItem, localStorageSetItem } from './LocalStorage';
import { LoadMetadataPage } from './LoadMetadataPage'; import { LoadMetadataPage } from './LoadMetadataPage';
import LoadContactsPage from './LoadContactsPage'; import LoadContactsPage from './LoadContactsPage';
@@ -25,13 +24,6 @@ const loadProfile = async () => {
(document.getElementById('navrelays') as HTMLElement).onclick = LoadRelaysPage; (document.getElementById('navrelays') as HTMLElement).onclick = LoadRelaysPage;
// load profile page (in loading mode) // load profile page (in loading mode)
LoadProfileHome(); LoadProfileHome();
// if my relays are known, connect to them
if (
fetchCachedProfileEvent(10002) !== null
|| fetchCachedProfileEvent(2) !== null
) await setupMyRelays();
// otherwise connect to default relays
else await setupDefaultRelays();
// load profile data // load profile data
await fetchMyProfileEvents( await fetchMyProfileEvents(
localStorageGetItem('pubkey') as string, localStorageGetItem('pubkey') as string,