Use welshman dvms

This commit is contained in:
Jon Staab 2024-07-19 09:08:11 -07:00
parent ab7a28ae34
commit b318cc6808
11 changed files with 81 additions and 124 deletions

View File

@ -19,7 +19,9 @@ If you like Coracle and want to support its development, you can donate sats via
- [x] NIP 87 closed groups
- [x] NIP 72 communities
- [x] NIP 89 client tag support
- [x] NIP 89 handler integration
- [x] NIP 32 labeling and collections
- [x] NIP 17 DMs
- [x] Private group calendars and listings
- [x] Cross-posting between communities and main feed
- [x] Bech32 entity search and scan
@ -46,6 +48,12 @@ If you like Coracle and want to support its development, you can donate sats via
- [x] Onboarding workflow
- [x] Multi-account support
- [x] Notifications view
- [x] Web of trust scores for less spam and better group/feed suggestions
- [x] Customizable and shareable feeds and lists
- [x] Customizable invite links
- [x] Reporting via tagr-bot
- [x] Nostr Wallet Connect support
- [x] Date/time localization
You can find a more complete changelog [here](./CHANGELOG.md).
@ -53,9 +61,9 @@ You can find a more complete changelog [here](./CHANGELOG.md).
- Clone the project repository: `git clone https://github.com/coracle-social/coracle.git`
- Navigate to the project directory: `cd coracle`
- Install dependencies: `yarn`
- Install dependencies: `npm i`
- Customize configuration in `.env` (optional, see below)
- Start the development server: `yarn dev`
- Start the development server: `npm run dev`
# Customization

BIN
package-lock.json generated

Binary file not shown.

View File

@ -9,7 +9,7 @@
"check:es": "eslint 'src/**/*.{js,ts,svelte}' --quiet",
"check:ts": "svelte-check --tsconfig ./tsconfig.json --threshold error",
"check:fmt": "prettier --check $(git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte)$' | xargs)",
"check:errors": "run-p check:es check:ts check:cycles",
"check:errors": "run-p check:es check:ts",
"check": "run-p check:errors check:fmt",
"format": "prettier --write $(git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte)$' | xargs)",
"watch": "find src -type f | entr -r"
@ -57,7 +57,8 @@
"@welshman/feeds": "^0.0.12",
"@welshman/lib": "^0.0.11",
"@welshman/net": "^0.0.14",
"@welshman/util": "^0.0.17",
"@welshman/util": "^0.0.18",
"@welshman/dvm": "^0.0.2",
"bowser": "^2.11.0",
"classnames": "^2.5.1",
"compressorjs": "^1.2.1",

View File

@ -1,5 +1,5 @@
<script lang="ts">
import {append} from "ramda"
import {append} from "@welshman/lib"
import {updateIn} from "src/util/misc"
import {slide} from "src/util/transition"
import Card from "src/partials/Card.svelte"
@ -9,7 +9,9 @@
export let task
const hideTask = () =>
updateCurrentSession(updateIn("onboarding_tasks_completed", append(task)))
updateCurrentSession(
updateIn("onboarding_tasks_completed", (tasks: string[]) => append(task, tasks)),
)
</script>
{#if !$session.onboarding_tasks_completed.includes(task)}
@ -19,9 +21,7 @@
<FlexColumn>
<slot />
</FlexColumn>
<i
class="fa fa-times absolute right-0 top-0 cursor-pointer p-2"
on:click={hideTask} />
<i class="fa fa-times absolute right-0 top-0 cursor-pointer p-2" on:click={hideTask} />
</Card>
</div>
</div>

View File

@ -1,4 +1,5 @@
<script lang="ts">
import cx from "classnames"
import Anchor from "src/partials/Anchor.svelte"
import {router} from "src/app/util/router"
import {deriveProfileDisplay, loadPubkeys} from "src/engine"
@ -11,6 +12,6 @@
loadPubkeys([pubkey])
</script>
<Anchor modal stopPropagation class={$$props.class} href={path}>
<Anchor modal stopPropagation class={cx("!no-underline", $$props.class)} href={path}>
@<span class="underline">{$display}</span>
</Anchor>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import {makeRelayFeed} from "@welshman/feeds"
import {makeRelayFeed, makeScopeFeed, Scope} from "@welshman/feeds"
import Anchor from "src/partials/Anchor.svelte"
import Feed from "src/app/shared/Feed.svelte"
import {router} from "src/app/util/router"
@ -13,6 +13,8 @@
if (isPlatformFeed) {
globalFeed.set(makeFeed({definition: makeRelayFeed(...$env.PLATFORM_RELAYS)}))
} else {
globalFeed.set(makeFeed({definition: makeScopeFeed(Scope.Follows)}))
}
document.title = "Feeds"

View File

@ -18,7 +18,7 @@
userIsGroupMember,
updateCurrentSession,
communityListsByAddress,
searchGroupMeta,
groupMetaSearch,
groupMeta,
} from "src/engine"
@ -34,7 +34,7 @@
let limit = 20
let element = null
$: otherGroupMeta = reject(userIsMember, $searchGroupMeta(q)).slice(0, limit)
$: otherGroupMeta = reject(userIsMember, $groupMetaSearch.searchOptions(q)).slice(0, limit)
document.title = "Groups"

