diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9255bb7..6b8acea 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -165,8 +165,10 @@ export class AppComponent { await this.profileService.populate(); await this.relayStorage.initialize(); + await this.relayService.initialize(); - await this.relayService.connect(); + this.relayService.connect(); + await this.feedService.initialize(); // This service will perform data cleanup, etc. diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 64aa0cc..93b0436 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -136,7 +136,7 @@ export class HomeComponent { async follow(profile: DefaultProfile) { if (profile.checked) { await this.profileService.follow(profile.pubkeyhex, undefined, profile as any); - await this.feedService.downloadRecent([profile.pubkeyhex]); + this.feedService.downloadRecent([profile.pubkeyhex]); // Perform a detected changes now, since 'profileService.profiles.length' should be updated. this.#defaultsChanged.next(this.defaults); @@ -164,7 +164,7 @@ export class HomeComponent { // await this.profileService.follow(pubKeys[i].pubkeyhex, undefined, pubKeys[i] as any); // } - // await this.feedService.downloadRecent(pubKeys.map((p) => p.pubkeyhex)); + // this.feedService.downloadRecent(pubKeys.map((p) => p.pubkeyhex)); // // Perform a detected changes now, since 'profileService.profiles.length' should be updated. // this.cd.detectChanges(); diff --git a/src/app/note/note.component.html b/src/app/note/note.component.html index 9509127..a41420f 100644 --- a/src/app/note/note.component.html +++ b/src/app/note/note.component.html @@ -92,9 +92,9 @@
{{ event.created_at | ago }} - + >{{ event.created_at | ago }} +
diff --git a/src/app/people/people.component.ts b/src/app/people/people.component.ts index 4f31db0..fc6eccf 100644 --- a/src/app/people/people.component.ts +++ b/src/app/people/people.component.ts @@ -128,7 +128,7 @@ export class PeopleComponent { pubkey = this.utilities.ensureHexIdentifier(pubkey); await this.profileService.follow(pubkey); - await this.feedService.downloadRecent([pubkey]); + this.feedService.downloadRecent([pubkey]); } createFollow(): void { diff --git a/src/app/services/data.service.ts b/src/app/services/data.service.ts index 244efc2..beeb42b 100644 --- a/src/app/services/data.service.ts +++ b/src/app/services/data.service.ts @@ -88,7 +88,6 @@ export class DataService { downloadProfile(pubkey: string) { if (!pubkey) { - debugger; return; } diff --git a/src/app/services/feed.service.ts b/src/app/services/feed.service.ts index 185dfb7..43640e5 100644 --- a/src/app/services/feed.service.ts +++ b/src/app/services/feed.service.ts @@ -492,14 +492,15 @@ export class FeedService { // this.relayStorage.list(); } - async downloadRecent(pubkeys: string[]) { + /** Download the recent events by the specified pubkeys and then disconnects the subscription. */ + downloadRecent(pubkeys: string[]) { console.log('DOWNLOAD RECENT FOR:', pubkeys); const relay = this.relayService.relays[0]; - const backInTime = moment().subtract(12, 'hours').unix(); + // const backInTime = moment().subtract(12, 'hours').unix(); - // Start subscribing to our people feeds. - const sub = relay.sub([{ kinds: [1], since: backInTime, authors: pubkeys }], {}) as NostrSubscription; + // Start subscribing to our people feeds. // since: backInTime + const sub = relay.sub([{ kinds: [1], limit: 1000, authors: pubkeys }], {}) as NostrSubscription; sub.loading = true; @@ -519,6 +520,7 @@ export class FeedService { sub.on('eose', () => { // console.log('Initial load of people feed completed.'); sub.loading = false; + sub.unsub(); }); } diff --git a/src/app/services/profile.service.ts b/src/app/services/profile.service.ts index e9657c2..d148093 100644 --- a/src/app/services/profile.service.ts +++ b/src/app/services/profile.service.ts @@ -97,7 +97,7 @@ export class ProfileService { this.#profileRequested.next(pubkey); } - async downloadRecent(pubkey: string) { + downloadRecent(pubkey: string) { this.#profileRequested.next(pubkey); } diff --git a/src/app/services/relay.service.ts b/src/app/services/relay.service.ts index 8707c76..667de0a 100644 --- a/src/app/services/relay.service.ts +++ b/src/app/services/relay.service.ts @@ -19,11 +19,13 @@ import { ApplicationState } from './applicationstate.service'; export class RelayService { /** Default relays that the app has for users without extension. This follows the document structure as extension data. */ defaultRelays: any = { - 'wss://relay.nostr.info': { read: true, write: true }, + // 'wss://relay.damus.io': { read: true, write: false }, 'wss://nostr-pub.wellorder.net': { read: true, write: true }, + // 'wss://relay.nostr.info': { read: true, write: true }, 'wss://nostr.nordlysln.net': { read: true, write: true }, - 'wss://relay.damus.io': { read: true, write: false }, 'wss://relay.nostr.ch': { read: true, write: true }, + 'wss://nostr.v0l.io': { read: true, write: true }, + 'wss://nostr-relay.wlvs.space': { read: true, write: true }, }; #table; @@ -47,6 +49,8 @@ export class RelayService { sortOrder: 'asc' | 'desc' = 'asc'; subs: Sub[] = []; + + /** These are relay instances that have connection over WebSocket and holds a reference to database metadata for the relay. */ relays: NostrRelay[] = []; #relaysChanged: BehaviorSubject = new BehaviorSubject(this.relays); @@ -188,6 +192,24 @@ export class RelayService { this.connect(); } }); + + // Every time the list of profiles changes, we will re-sub to have a single subscription against all following: + this.profileService.profilesChanged$.subscribe(() => { + console.log('profilesChanged$!!!, SUBSCRIBE TO FOLLOWING!'); + for (let i = 0; i < this.relays.length; i++) { + this.subscribeToFollowing(this.relays[i]); + } + }); + } + + getActiveRelay(url: string) { + const index = this.relays.findIndex((r) => r.url == url); + + if (index == -1) { + return null; + } else { + return this.relays[index]; + } } /** Add an in-memory instance of relay and get stored metadata for it. */ @@ -340,7 +362,12 @@ export class RelayService { await this.relayStorage.delete(url); const relayIndex = this.relays.findIndex((r) => r.url == url); - this.relays.splice(relayIndex, 1); + let existingRelayInstance = this.relays.splice(relayIndex, 1); + + // Disconnect from the relay when we delete it. + if (existingRelayInstance.length > 0) { + existingRelayInstance[0].close(); + } this.relaysUpdated(); } @@ -378,6 +405,13 @@ export class RelayService { } async #connectToRelay(server: NostrRelayDocument, onConnected: any) { + const existingActiveRelay = this.getActiveRelay(server.id); + + // If the relay already exists, just return that and do nothing else. + if (existingActiveRelay) { + onConnected(existingActiveRelay); + } + // const relay = relayInit('wss://relay.nostr.info'); const relay = relayInit(server.id) as NostrRelay; @@ -395,11 +429,16 @@ export class RelayService { console.log(`NOTICE FROM ${relay?.url}`); }); - relay.connect(); - // Keep a reference of the metadata on the relay instance. relay.metadata = server; + try { + await relay.connect(); + } catch (err) { + console.log(err); + relay.metadata.error = 'Unable to connect.'; + } + await this.addRelay(relay); return relay; @@ -412,40 +451,66 @@ export class RelayService { // When finished, trigger an observable that we are connected. this.appState.connected(true); - const authors = this.profileService.profiles.map((p) => p.pubkey); + this.subscribeToFollowing(relay); + }); + } - // Append ourself to the authors list so we receive everything we publish to any relay. - authors.push(this.appState.getPublicKey()); + subscriptions: any = {}; - const backInTime = moment().subtract(120, 'minutes').unix(); + /** Subscribes to the following on the specific relay. */ + subscribeToFollowing(relay: Relay) { + const authors = this.profileService.profiles.map((p) => p.pubkey); - // Start subscribing to our people feeds. - const sub = relay.sub([{ kinds: [1], since: backInTime, authors: authors }], {}) as NostrSubscription; + // Append ourself to the authors list so we receive everything we publish to any relay. + authors.push(this.appState.getPublicKey()); - sub.loading = true; + // TODO: We must store last time user closed the application and use that as back in time value. + // const backInTime = moment().subtract(5, 'days').unix(); - // Keep all subscriptions around so we can close them when needed. - this.subs.push(sub); + // Start subscribing to our people feeds. // since: backInTime, + // TODO: MAYBE WE SHOULD UNSUB AND SUB AGAIN, OR WILL THIS OVERRIDE EXISTING SUB?! - sub.on('event', (originalEvent: any) => { - const event = this.eventService.processEvent(originalEvent); + const filters = authors.map((a) => { + return { kinds: [1], limit: 500, authors: [a] }; + }); - if (!event) { - return; - } + const sub = relay.sub(filters, {}) as NostrSubscription; - this.#persist(event); - }); + // If we are still waiting for "loading" after 30 seconds, retry the subscription. + setTimeout(() => { + console.log('Subscription Timeout Protection was triggered.', sub.loading); - sub.on('eose', () => { - // console.log('Initial load of people feed completed.'); - sub.loading = false; - }); + if (sub.loading) { + console.log('Unsubbing and restarting subscription.', relay); + sub.unsub(); + this.subscribeToFollowing(relay); + } + }, 5 * 60 * 1000); + + sub.loading = true; + + // Keep all subscriptions around so we can close them when needed. + this.subs.push(sub); + + sub.on('event', (originalEvent: any) => { + const event = this.eventService.processEvent(originalEvent); + + if (!event) { + return; + } + + this.#persist(event); + }); + + sub.on('eose', () => { + console.log('Initial load of people feed completed.'); + sub.loading = false; }); } async initialize() { - if (this.relays.length === 0) { + // If there are no relay metatadata in database, get it from extension or default + if (this.relayStorage.items.length === 0) { let relays; try { diff --git a/src/app/shared/event-actions/event-actions.component.ts b/src/app/shared/event-actions/event-actions.component.ts index 627eb9f..0ac9ce7 100644 --- a/src/app/shared/event-actions/event-actions.component.ts +++ b/src/app/shared/event-actions/event-actions.component.ts @@ -56,7 +56,7 @@ export class EventActionsComponent { // If not already following, add a full follow and download recent: if (!this.profile.follow) { await this.profileService.follow(this.profile.pubkey, circle); - await this.feedService.downloadRecent([this.profile.pubkey]); + this.feedService.downloadRecent([this.profile.pubkey]); } else { // If we already follow but just change the circle, do a smaller operation. await this.profileService.setCircle(this.profile.pubkey, circle); diff --git a/src/app/shared/profile-actions/profile-actions.component.ts b/src/app/shared/profile-actions/profile-actions.component.ts index 4e0bf18..c29736f 100644 --- a/src/app/shared/profile-actions/profile-actions.component.ts +++ b/src/app/shared/profile-actions/profile-actions.component.ts @@ -56,7 +56,7 @@ export class ProfileActionsComponent { // If not already following, add a full follow and download recent: if (!this.profile.follow) { await this.profileService.follow(this.profile.pubkey, circle); - await this.feedService.downloadRecent([this.profile.pubkey]); + this.feedService.downloadRecent([this.profile.pubkey]); } else { // If we already follow but just change the circle, do a smaller operation. await this.profileService.setCircle(this.profile.pubkey, circle); diff --git a/src/app/user/user.component.ts b/src/app/user/user.component.ts index 63ec203..2405070 100644 --- a/src/app/user/user.component.ts +++ b/src/app/user/user.component.ts @@ -126,7 +126,7 @@ export class UserComponent { async follow() { this.profile!.follow = true; await this.profiles.follow(this.pubkey!); - await this.feedService.downloadRecent([this.pubkey!]); + this.feedService.downloadRecent([this.pubkey!]); } tabIndex?: number;