|
|
|
@ -24,6 +24,7 @@ type TTimelineRef = [string, number] |
|
|
|
class ClientService extends EventTarget { |
|
|
|
class ClientService extends EventTarget { |
|
|
|
static instance: ClientService |
|
|
|
static instance: ClientService |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
signer?: (evt: TDraftEvent) => Promise<VerifiedEvent> |
|
|
|
private defaultRelayUrls: string[] = BIG_RELAY_URLS |
|
|
|
private defaultRelayUrls: string[] = BIG_RELAY_URLS |
|
|
|
private pool: SimplePool |
|
|
|
private pool: SimplePool |
|
|
|
|
|
|
|
|
|
|
|
@ -109,17 +110,11 @@ class ClientService extends EventTarget { |
|
|
|
return this.defaultRelayUrls |
|
|
|
return this.defaultRelayUrls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async publishEvent( |
|
|
|
async publishEvent(relayUrls: string[], event: NEvent) { |
|
|
|
relayUrls: string[], |
|
|
|
|
|
|
|
event: NEvent, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
signer |
|
|
|
|
|
|
|
}: { |
|
|
|
|
|
|
|
signer?: (evt: TDraftEvent) => Promise<VerifiedEvent> |
|
|
|
|
|
|
|
} = {} |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
const result = await Promise.any( |
|
|
|
const result = await Promise.any( |
|
|
|
relayUrls.map(async (url) => { |
|
|
|
relayUrls.map(async (url) => { |
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
|
|
|
|
|
|
const that = this |
|
|
|
const relay = await this.pool.ensureRelay(url) |
|
|
|
const relay = await this.pool.ensureRelay(url) |
|
|
|
return relay |
|
|
|
return relay |
|
|
|
.publish(event) |
|
|
|
.publish(event) |
|
|
|
@ -128,9 +123,13 @@ class ClientService extends EventTarget { |
|
|
|
return reason |
|
|
|
return reason |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((error) => { |
|
|
|
.catch((error) => { |
|
|
|
if (error instanceof Error && error.message.startsWith('auth-required:') && signer) { |
|
|
|
if ( |
|
|
|
|
|
|
|
error instanceof Error && |
|
|
|
|
|
|
|
error.message.startsWith('auth-required:') && |
|
|
|
|
|
|
|
!!that.signer |
|
|
|
|
|
|
|
) { |
|
|
|
relay |
|
|
|
relay |
|
|
|
.auth((authEvt: EventTemplate) => signer(authEvt)) |
|
|
|
.auth((authEvt: EventTemplate) => that.signer!(authEvt)) |
|
|
|
.then(() => relay.publish(event)) |
|
|
|
.then(() => relay.publish(event)) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
throw error |
|
|
|
throw error |
|
|
|
@ -162,10 +161,10 @@ class ClientService extends EventTarget { |
|
|
|
onNew: (evt: NEvent) => void |
|
|
|
onNew: (evt: NEvent) => void |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
signer, |
|
|
|
startLogin, |
|
|
|
needSort = true |
|
|
|
needSort = true |
|
|
|
}: { |
|
|
|
}: { |
|
|
|
signer?: (evt: TDraftEvent) => Promise<NEvent | null> |
|
|
|
startLogin?: () => void |
|
|
|
needSort?: boolean |
|
|
|
needSort?: boolean |
|
|
|
} = {} |
|
|
|
} = {} |
|
|
|
) { |
|
|
|
) { |
|
|
|
@ -255,11 +254,13 @@ class ClientService extends EventTarget { |
|
|
|
timeline.refs.splice(idx, 0, [evt.id, evt.created_at]) |
|
|
|
timeline.refs.splice(idx, 0, [evt.id, evt.created_at]) |
|
|
|
}, |
|
|
|
}, |
|
|
|
onclose: (reason: string) => { |
|
|
|
onclose: (reason: string) => { |
|
|
|
if (reason.startsWith('auth-required:')) { |
|
|
|
if (!reason.startsWith('auth-required:')) return |
|
|
|
if (!hasAuthed && signer) { |
|
|
|
if (hasAuthed) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (that.signer) { |
|
|
|
relay |
|
|
|
relay |
|
|
|
.auth(async (authEvt: EventTemplate) => { |
|
|
|
.auth(async (authEvt: EventTemplate) => { |
|
|
|
const evt = await signer(authEvt) |
|
|
|
const evt = await that.signer!(authEvt) |
|
|
|
if (!evt) { |
|
|
|
if (!evt) { |
|
|
|
throw new Error('sign event failed') |
|
|
|
throw new Error('sign event failed') |
|
|
|
} |
|
|
|
} |
|
|
|
@ -274,7 +275,8 @@ class ClientService extends EventTarget { |
|
|
|
.catch(() => { |
|
|
|
.catch(() => { |
|
|
|
// ignore
|
|
|
|
// ignore
|
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} else if (startLogin) { |
|
|
|
|
|
|
|
startLogin() |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
oneose: () => { |
|
|
|
oneose: () => { |
|
|
|
@ -344,6 +346,55 @@ class ClientService extends EventTarget { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async query(urls: string[], filter: Filter) { |
|
|
|
|
|
|
|
const _knownIds = new Set<string>() |
|
|
|
|
|
|
|
const events: NEvent[] = [] |
|
|
|
|
|
|
|
await Promise.allSettled( |
|
|
|
|
|
|
|
urls.map(async (url) => { |
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
|
|
|
|
|
|
const that = this |
|
|
|
|
|
|
|
const relay = await this.pool.ensureRelay(url) |
|
|
|
|
|
|
|
let hasAuthed = false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise<void>((resolve, reject) => { |
|
|
|
|
|
|
|
const startQuery = () => { |
|
|
|
|
|
|
|
relay.subscribe([filter], { |
|
|
|
|
|
|
|
receivedEvent(relay, id) { |
|
|
|
|
|
|
|
that.trackEventSeenOn(id, relay) |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
onclose(reason) { |
|
|
|
|
|
|
|
if (!reason.startsWith('auth-required:') || hasAuthed) { |
|
|
|
|
|
|
|
resolve() |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (that.signer) { |
|
|
|
|
|
|
|
relay |
|
|
|
|
|
|
|
.auth((authEvt: EventTemplate) => that.signer!(authEvt)) |
|
|
|
|
|
|
|
.then(() => { |
|
|
|
|
|
|
|
hasAuthed = true |
|
|
|
|
|
|
|
startQuery() |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch(reject) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
oneose() { |
|
|
|
|
|
|
|
resolve() |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
onevent(evt) { |
|
|
|
|
|
|
|
if (_knownIds.has(evt.id)) return |
|
|
|
|
|
|
|
_knownIds.add(evt.id) |
|
|
|
|
|
|
|
events.push(evt) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
startQuery() |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
return events |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async loadMoreTimeline(key: string, until: number, limit: number) { |
|
|
|
async loadMoreTimeline(key: string, until: number, limit: number) { |
|
|
|
const timeline = this.timelines[key] |
|
|
|
const timeline = this.timelines[key] |
|
|
|
if (!timeline) return [] |
|
|
|
if (!timeline) return [] |
|
|
|
@ -362,7 +413,7 @@ class ClientService extends EventTarget { |
|
|
|
return cachedEvents |
|
|
|
return cachedEvents |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let events = await this.pool.querySync(urls, { ...filter, until: until, limit: limit }) |
|
|
|
let events = await this.query(urls, { ...filter, until: until, limit: limit }) |
|
|
|
events.forEach((evt) => { |
|
|
|
events.forEach((evt) => { |
|
|
|
this.eventDataLoader.prime(evt.id, Promise.resolve(evt)) |
|
|
|
this.eventDataLoader.prime(evt.id, Promise.resolve(evt)) |
|
|
|
}) |
|
|
|
}) |
|
|
|
@ -372,7 +423,7 @@ class ClientService extends EventTarget { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async fetchEvents(relayUrls: string[], filter: Filter, cache = false) { |
|
|
|
async fetchEvents(relayUrls: string[], filter: Filter, cache = false) { |
|
|
|
const events = await this.pool.querySync( |
|
|
|
const events = await this.query( |
|
|
|
relayUrls.length > 0 ? relayUrls : this.defaultRelayUrls, |
|
|
|
relayUrls.length > 0 ? relayUrls : this.defaultRelayUrls, |
|
|
|
filter |
|
|
|
filter |
|
|
|
) |
|
|
|
) |
|
|
|
@ -440,7 +491,7 @@ class ClientService extends EventTarget { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async fetchProfiles(relayUrls: string[], filter: Filter): Promise<TProfile[]> { |
|
|
|
async fetchProfiles(relayUrls: string[], filter: Filter): Promise<TProfile[]> { |
|
|
|
const events = await this.pool.querySync(relayUrls, { |
|
|
|
const events = await this.query(relayUrls, { |
|
|
|
...filter, |
|
|
|
...filter, |
|
|
|
kinds: [kinds.Metadata] |
|
|
|
kinds: [kinds.Metadata] |
|
|
|
}) |
|
|
|
}) |
|
|
|
@ -560,9 +611,12 @@ class ClientService extends EventTarget { |
|
|
|
return this.getSeenEventRelays(eventId).map((relay) => relay.url) |
|
|
|
return this.getSeenEventRelays(eventId).map((relay) => relay.url) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getEventHints(eventId: string) { |
|
|
|
|
|
|
|
return this.getSeenEventRelayUrls(eventId).filter((url) => !isLocalNetworkUrl(url)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
getEventHint(eventId: string) { |
|
|
|
getEventHint(eventId: string) { |
|
|
|
const relayUrls = this.getSeenEventRelayUrls(eventId) |
|
|
|
return this.getSeenEventRelayUrls(eventId).find((url) => !isLocalNetworkUrl(url)) ?? '' |
|
|
|
return relayUrls.find((url) => !isLocalNetworkUrl(url)) ?? '' |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
trackEventSeenOn(eventId: string, relay: AbstractRelay) { |
|
|
|
trackEventSeenOn(eventId: string, relay: AbstractRelay) { |
|
|
|
@ -617,7 +671,9 @@ class ClientService extends EventTarget { |
|
|
|
let event: NEvent | undefined |
|
|
|
let event: NEvent | undefined |
|
|
|
if (filter.ids) { |
|
|
|
if (filter.ids) { |
|
|
|
event = await this.fetchEventById(relays, filter.ids[0]) |
|
|
|
event = await this.fetchEventById(relays, filter.ids[0]) |
|
|
|
} else { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!event) { |
|
|
|
event = await this.tryHarderToFetchEvent(relays, filter) |
|
|
|
event = await this.tryHarderToFetchEvent(relays, filter) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -710,12 +766,12 @@ class ClientService extends EventTarget { |
|
|
|
} |
|
|
|
} |
|
|
|
if (!relayUrls.length) return |
|
|
|
if (!relayUrls.length) return |
|
|
|
|
|
|
|
|
|
|
|
const events = await this.pool.querySync(relayUrls, filter) |
|
|
|
const events = await this.query(relayUrls, filter) |
|
|
|
return events.sort((a, b) => b.created_at - a.created_at)[0] |
|
|
|
return events.sort((a, b) => b.created_at - a.created_at)[0] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async eventBatchLoadFn(ids: readonly string[]) { |
|
|
|
private async eventBatchLoadFn(ids: readonly string[]) { |
|
|
|
const events = await this.pool.querySync(this.defaultRelayUrls, { |
|
|
|
const events = await this.query(this.defaultRelayUrls, { |
|
|
|
ids: Array.from(new Set(ids)), |
|
|
|
ids: Array.from(new Set(ids)), |
|
|
|
limit: ids.length |
|
|
|
limit: ids.length |
|
|
|
}) |
|
|
|
}) |
|
|
|
@ -728,7 +784,7 @@ class ClientService extends EventTarget { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async profileEventBatchLoadFn(pubkeys: readonly string[]) { |
|
|
|
private async profileEventBatchLoadFn(pubkeys: readonly string[]) { |
|
|
|
const events = await this.pool.querySync(this.defaultRelayUrls, { |
|
|
|
const events = await this.query(this.defaultRelayUrls, { |
|
|
|
authors: Array.from(new Set(pubkeys)), |
|
|
|
authors: Array.from(new Set(pubkeys)), |
|
|
|
kinds: [kinds.Metadata], |
|
|
|
kinds: [kinds.Metadata], |
|
|
|
limit: pubkeys.length |
|
|
|
limit: pubkeys.length |
|
|
|
@ -748,7 +804,7 @@ class ClientService extends EventTarget { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async relayListEventBatchLoadFn(pubkeys: readonly string[]) { |
|
|
|
private async relayListEventBatchLoadFn(pubkeys: readonly string[]) { |
|
|
|
const events = await this.pool.querySync(this.defaultRelayUrls, { |
|
|
|
const events = await this.query(this.defaultRelayUrls, { |
|
|
|
authors: pubkeys as string[], |
|
|
|
authors: pubkeys as string[], |
|
|
|
kinds: [kinds.RelayList], |
|
|
|
kinds: [kinds.RelayList], |
|
|
|
limit: pubkeys.length |
|
|
|
limit: pubkeys.length |
|
|
|
@ -767,13 +823,10 @@ class ClientService extends EventTarget { |
|
|
|
|
|
|
|
|
|
|
|
private async _fetchFollowListEvent(pubkey: string) { |
|
|
|
private async _fetchFollowListEvent(pubkey: string) { |
|
|
|
const relayList = await this.fetchRelayList(pubkey) |
|
|
|
const relayList = await this.fetchRelayList(pubkey) |
|
|
|
const followListEvents = await this.pool.querySync( |
|
|
|
const followListEvents = await this.query(relayList.write.concat(this.defaultRelayUrls), { |
|
|
|
relayList.write.concat(this.defaultRelayUrls), |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
authors: [pubkey], |
|
|
|
authors: [pubkey], |
|
|
|
kinds: [kinds.Contacts] |
|
|
|
kinds: [kinds.Contacts] |
|
|
|
} |
|
|
|
}) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return followListEvents.sort((a, b) => b.created_at - a.created_at)[0] |
|
|
|
return followListEvents.sort((a, b) => b.created_at - a.created_at)[0] |
|
|
|
} |
|
|
|
} |
|
|
|
|