Cache user profile/settings events in IndexedDB
This commit is contained in:
@@ -4,6 +4,8 @@ import { EventStore } from 'applesauce-core/event-store';
|
|||||||
import { ProfileModel } from 'applesauce-core/models/profile';
|
import { ProfileModel } from 'applesauce-core/models/profile';
|
||||||
import { MailboxesModel } from 'applesauce-core/models/mailboxes';
|
import { MailboxesModel } from 'applesauce-core/models/mailboxes';
|
||||||
import { npubEncode } from 'applesauce-core/helpers/pointers';
|
import { npubEncode } from 'applesauce-core/helpers/pointers';
|
||||||
|
import { persistEventsToCache } from 'applesauce-core/helpers/event-cache';
|
||||||
|
import { NostrIDB, openDB } from 'nostr-idb';
|
||||||
|
|
||||||
const BOOTSTRAP_RELAYS = [
|
const BOOTSTRAP_RELAYS = [
|
||||||
'wss://purplepag.es',
|
'wss://purplepag.es',
|
||||||
@@ -26,10 +28,40 @@ export default class NostrDataService extends Service {
|
|||||||
_blossomSub = null;
|
_blossomSub = null;
|
||||||
|
|
||||||
_requestSub = null;
|
_requestSub = null;
|
||||||
|
_cachePromise = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
|
|
||||||
|
// Initialize the IndexedDB cache
|
||||||
|
this._cachePromise = openDB('applesauce-events').then(async (db) => {
|
||||||
|
this.cache = new NostrIDB(db, {
|
||||||
|
cacheIndexes: 1000,
|
||||||
|
maxEvents: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.cache.start();
|
||||||
|
|
||||||
|
// Automatically persist new events to the cache
|
||||||
|
this._stopPersisting = persistEventsToCache(
|
||||||
|
this.store,
|
||||||
|
async (events) => {
|
||||||
|
// Only cache profiles, mailboxes, and blossom servers
|
||||||
|
const toCache = events.filter(
|
||||||
|
(e) => e.kind === 0 || e.kind === 10002 || e.kind === 10063
|
||||||
|
);
|
||||||
|
|
||||||
|
if (toCache.length > 0) {
|
||||||
|
await Promise.all(toCache.map((event) => this.cache.add(event)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
batchTime: 1000, // Batch writes every 1 second
|
||||||
|
maxBatchSize: 100,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Feed events from the relay pool into the event store
|
// Feed events from the relay pool into the event store
|
||||||
this.nostrRelay.pool.relays$.subscribe(() => {
|
this.nostrRelay.pool.relays$.subscribe(() => {
|
||||||
// Setup relay subscription tracking if needed, or we just rely on request()
|
// Setup relay subscription tracking if needed, or we just rely on request()
|
||||||
@@ -63,24 +95,8 @@ export default class NostrDataService extends Service {
|
|||||||
|
|
||||||
const relayList = Array.from(relays);
|
const relayList = Array.from(relays);
|
||||||
|
|
||||||
// Request events and dump them into the store
|
// Setup models to track state reactively FIRST
|
||||||
this._requestSub = this.nostrRelay.pool
|
// This way, if cached events populate the store, the UI updates instantly.
|
||||||
.request(relayList, [
|
|
||||||
{
|
|
||||||
authors: [pubkey],
|
|
||||||
kinds: [0, 10002, 10063],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.subscribe({
|
|
||||||
next: (event) => {
|
|
||||||
this.store.add(event);
|
|
||||||
},
|
|
||||||
error: (err) => {
|
|
||||||
console.error('Error fetching profile events:', err);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup models to track state reactively
|
|
||||||
this._profileSub = this.store
|
this._profileSub = this.store
|
||||||
.model(ProfileModel, pubkey)
|
.model(ProfileModel, pubkey)
|
||||||
.subscribe((profileContent) => {
|
.subscribe((profileContent) => {
|
||||||
@@ -104,6 +120,43 @@ export default class NostrDataService extends Service {
|
|||||||
this.blossomServers = [];
|
this.blossomServers = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 1. Await cache initialization and populate the EventStore with local data
|
||||||
|
try {
|
||||||
|
await this._cachePromise;
|
||||||
|
|
||||||
|
const cachedEvents = await this.cache.query([
|
||||||
|
{
|
||||||
|
authors: [pubkey],
|
||||||
|
kinds: [0, 10002, 10063],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (cachedEvents && cachedEvents.length > 0) {
|
||||||
|
for (const event of cachedEvents) {
|
||||||
|
this.store.add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to read from local Nostr IDB cache', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Request new events from the network in the background and dump them into the store
|
||||||
|
this._requestSub = this.nostrRelay.pool
|
||||||
|
.request(relayList, [
|
||||||
|
{
|
||||||
|
authors: [pubkey],
|
||||||
|
kinds: [0, 10002, 10063],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.subscribe({
|
||||||
|
next: (event) => {
|
||||||
|
this.store.add(event);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error fetching profile events:', err);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get userDisplayName() {
|
get userDisplayName() {
|
||||||
@@ -154,5 +207,13 @@ export default class NostrDataService extends Service {
|
|||||||
willDestroy() {
|
willDestroy() {
|
||||||
super.willDestroy(...arguments);
|
super.willDestroy(...arguments);
|
||||||
this._cleanupSubscriptions();
|
this._cleanupSubscriptions();
|
||||||
|
|
||||||
|
if (this._stopPersisting) {
|
||||||
|
this._stopPersisting();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cache) {
|
||||||
|
this.cache.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
"ember-concurrency": "^5.2.0",
|
"ember-concurrency": "^5.2.0",
|
||||||
"ember-lifeline": "^7.0.0",
|
"ember-lifeline": "^7.0.0",
|
||||||
|
"nostr-idb": "^5.0.0",
|
||||||
"oauth2-pkce": "^2.1.3",
|
"oauth2-pkce": "^2.1.3",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"rxjs": "^7.8.2"
|
"rxjs": "^7.8.2"
|
||||||
|
|||||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
|||||||
ember-lifeline:
|
ember-lifeline:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.0(@ember/test-helpers@5.4.1(@babel/core@7.28.6))
|
version: 7.0.0(@ember/test-helpers@5.4.1(@babel/core@7.28.6))
|
||||||
|
nostr-idb:
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: 5.0.0
|
||||||
oauth2-pkce:
|
oauth2-pkce:
|
||||||
specifier: ^2.1.3
|
specifier: ^2.1.3
|
||||||
version: 2.1.3
|
version: 2.1.3
|
||||||
@@ -3709,6 +3712,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
idb@8.0.3:
|
||||||
|
resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==}
|
||||||
|
|
||||||
ignore@5.3.2:
|
ignore@5.3.2:
|
||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@@ -4405,6 +4411,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
nostr-idb@5.0.0:
|
||||||
|
resolution: {integrity: sha512-w5y4AnHefZIwCCL11NryfM2xp3U0Ka4qVNQEYAjnQbPwyoV+bZTdwuPXHCdRDWvhOFP2bZr1WBegcsAmkBjrxQ==}
|
||||||
|
|
||||||
nostr-signer-capacitor-plugin@0.0.5:
|
nostr-signer-capacitor-plugin@0.0.5:
|
||||||
resolution: {integrity: sha512-/EvqWz71HZ5cWmzvfXWTm48AWZtbeZDbOg3vLwXyXPjnIp1DR7Wurww/Mo41ORNu1DNPlqH20l7kIXKO6vR5og==}
|
resolution: {integrity: sha512-/EvqWz71HZ5cWmzvfXWTm48AWZtbeZDbOg3vLwXyXPjnIp1DR7Wurww/Mo41ORNu1DNPlqH20l7kIXKO6vR5og==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -10286,6 +10295,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
|
idb@8.0.3: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
ignore@7.0.5: {}
|
ignore@7.0.5: {}
|
||||||
@@ -11021,6 +11032,13 @@ snapshots:
|
|||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
|
|
||||||
|
nostr-idb@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
idb: 8.0.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
nostr-signer-capacitor-plugin@0.0.5(@capacitor/core@7.6.2):
|
nostr-signer-capacitor-plugin@0.0.5(@capacitor/core@7.6.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@capacitor/core': 7.6.2
|
'@capacitor/core': 7.6.2
|
||||||
|
|||||||
Reference in New Issue
Block a user