View File

@ -15,8 +15,8 @@
import GroupCircle from "src/app/shared/GroupCircle.svelte"
import PersonSelect from "src/app/shared/PersonSelect.svelte"
import {router} from "src/app/util/router"
import {displayRelayUrl, displayGroupMeta} from "src/domain"
import {hints, relaySearch, searchGroupMeta, groupMetaByAddress} from "src/engine"
import {displayRelayUrl} from "src/domain"
import {hints, relaySearch, groupMetaSearch, displayGroupByAddress} from "src/engine"
export let initialPubkey = null
export let initialGroupAddress = null
@ -70,8 +70,6 @@
groups = toSpliced(groups, i, 1)
}
const displayGroupFromAddress = a => displayGroupMeta($groupMetaByAddress.get(a))
let relayInput, groupInput
let sections = []
let pubkeys = []
@ -185,7 +183,7 @@
</p>
{#each groups as group, i (group.address + i)}
<ListItem on:remove={() => removeGroup(i)}>
<span slot="label">{displayGroupFromAddress(group.address)}</span>
<span slot="label">{displayGroupByAddress(group.address)}</span>
<span slot="data">
<Input bind:value={group.claim} placeholder="Invite code (optional)" />
</span>
@ -194,8 +192,8 @@
<SearchSelect
value={null}
bind:this={groupInput}
search={$searchGroupMeta}
displayItem={displayGroupMeta}
search={$groupMetaSearch.searchOptions}
displayItem={$groupMetaSearch.displayOption}
getKey={groupMeta => getAddress(groupMeta.event)}
onChange={groupMeta => groupMeta && addGroup(getAddress(groupMeta.event))}>
<i slot="before" class="fa fa-search" />

View File

@ -17,6 +17,7 @@ import {
getIdFilters,
isGroupAddress,
isSignedEvent,
createEvent,
WRAP,
WRAP_NIP04,
EPOCH,
@ -28,6 +29,7 @@ import {
HANDLER_INFORMATION,
HANDLER_RECOMMENDATION,
} from "@welshman/util"
import {makeDvmRequest} from "@welshman/dvm"
import {updateIn, createBatcher} from "src/util/misc"
import {giftWrapKinds, noteKinds, reactionKinds, repostKinds} from "src/util/nostr"
import {always, partition, pluck, uniq, without} from "ramda"
@ -38,7 +40,6 @@ import {
getUserCircles,
getGroupReqInfo,
getCommunityReqInfo,
dvmRequest,
env,
getFollows,
getFilterSelections,
@ -59,9 +60,9 @@ import {
subscribe,
subscribePersistent,
dufflepud,
signer,
} from "src/engine/state"
import {updateCurrentSession, updateSession} from "src/engine/commands"
import {loadPubkeyRelays} from "src/engine/requests/pubkeys"
export * from "src/engine/requests/pubkeys"
@ -275,22 +276,22 @@ export const feedLoader = new FeedLoader<TrustedEvent>({
}
},
requestDVM: async ({kind, onEvent, tags = [], ...request}) => {
let relays
if (request.relays?.length > 0) {
relays = hints.fromRelays(request.relays).getUrls()
} else {
const pubkeys = tags.filter(nthEq(0, "p")).map(nth(1))
tags = [...tags, ["expiration", String(now() + 5)]]
await loadPubkeyRelays(pubkeys)
const req = makeDvmRequest({
event: await signer.get().signAsUser(createEvent(kind, {tags})),
relays:
request.relays?.length > 0
? hints.fromRelays(request.relays).getUrls()
: hints.Messages(tags.filter(nthEq(0, "p")).map(nth(1))).getUrls(),
})
relays = hints.Messages(pubkeys).getUrls()
}
const event = await dvmRequest({kind, tags, relays})
if (event) {
await new Promise<void>(resolve => {
req.emitter.on("result", (url, event) => {
onEvent(event)
}
resolve()
})
})
},
getPubkeysForScope: (scope: string) => {
const $pubkey = pubkey.get()

View File

@ -54,7 +54,7 @@ const getFiltersForKey = (key: string, authors: string[]) => {
case "pubkey/relays":
return [{authors, kinds: [RELAYS, INBOX_RELAYS]}]
case "pubkey/profile":
return [{authors, kinds: [PROFILE, FOLLOWS, HANDLER_INFORMATION, COMMUNITIES]}]
return [{authors, kinds: [PROFILE, FOLLOWS, MUTES, HANDLER_INFORMATION, COMMUNITIES]}]
case "pubkey/user":
return [
{authors, kinds: [PROFILE, RELAYS, MUTES, FOLLOWS, COMMUNITIES, FEEDS]},

View File

@ -92,7 +92,7 @@ import {
publish as basePublish,
subscribe as baseSubscribe,
} from "@welshman/net"
import type {Publish, PublishRequest, SubscribeRequest} from "@welshman/net"
import type {PublishRequest, SubscribeRequest} from "@welshman/net"
import * as Content from "@welshman/content"
import {fuzzy, synced, withGetter, pushToKey, tryJson, fromCsv, SearchHelper} from "src/util/misc"
import {
@ -108,6 +108,7 @@ import {
} from "src/util/nostr"
import logger from "src/util/logger"
import type {
GroupMeta,
PublishedFeed,
PublishedProfile,
PublishedListFeed,
@ -142,6 +143,7 @@ import {
filterRelaysByNip,
displayRelayUrl,
readGroupMeta,
displayGroupMeta,
} from "src/domain"
import type {
Channel,
@ -693,7 +695,32 @@ export const groupMetaByAddress = withGetter(
export const deriveGroupMeta = (address: string) =>
derived(groupMetaByAddress, $m => $m.get(address))
export const searchGroupMeta = derived(
export const displayGroupByAddress = a => displayGroupMeta(groupMetaByAddress.get().get(a))
export class GroupSearch extends SearchHelper<GroupMeta & {score: number}, string> {
config = {
keys: [{name: "identifier", weight: 0.2}, "name", {name: "about", weight: 0.5}],
threshold: 0.3,
shouldSort: false,
includeScore: true,
}
getSearch = () => {
const fuse = new Fuse(this.options, this.config)
const sortFn = (r: any) => r.score - Math.pow(Math.max(0, r.item.score), 1 / 100)
return (term: string) =>
term
? sortBy(sortFn, fuse.search(term)).map((r: any) => r.item)
: sortBy(meta => -meta.score, this.options)
}
getValue = (option: GroupMeta) => getAddress(option.event)
displayValue = displayGroupByAddress
}
export const groupMetaSearch = derived(
[groupMeta, communityListsByAddress, userFollows],
([$groupMeta, $communityListsByAddress, $userFollows]) => {
const options = $groupMeta.map(meta => {
@ -704,19 +731,7 @@ export const searchGroupMeta = derived(
return {...meta, score: followedMembers.length}
})
const fuse = new Fuse(options, {
keys: [{name: "identifier", weight: 0.2}, "name", {name: "about", weight: 0.5}],
threshold: 0.3,
shouldSort: false,
includeScore: true,
})
const sortFn = (r: any) => r.score - Math.pow(Math.max(0, r.item.score), 1 / 100)
return (term: string) =>
term
? sortBy(sortFn, fuse.search(term)).map((r: any) => r.item)
: sortBy(meta => -meta.score, options)
return new GroupSearch(options)
},
)
@ -1975,75 +1990,6 @@ export const getClientTags = () => {
return [tag]
}
// DVMs
export type DVMRequestOpts = {
kind: number
input?: any
inputOpts?: string[]
tags?: string[][]
relays?: string[]
timeout?: number
onPublish?: (pub: Publish) => void
onProgress?: (e: TrustedEvent) => void
sk?: string
}
export const dvmRequest = async ({
kind,
tags = [],
timeout = 30_000,
relays = [],
onPublish = null,
onProgress = null,
sk = null,
}: DVMRequestOpts): Promise<TrustedEvent> => {
if (!sk && !signer.get().isEnabled()) {
sk = generatePrivateKey()
}
if (relays.length === 0) {
relays = hints.merge([hints.WriteRelays(), hints.fromRelays(env.get().DVM_RELAYS)]).getUrls()
}
tags = tags.concat([["expiration", String(now() + seconds(1, "hour"))]])
const pub = await createAndPublish({kind, relays, sk, tags, forcePlatform: false})
onPublish?.(pub)
return new Promise(resolve => {
const kinds = [kind + 1000]
if (onProgress) {
kinds.push(7000)
}
const sub = subscribe({
relays,
timeout,
filters: [
{
kinds,
since: now() - seconds(1, "minute"),
"#e": [pub.request.event.id],
},
],
onEvent: (e: TrustedEvent) => {
if (e.kind === 7000) {
onProgress?.(e)
} else {
resolve(e)
sub.close()
}
},
onComplete: () => {
resolve(null)
},
})
})
}
// Thread
const getAncestorIds = e => {
@ -2162,19 +2108,19 @@ class IndexedDBAdapter {
const removedRecords = prev.filter(r => !currentIds.has(r[key]))
if (newRecords.length > 0) {
console.log("putting", name, newRecords.length, current.length)
await storage.bulkPut(name, newRecords)
}
if (removedRecords.length > 0) {
console.trace("deleting", name, removedRecords.length, current.length)
if (name === "repository") {
console.trace("deleting", removedRecords.length, current.length)
}
await storage.bulkDelete(name, removedRecords.map(prop(key)))
}
// If we have much more than our limit, prune our store. This will get persisted
// the next time around.
if (current.length > limit * 1.5) {
console.log("pruning", name, current.length)
set((sort ? sort(current) : current).slice(0, limit))
}