You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
9.8 KiB
313 lines
9.8 KiB
import { |
|
collection_defaults, |
|
type RepoCollection, |
|
type RepoEvent, |
|
type RepoSummary, |
|
} from '$lib/components/repo/type' |
|
import { NDKRelaySet, type NDKFilter, NDKEvent } from '@nostr-dev-kit/ndk' |
|
import { writable, type Writable } from 'svelte/store' |
|
import { base_relays, ndk } from './ndk' |
|
import { repo_kind } from '$lib/kinds' |
|
import type { User } from '$lib/components/users/type' |
|
import { ensureUser } from './users' |
|
import { selectRepoFromCollection } from '$lib/components/repo/utils' |
|
|
|
export const repos: { |
|
[unique_commit_or_identifier: string]: Writable<RepoCollection> |
|
} = {} |
|
|
|
export const ensureRepoCollection = ( |
|
unique_commit_or_identifier: string |
|
): Writable<RepoCollection> => { |
|
if (!repos[unique_commit_or_identifier]) { |
|
let base: RepoCollection = { |
|
...collection_defaults, |
|
} |
|
if (unique_commit_or_identifier.length === 40) { |
|
base = { ...base, unique_commit: unique_commit_or_identifier } |
|
} else { |
|
base = { ...base, identifier: unique_commit_or_identifier } |
|
} |
|
|
|
repos[unique_commit_or_identifier] = writable(base) |
|
const filter: NDKFilter = base.unique_commit |
|
? { |
|
kinds: [repo_kind], |
|
'#r': [base.unique_commit], |
|
limit: 100, |
|
} |
|
: { |
|
kinds: [repo_kind], |
|
'#d': [base.identifier], |
|
limit: 100, |
|
} |
|
const sub = ndk.subscribe( |
|
filter, |
|
{ |
|
groupable: true, |
|
// default 100 |
|
groupableDelay: 200, |
|
closeOnEose: true, |
|
}, |
|
NDKRelaySet.fromRelayUrls(base_relays, ndk) |
|
) |
|
sub.on('event', (event: NDKEvent) => { |
|
const repo_event = eventToRepoEvent(event) |
|
if (repo_event) { |
|
const collection_for_unique_commit = |
|
unique_commit_or_identifier.length === 40 |
|
// get repo events with same identifer but no unique_commit as |
|
// the assumption is that they will be the same repo |
|
if (collection_for_unique_commit) { |
|
ensureRepoCollection(repo_event.identifier) |
|
// we will process them just before we turn loading to true |
|
} |
|
repos[unique_commit_or_identifier].update((repo_collection) => { |
|
return { |
|
...repo_collection, |
|
events: [...repo_collection.events, repo_event as RepoEvent], |
|
} |
|
}) |
|
const relays_to_use = |
|
repo_event.relays.length < 3 |
|
? repo_event.relays |
|
: [...base_relays].concat(repo_event.relays) |
|
|
|
// get references |
|
const ref_sub = ndk.subscribe( |
|
{ |
|
'#a': [ |
|
`${repo_kind}:${repo_event.maintainers[0].hexpubkey}:${repo_event.identifier}`, |
|
], |
|
limit: 10, |
|
}, |
|
{ |
|
groupable: true, |
|
// default 100 |
|
groupableDelay: 200, |
|
closeOnEose: true, |
|
}, |
|
NDKRelaySet.fromRelayUrls(relays_to_use, ndk) |
|
) |
|
ref_sub.on('event', (ref_event: NDKEvent) => { |
|
repos[unique_commit_or_identifier].update((repo_collection) => { |
|
return { |
|
...repo_collection, |
|
events: [ |
|
...repo_collection.events.map((latest_ref_event) => { |
|
if (latest_ref_event.event_id === repo_event.event_id) { |
|
return { |
|
...latest_ref_event, |
|
referenced_by: latest_ref_event.referenced_by |
|
? [...latest_ref_event.referenced_by, ref_event.id] |
|
: [ref_event.id], |
|
} |
|
} |
|
return latest_ref_event |
|
}), |
|
], |
|
} |
|
}) |
|
}) |
|
ref_sub.on('eose', () => { |
|
repos[unique_commit_or_identifier].update((repo_collection) => { |
|
const events = [ |
|
...repo_collection.events.map((latest_ref_event) => { |
|
if (latest_ref_event.event_id === repo_event.event_id) { |
|
return { |
|
...latest_ref_event, |
|
// finished loading repo_event as we have all referenced_by events |
|
loading: false, |
|
} |
|
} |
|
return latest_ref_event |
|
}), |
|
] |
|
const still_loading_events_in_collection = events.some( |
|
(e) => e.loading |
|
) |
|
if ( |
|
collection_for_unique_commit && |
|
!still_loading_events_in_collection |
|
) |
|
addEventsWithMatchingIdentifiers(events) |
|
|
|
return { |
|
...repo_collection, |
|
events, |
|
loading: |
|
still_loading_events_in_collection || |
|
// for uninque_commit loading will complete after extra identifer events are added |
|
collection_for_unique_commit, |
|
} |
|
}) |
|
}) |
|
|
|
// load maintainers - we will subscribe later to prevent too many updates |
|
repo_event.maintainers.forEach((m) => ensureUser(m.hexpubkey)) |
|
} |
|
}) |
|
sub.on('eose', () => { |
|
// still awaiting reference_by at this point |
|
repos[unique_commit_or_identifier].update((repo_collection) => { |
|
// subscribe to maintainers |
|
const hexpubkeys = repo_collection.events.flatMap((repo_event) => |
|
repo_event.maintainers.map((m) => m.hexpubkey) |
|
) |
|
hexpubkeys.forEach((hexpubkey) => { |
|
ensureUser(hexpubkey).subscribe((u) => { |
|
repos[unique_commit_or_identifier].update((repo_collection) => ({ |
|
...repo_collection, |
|
events: [ |
|
...repo_collection.events.map((repo_event) => ({ |
|
...repo_event, |
|
maintainers: [ |
|
...repo_event.maintainers.map((m) => ({ |
|
...(m.hexpubkey === u.hexpubkey ? u : m), |
|
})), |
|
], |
|
})), |
|
], |
|
})) |
|
}) |
|
}) |
|
return { |
|
...repo_collection, |
|
loading: false, |
|
} |
|
}) |
|
}) |
|
} |
|
setTimeout(() => { |
|
repos[unique_commit_or_identifier].update((collection) => { |
|
return { |
|
...collection, |
|
events: collection.events.map((e) => ({ ...e, loading: false })), |
|
loading: false, |
|
} |
|
}) |
|
}, 5000) |
|
return repos[unique_commit_or_identifier] |
|
} |
|
|
|
export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => { |
|
if (event.kind !== repo_kind) return undefined |
|
|
|
const maintainers = [ |
|
{ |
|
hexpubkey: event.pubkey, |
|
loading: true, |
|
npub: '', |
|
} as User, |
|
] |
|
event.getMatchingTags('maintainers').forEach((t: string[]) => { |
|
t.forEach((v, i) => { |
|
if (i > 0 && v !== maintainers[0].hexpubkey) { |
|
maintainers.push({ |
|
hexpubkey: v, |
|
loading: true, |
|
npub: '', |
|
} as User) |
|
} |
|
}) |
|
}) |
|
const relays: string[] = [] |
|
event.getMatchingTags('relays').forEach((t: string[]) => { |
|
t.forEach((v, i) => { |
|
if (i > 0) { |
|
relays.push(v) |
|
} |
|
}) |
|
}) |
|
const web: string[] = [] |
|
event.getMatchingTags('web').forEach((t: string[]) => { |
|
t.forEach((v, i) => { |
|
if (i > 0) { |
|
relays.push(v) |
|
} |
|
}) |
|
}) |
|
return { |
|
event_id: event.id, |
|
identifier: event.replaceableDTag(), |
|
unique_commit: event.tagValue('r') || undefined, |
|
name: event.tagValue('name') || '', |
|
description: event.tagValue('description') || '', |
|
clone: event.tagValue('clone') || '', |
|
web, |
|
tags: event.getMatchingTags('t').map((t) => t[1]) || [], |
|
maintainers, |
|
relays, |
|
referenced_by: [], |
|
created_at: event.created_at || 0, |
|
loading: true, // loading until references fetched |
|
} |
|
} |
|
|
|
export const repoCollectionToSummary = ( |
|
collection: RepoCollection |
|
): RepoSummary | undefined => { |
|
const selected = selectRepoFromCollection(collection) |
|
if (!selected) return undefined |
|
return { |
|
name: selected.name, |
|
identifier: selected.identifier, |
|
unique_commit: selected.unique_commit, |
|
description: selected.description, |
|
maintainers: selected.maintainers, |
|
loading: collection.loading, |
|
created_at: selected.created_at, |
|
} as RepoSummary |
|
} |
|
|
|
/** to be called once all existing events have been found. this |
|
* function is useful if we assume events with the same |
|
* identifier reference the same repository */ |
|
const addEventsWithMatchingIdentifiers = (exisiting_events: RepoEvent[]) => { |
|
// add events with same identifier but no unique_commit |
|
exisiting_events |
|
// filter out duplicate identifiers |
|
.filter( |
|
(e, i) => |
|
exisiting_events.findIndex((v) => v.identifier == e.identifier) === i |
|
) |
|
// subscribe to each identifier |
|
.forEach((repo_event) => { |
|
ensureRepoCollection(repo_event.identifier).subscribe( |
|
(identiifer_collection) => { |
|
// if extra event(s) |
|
if ( |
|
identiifer_collection.events.some( |
|
(identifier_repo) => |
|
!exisiting_events.some( |
|
(e) => e.event_id === identifier_repo.event_id |
|
) |
|
) |
|
) { |
|
// add identifier events |
|
repos[repo_event.unique_commit as string].update( |
|
(repo_collection) => { |
|
const events = [ |
|
...repo_collection.events, |
|
...identiifer_collection.events |
|
.filter( |
|
(identifier_repo) => |
|
!repo_collection.events.some( |
|
(e) => e.event_id === identifier_repo.event_id |
|
) |
|
) |
|
.map((e) => ({ ...e })), |
|
] |
|
return { |
|
...repo_collection, |
|
events, |
|
// if all RepoEvents are loaded, the collection is too |
|
loading: events.some((e) => e.loading), |
|
} |
|
} |
|
) |
|
} |
|
} |
|
) |
|
}) |
|
}
|
|
|