chore(refactor): improve subscription process
All checks were successful
Release to Staging / build_and_release (push) Successful in 44s

This commit is contained in:
daniyal 2024-09-11 22:27:37 +05:00
parent 53d47fcb80
commit 34b096b121
3 changed files with 110 additions and 89 deletions

View File

@ -1,4 +1,4 @@
import { Event, Filter, kinds, Relay } from 'nostr-tools' import { Event, Filter, kinds, nip57, Relay } from 'nostr-tools'
import { import {
extractZapAmount, extractZapAmount,
log, log,
@ -600,10 +600,8 @@ export class RelayController {
const processedEvents: string[] = [] // To keep track of processed events const processedEvents: string[] = [] // To keep track of processed events
// Create a promise for each relay subscription // Create a promise for each relay subscription
const subPromises = relays.map((relay) => { const subscriptions = relays.map((relay) =>
return new Promise<void>((resolve) => { relay.subscribe([filter], {
// Subscribe to the relay with the specified filter
const sub = relay.subscribe([filter], {
// Handle incoming events // Handle incoming events
onevent: (e) => { onevent: (e) => {
// Process event only if it hasn't been processed before // Process event only if it hasn't been processed before
@ -611,18 +609,11 @@ export class RelayController {
processedEvents.push(e.id) processedEvents.push(e.id)
eventHandler(e) // Call the event handler with the event eventHandler(e) // Call the event handler with the event
} }
},
// Handle the End-Of-Stream (EOSE) message
oneose: () => {
sub.close() // Close the subscription
resolve() // Resolve the promise when EOSE is received
} }
}) })
}) )
})
// Wait for all subscriptions to complete return subscriptions
await Promise.allSettled(subPromises)
} }
getTotalZapAmount = async ( getTotalZapAmount = async (
@ -638,53 +629,90 @@ export class RelayController {
UserRelaysType.Read UserRelaysType.Read
) )
// add app relay to relays array const appRelay = import.meta.env.VITE_APP_RELAY
relayUrls.push(import.meta.env.VITE_APP_RELAY) if (!relayUrls.includes(appRelay)) {
relayUrls.push(appRelay)
// add admin relays to relays array }
metadataController.adminRelays.forEach((url) => {
relayUrls.push(url)
})
// Connect to all specified relays // Connect to all specified relays
const relayPromises = relayUrls.map((relayUrl) => const relayPromises = relayUrls.map((relayUrl) =>
this.connectRelay(relayUrl) this.connectRelay(relayUrl)
) )
await Promise.allSettled(relayPromises)
// Use Promise.allSettled to wait for all promises to settle
const results = await Promise.allSettled(relayPromises)
// Extract non-null values from fulfilled promises in a single pass
const relays = results.reduce<Relay[]>((acc, result) => {
if (result.status === 'fulfilled') {
const value = result.value
if (value) {
acc.push(value)
}
}
return acc
}, [])
let accumulatedZapAmount = 0 let accumulatedZapAmount = 0
let hasZapped = false let hasZapped = false
const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates
const filter: Filter = { const filters: Filter[] = [
kinds: [kinds.Zap] {
kinds: [kinds.Zap],
'#e': [eTag]
} }
]
if (aTag) { if (aTag) {
filter['#a'] = [aTag] filters.push({
} else { kinds: [kinds.Zap],
filter['#e'] = [eTag] '#a': [aTag]
})
} }
// Create a promise for each relay subscription // Create a promise for each relay subscription
const subPromises = this.connectedRelays.map((relay) => { const subPromises = relays.map((relay) => {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
// Subscribe to the relay with the specified filter // Subscribe to the relay with the specified filter
const sub = relay.subscribe([filter], { const sub = relay.subscribe(filters, {
// Handle incoming events // Handle incoming events
onevent: (e) => { onevent: (e) => {
// Add the event to the array if it's not a duplicate // Add the event to the array if it's not a duplicate
if (!eventIds.has(e.id)) { if (!eventIds.has(e.id)) {
eventIds.add(e.id) // Record the event ID eventIds.add(e.id) // Record the event ID
const amount = extractZapAmount(e)
const zapRequestStr = e.tags.find(
(t) => t[0] === 'description'
)?.[1]
if (!zapRequestStr) return
const error = nip57.validateZapRequest(zapRequestStr)
if (error) return
let zapRequest: Event | null = null
try {
zapRequest = JSON.parse(zapRequestStr)
} catch (error) {
log(
true,
LogType.Error,
'Error occurred in parsing zap request',
error
)
}
if (!zapRequest) return
const amount = extractZapAmount(zapRequest)
accumulatedZapAmount += amount accumulatedZapAmount += amount
if (amount > 0) {
if (!hasZapped) { if (!hasZapped) {
hasZapped = hasZapped = zapRequest.pubkey === currentLoggedInUser
e.tags.findIndex( }
(tag) => tag[0] === 'P' && tag[1] === currentLoggedInUser
) > -1
} }
} }
}, },

View File

@ -12,6 +12,7 @@ import {
} from '../types' } from '../types'
import { log, LogType, npubToHex } from '../utils' import { log, LogType, npubToHex } from '../utils'
import { RelayController } from './relay' import { RelayController } from './relay'
import { MetadataController, UserRelaysType } from './metadata'
/** /**
* Singleton class to manage zap related operations. * Singleton class to manage zap related operations.
@ -147,7 +148,7 @@ export class ZapController {
const cleanup = () => { const cleanup = () => {
clearTimeout(timeout) clearTimeout(timeout)
sub.close() subscriptions.forEach((subscription) => subscription.close())
} }
// Polling timeout // Polling timeout
@ -160,13 +161,11 @@ export class ZapController {
pollingTimeout || 6 * 60 * 1000 // 6 minutes pollingTimeout || 6 * 60 * 1000 // 6 minutes
) )
const relay = await RelayController.getInstance().connectRelay( const relaysTag = zapRequest.tags.find((t) => t[0] === 'relays')
this.appRelay if (!relaysTag)
) throw new Error('Zap request does not contain relays tag.')
if (!relay) { const relayUrls = relaysTag.slice(1)
return reject('Polling Zap Receipt: Could not connect to app relay!')
}
// filter relay for event of kind 9735 // filter relay for event of kind 9735
const filter: Filter = { const filter: Filter = {
@ -174,9 +173,11 @@ export class ZapController {
since: created_at since: created_at
} }
const sub = relay.subscribe([filter], { const subscriptions =
// Handle incoming events await RelayController.getInstance().subscribeForEvents(
onevent: async (event) => { filter,
relayUrls,
async (event) => {
// get description tag of the event // get description tag of the event
const description = event.tags.filter( const description = event.tags.filter(
(tag) => tag[0] === 'description' (tag) => tag[0] === 'description'
@ -192,7 +193,7 @@ export class ZapController {
} }
} }
} }
}) )
}) })
} }
@ -280,11 +281,21 @@ export class ZapController {
if (!recipientHexKey) throw 'Invalid recipient pubKey.' if (!recipientHexKey) throw 'Invalid recipient pubKey.'
const metadataController = await MetadataController.getInstance()
const receiverReadRelays = await metadataController.findUserRelays(
recipientHexKey,
UserRelaysType.Read
)
if (!receiverReadRelays.includes(this.appRelay)) {
receiverReadRelays.push(this.appRelay)
}
const zapRequest: ZapRequest = { const zapRequest: ZapRequest = {
kind: kinds.ZapRequest, kind: kinds.ZapRequest,
content, content,
tags: [ tags: [
['relays', `${this.appRelay}`], ['relays', ...receiverReadRelays],
['amount', `${amount}`], ['amount', `${amount}`],
['p', recipientHexKey] ['p', recipientHexKey]
], ],

View File

@ -104,31 +104,13 @@ export const npubToHex = (pubKey: string): string | null => {
* @returns The zap amount in the form of a number, converted from the extracted data, or 0 if the amount cannot be determined. * @returns The zap amount in the form of a number, converted from the extracted data, or 0 if the amount cannot be determined.
*/ */
export const extractZapAmount = (event: Event): number => { export const extractZapAmount = (event: Event): number => {
// Find the 'description' tag within the event's tags
const description = event.tags.find(
(tag) => tag[0] === 'description' && typeof tag[1] === 'string'
)
// If the 'description' tag is found and it has a valid value
if (description && description[1]) {
try {
// Parse the description as JSON to get additional details
const parsedDescription: Event = JSON.parse(description[1])
// Find the 'amount' tag within the parsed description's tags // Find the 'amount' tag within the parsed description's tags
const amountTag = parsedDescription.tags.find( const amountTag = event.tags.find(
(tag) => tag[0] === 'amount' && typeof tag[1] === 'string' (tag) => tag[0] === 'amount' && typeof tag[1] === 'string'
) )
// If the 'amount' tag is found and it has a valid value, convert it to an integer and return // If the 'amount' tag is found and it has a valid value, convert it to an integer and return
if (amountTag && amountTag[1]) return parseInt(amountTag[1]) / 1000 if (amountTag && amountTag[1]) return parseInt(amountTag[1]) / 1000
} catch (error) {
// Log an error message if JSON parsing fails
console.log(
`An error occurred while parsing description of zap event: ${error}`
)
}
}
// Return 0 if the zap amount cannot be determined // Return 0 if the zap amount cannot be determined
return 0 return 0