applesauce-core 0.0.0-next-20250918142212 → 0.0.0-next-20250919114711
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/mailboxes.d.ts +2 -6
- package/dist/helpers/mailboxes.js +26 -20
- package/dist/helpers/pointers.js +2 -1
- package/dist/helpers/relay-selection.d.ts +3 -9
- package/dist/helpers/relay-selection.js +63 -104
- package/dist/helpers/url.js +3 -3
- package/dist/models/outbox.d.ts +1 -1
- package/dist/models/outbox.js +4 -15
- package/dist/observable/relay-selection.d.ts +0 -2
- package/dist/observable/relay-selection.js +13 -55
- package/package.json +3 -1
- package/dist/__tests__/exports.test.d.ts +0 -1
- package/dist/__tests__/exports.test.js +0 -27
- package/dist/__tests__/fixtures.d.ts +0 -17
- package/dist/__tests__/fixtures.js +0 -28
- package/dist/event-store/__tests__/event-store.test.d.ts +0 -1
- package/dist/event-store/__tests__/event-store.test.js +0 -386
- package/dist/event-store/common.d.ts +0 -1
- package/dist/event-store/common.js +0 -2
- package/dist/event-store/database.d.ts +0 -67
- package/dist/event-store/database.js +0 -316
- package/dist/event-store/event-database.d.ts +0 -74
- package/dist/event-store/event-database.js +0 -339
- package/dist/event-store/event-set.d.ts +0 -75
- package/dist/event-store/event-set.js +0 -341
- package/dist/event-store/event-store.test.d.ts +0 -1
- package/dist/event-store/event-store.test.js +0 -74
- package/dist/helpers/__tests__/app-handlers.test.d.ts +0 -1
- package/dist/helpers/__tests__/app-handlers.test.js +0 -184
- package/dist/helpers/__tests__/blossom.test.d.ts +0 -1
- package/dist/helpers/__tests__/blossom.test.js +0 -13
- package/dist/helpers/__tests__/bookmarks.test.d.ts +0 -1
- package/dist/helpers/__tests__/bookmarks.test.js +0 -88
- package/dist/helpers/__tests__/comment.test.d.ts +0 -1
- package/dist/helpers/__tests__/comment.test.js +0 -249
- package/dist/helpers/__tests__/contacts.test.d.ts +0 -1
- package/dist/helpers/__tests__/contacts.test.js +0 -34
- package/dist/helpers/__tests__/emoji.test.d.ts +0 -1
- package/dist/helpers/__tests__/emoji.test.js +0 -110
- package/dist/helpers/__tests__/encrypted-content-cache.test.d.ts +0 -1
- package/dist/helpers/__tests__/encrypted-content-cache.test.js +0 -65
- package/dist/helpers/__tests__/encryption.test.d.ts +0 -1
- package/dist/helpers/__tests__/encryption.test.js +0 -21
- package/dist/helpers/__tests__/event.test.d.ts +0 -1
- package/dist/helpers/__tests__/event.test.js +0 -36
- package/dist/helpers/__tests__/events.test.d.ts +0 -1
- package/dist/helpers/__tests__/events.test.js +0 -32
- package/dist/helpers/__tests__/exports.test.d.ts +0 -1
- package/dist/helpers/__tests__/exports.test.js +0 -293
- package/dist/helpers/__tests__/file-metadata.test.d.ts +0 -1
- package/dist/helpers/__tests__/file-metadata.test.js +0 -103
- package/dist/helpers/__tests__/groups.test.d.ts +0 -1
- package/dist/helpers/__tests__/groups.test.js +0 -61
- package/dist/helpers/__tests__/hidden-tags.test.d.ts +0 -1
- package/dist/helpers/__tests__/hidden-tags.test.js +0 -29
- package/dist/helpers/__tests__/mailboxes.test.d.ts +0 -1
- package/dist/helpers/__tests__/mailboxes.test.js +0 -81
- package/dist/helpers/__tests__/messages.test.d.ts +0 -1
- package/dist/helpers/__tests__/messages.test.js +0 -91
- package/dist/helpers/__tests__/mutes.test.d.ts +0 -1
- package/dist/helpers/__tests__/mutes.test.js +0 -55
- package/dist/helpers/__tests__/nip-19.test.d.ts +0 -1
- package/dist/helpers/__tests__/nip-19.test.js +0 -42
- package/dist/helpers/__tests__/pointers.test.d.ts +0 -1
- package/dist/helpers/__tests__/pointers.test.js +0 -118
- package/dist/helpers/__tests__/profile.test.d.ts +0 -1
- package/dist/helpers/__tests__/profile.test.js +0 -72
- package/dist/helpers/__tests__/reactions.test.d.ts +0 -1
- package/dist/helpers/__tests__/reactions.test.js +0 -88
- package/dist/helpers/__tests__/relays.test.d.ts +0 -1
- package/dist/helpers/__tests__/relays.test.js +0 -21
- package/dist/helpers/__tests__/tags.test.d.ts +0 -1
- package/dist/helpers/__tests__/tags.test.js +0 -24
- package/dist/helpers/__tests__/threading.test.d.ts +0 -1
- package/dist/helpers/__tests__/threading.test.js +0 -41
- package/dist/helpers/direct-messages.d.ts +0 -4
- package/dist/helpers/direct-messages.js +0 -5
- package/dist/helpers/file-metadata.test.d.ts +0 -1
- package/dist/helpers/file-metadata.test.js +0 -103
- package/dist/helpers/hidden-tags.test.d.ts +0 -1
- package/dist/helpers/hidden-tags.test.js +0 -29
- package/dist/helpers/legacy-direct-messages.d.ts +0 -8
- package/dist/helpers/legacy-direct-messages.js +0 -17
- package/dist/helpers/mailboxes.test.d.ts +0 -1
- package/dist/helpers/mailboxes.test.js +0 -81
- package/dist/helpers/nip-19.d.ts +0 -4
- package/dist/helpers/nip-19.js +0 -27
- package/dist/helpers/tags.test.d.ts +0 -1
- package/dist/helpers/tags.test.js +0 -16
- package/dist/helpers/threading.test.d.ts +0 -1
- package/dist/helpers/threading.test.js +0 -41
- package/dist/helpers/wrapped-direct-messages.d.ts +0 -6
- package/dist/helpers/wrapped-direct-messages.js +0 -11
- package/dist/models/__tests__/comments.test.d.ts +0 -1
- package/dist/models/__tests__/comments.test.js +0 -36
- package/dist/models/__tests__/exports.test.d.ts +0 -1
- package/dist/models/__tests__/exports.test.js +0 -53
- package/dist/observable/__tests__/claim-events.test.d.ts +0 -1
- package/dist/observable/__tests__/claim-events.test.js +0 -23
- package/dist/observable/__tests__/claim-latest.test.d.ts +0 -1
- package/dist/observable/__tests__/claim-latest.test.js +0 -37
- package/dist/observable/__tests__/exports.test.d.ts +0 -1
- package/dist/observable/__tests__/exports.test.js +0 -21
- package/dist/observable/__tests__/map-events-to-store.test.d.ts +0 -1
- package/dist/observable/__tests__/map-events-to-store.test.js +0 -38
- package/dist/observable/__tests__/simple-timeout.test.d.ts +0 -1
- package/dist/observable/__tests__/simple-timeout.test.js +0 -34
- package/dist/observable/__tests__/watch-event-updates.test.d.ts +0 -1
- package/dist/observable/__tests__/watch-event-updates.test.js +0 -55
- package/dist/observable/getValue.d.ts +0 -2
- package/dist/observable/getValue.js +0 -13
- package/dist/observable/map-events-timeline.d.ts +0 -7
- package/dist/observable/map-events-timeline.js +0 -9
- package/dist/observable/share-behavior.d.ts +0 -2
- package/dist/observable/share-behavior.js +0 -7
- package/dist/observable/share-latest-value.d.ts +0 -6
- package/dist/observable/share-latest-value.js +0 -24
- package/dist/observable/stateful.d.ts +0 -10
- package/dist/observable/stateful.js +0 -60
- package/dist/observable/throttle.d.ts +0 -3
- package/dist/observable/throttle.js +0 -23
- package/dist/promise/__tests__/exports.test.d.ts +0 -1
- package/dist/promise/__tests__/exports.test.js +0 -11
- package/dist/queries/blossom.d.ts +0 -2
- package/dist/queries/blossom.js +0 -10
- package/dist/queries/bookmarks.d.ts +0 -8
- package/dist/queries/bookmarks.js +0 -23
- package/dist/queries/channels.d.ts +0 -11
- package/dist/queries/channels.js +0 -73
- package/dist/queries/comments.d.ts +0 -4
- package/dist/queries/comments.js +0 -14
- package/dist/queries/contacts.d.ts +0 -3
- package/dist/queries/contacts.js +0 -12
- package/dist/queries/index.d.ts +0 -13
- package/dist/queries/index.js +0 -13
- package/dist/queries/mailboxes.d.ts +0 -6
- package/dist/queries/mailboxes.js +0 -13
- package/dist/queries/mutes.d.ts +0 -8
- package/dist/queries/mutes.js +0 -23
- package/dist/queries/pins.d.ts +0 -3
- package/dist/queries/pins.js +0 -12
- package/dist/queries/profile.d.ts +0 -4
- package/dist/queries/profile.js +0 -12
- package/dist/queries/reactions.d.ts +0 -4
- package/dist/queries/reactions.js +0 -19
- package/dist/queries/simple.d.ts +0 -16
- package/dist/queries/simple.js +0 -38
- package/dist/queries/thread.d.ts +0 -25
- package/dist/queries/thread.js +0 -92
- package/dist/queries/user-status.d.ts +0 -11
- package/dist/queries/user-status.js +0 -39
- package/dist/queries/zaps.d.ts +0 -5
- package/dist/queries/zaps.js +0 -21
- package/dist/query-store/__tests__/query-store.test.d.ts +0 -1
- package/dist/query-store/__tests__/query-store.test.js +0 -63
- package/dist/query-store/index.d.ts +0 -1
- package/dist/query-store/index.js +0 -1
- package/dist/query-store/query-store.d.ts +0 -53
- package/dist/query-store/query-store.js +0 -97
- package/dist/query-store/query-store.test.d.ts +0 -1
- package/dist/query-store/query-store.test.js +0 -33
- package/dist/utils/lru.d.ts +0 -32
- package/dist/utils/lru.js +0 -148
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
2
|
export declare const MailboxesInboxesSymbol: unique symbol;
|
|
3
3
|
export declare const MailboxesOutboxesSymbol: unique symbol;
|
|
4
|
-
/**
|
|
5
|
-
* Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
|
|
6
|
-
*/
|
|
4
|
+
/** Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol */
|
|
7
5
|
export declare function getInboxes(event: NostrEvent): string[];
|
|
8
|
-
/**
|
|
9
|
-
* Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
|
|
10
|
-
*/
|
|
6
|
+
/** Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol */
|
|
11
7
|
export declare function getOutboxes(event: NostrEvent): string[];
|
|
@@ -1,41 +1,47 @@
|
|
|
1
1
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
2
2
|
import { isSafeRelayURL } from "./relays.js";
|
|
3
|
+
import { isRTag } from "./tags.js";
|
|
3
4
|
import { normalizeURL } from "./url.js";
|
|
4
5
|
export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
|
|
5
6
|
export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
|
|
6
|
-
/**
|
|
7
|
-
* Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
|
|
8
|
-
*/
|
|
7
|
+
/** Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol */
|
|
9
8
|
export function getInboxes(event) {
|
|
10
9
|
return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
|
|
11
10
|
const inboxes = [];
|
|
12
11
|
for (const tag of event.tags) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
!inboxes.includes(url) &&
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
if (!isRTag(tag))
|
|
13
|
+
continue;
|
|
14
|
+
try {
|
|
15
|
+
const [, url, mode] = tag;
|
|
16
|
+
if (url && isSafeRelayURL(url) && !inboxes.includes(url) && (mode === "read" || mode === undefined)) {
|
|
17
|
+
inboxes.push(normalizeURL(url));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Ignore invalid url tags
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
return inboxes;
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
|
|
27
|
-
*/
|
|
27
|
+
/** Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol */
|
|
28
28
|
export function getOutboxes(event) {
|
|
29
29
|
return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
|
|
30
30
|
const outboxes = [];
|
|
31
31
|
for (const tag of event.tags) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
if (!isRTag(tag))
|
|
33
|
+
continue;
|
|
34
|
+
try {
|
|
35
|
+
const [name, url, mode] = tag;
|
|
36
|
+
if (name === "r" &&
|
|
37
|
+
isSafeRelayURL(url) &&
|
|
38
|
+
!outboxes.includes(url) &&
|
|
39
|
+
(mode === "write" || mode === undefined)) {
|
|
40
|
+
outboxes.push(normalizeURL(url));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Ignore invalid url tags
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
return outboxes;
|
package/dist/helpers/pointers.js
CHANGED
|
@@ -5,6 +5,7 @@ import { isAddressableKind } from "nostr-tools/kinds";
|
|
|
5
5
|
import { isSafeRelayURL, mergeRelaySets } from "./relays.js";
|
|
6
6
|
import { isHexKey } from "./string.js";
|
|
7
7
|
import { hexToBytes } from "@noble/hashes/utils";
|
|
8
|
+
import { normalizeURL } from "./url.js";
|
|
8
9
|
export function parseCoordinate(a, requireD = false, silent = true) {
|
|
9
10
|
const parts = a.split(":");
|
|
10
11
|
const kind = parts[0] ? parseInt(parts[0]) : undefined;
|
|
@@ -117,7 +118,7 @@ export function getProfilePointerFromPTag(tag) {
|
|
|
117
118
|
throw new Error("Invalid pubkey");
|
|
118
119
|
const pointer = { pubkey: tag[1] };
|
|
119
120
|
if (tag[2] && isSafeRelayURL(tag[2]))
|
|
120
|
-
pointer.relays = [tag[2]];
|
|
121
|
+
pointer.relays = [normalizeURL(tag[2])];
|
|
121
122
|
return pointer;
|
|
122
123
|
}
|
|
123
124
|
/** Checks if a pointer is an AddressPointer */
|
|
@@ -2,18 +2,12 @@ import { ProfilePointer } from "nostr-tools/nip19";
|
|
|
2
2
|
export type SelectOptimalRelaysOptions = {
|
|
3
3
|
/** Maximum number of connections (relays) to select */
|
|
4
4
|
maxConnections: number;
|
|
5
|
-
/**
|
|
6
|
-
maxRelayCoverage?: number;
|
|
7
|
-
/** Maximum number of relays per user (default 8) */
|
|
5
|
+
/** Cap the number of relays a user can have */
|
|
8
6
|
maxRelaysPerUser?: number;
|
|
9
|
-
/** Minimum number of relays per user (default 2) */
|
|
10
|
-
minRelaysPerUser?: number;
|
|
11
7
|
};
|
|
12
8
|
/** Selects the optimal relays for a list of ProfilePointers */
|
|
13
|
-
export declare function selectOptimalRelays(users: ProfilePointer[], { maxConnections,
|
|
14
|
-
/** Sorts each ProfilePointer's relays by popularity */
|
|
15
|
-
export declare function sortRelaysByPopularity(users: ProfilePointer[]): ProfilePointer[];
|
|
9
|
+
export declare function selectOptimalRelays(users: ProfilePointer[], { maxConnections, maxRelaysPerUser }: SelectOptimalRelaysOptions): ProfilePointer[];
|
|
16
10
|
/** A map of pubkeys by relay */
|
|
17
|
-
export type OutboxMap = Record<string,
|
|
11
|
+
export type OutboxMap = Record<string, ProfilePointer[]>;
|
|
18
12
|
/** RxJS operator that aggregates contacts with outboxes into a relay -> pubkeys map */
|
|
19
13
|
export declare function groupPubkeysByRelay(pointers: ProfilePointer[]): OutboxMap;
|
|
@@ -1,111 +1,72 @@
|
|
|
1
|
-
import { logger } from "../logger.js";
|
|
2
|
-
const log = logger.extend("relay-selection");
|
|
3
1
|
/** Selects the optimal relays for a list of ProfilePointers */
|
|
4
|
-
export function selectOptimalRelays(users, { maxConnections,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const relayUserCounts = new Map();
|
|
11
|
-
const totalUsers = users.length;
|
|
12
|
-
const maxUsersPerRelay = Math.ceil((totalUsers * maxRelayCoverage) / 100);
|
|
13
|
-
// Process each user to select optimal relays
|
|
14
|
-
for (const user of users) {
|
|
15
|
-
const userRelays = [];
|
|
16
|
-
const availableRelays = user.relays || [];
|
|
17
|
-
// If user has no relays, add them with empty relays
|
|
18
|
-
if (availableRelays.length === 0) {
|
|
19
|
-
result.push({ ...user, relays: [] });
|
|
2
|
+
export function selectOptimalRelays(users, { maxConnections, maxRelaysPerUser }) {
|
|
3
|
+
const usersWithRelays = users.filter((user) => user.relays && user.relays.length > 0);
|
|
4
|
+
// create map of popular relays
|
|
5
|
+
const popular = new Map();
|
|
6
|
+
for (const user of usersWithRelays) {
|
|
7
|
+
if (!user.relays)
|
|
20
8
|
continue;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let attempts = 0;
|
|
24
|
-
const maxAttempts = availableRelays.length * 2; // Prevent infinite loops
|
|
25
|
-
while (userRelays.length < (maxRelaysPerUser || availableRelays.length) &&
|
|
26
|
-
selectedRelays.size < maxConnections &&
|
|
27
|
-
attempts < maxAttempts) {
|
|
28
|
-
attempts++;
|
|
29
|
-
let foundRelay = false;
|
|
30
|
-
// Try each relay in priority order (first = highest priority)
|
|
31
|
-
for (const relay of availableRelays) {
|
|
32
|
-
// Skip if we already selected this relay for this user
|
|
33
|
-
if (userRelays.includes(relay))
|
|
34
|
-
continue;
|
|
35
|
-
// Check if this relay would exceed coverage limit
|
|
36
|
-
const currentRelayUsers = relayUserCounts.get(relay) || 0;
|
|
37
|
-
if (currentRelayUsers >= maxUsersPerRelay)
|
|
38
|
-
continue;
|
|
39
|
-
// Select this relay
|
|
40
|
-
userRelays.push(relay);
|
|
41
|
-
selectedRelays.add(relay);
|
|
42
|
-
relayUserCounts.set(relay, currentRelayUsers + 1);
|
|
43
|
-
foundRelay = true;
|
|
44
|
-
// Stop if we've reached maxRelaysPerUser for this user
|
|
45
|
-
if (maxRelaysPerUser && userRelays.length >= maxRelaysPerUser)
|
|
46
|
-
break;
|
|
47
|
-
// Stop if we've reached maxConnections globally
|
|
48
|
-
if (selectedRelays.size >= maxConnections)
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
// If we couldn't find any more suitable relays, break
|
|
52
|
-
if (!foundRelay)
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
// Ensure minimum relays per user if specified
|
|
56
|
-
if (minRelaysPerUser && userRelays.length < minRelaysPerUser) {
|
|
57
|
-
// Try to add more relays even if they exceed coverage limits
|
|
58
|
-
let minAttempts = 0;
|
|
59
|
-
const maxMinAttempts = availableRelays.length;
|
|
60
|
-
while (userRelays.length < minRelaysPerUser && minAttempts < maxMinAttempts) {
|
|
61
|
-
minAttempts++;
|
|
62
|
-
for (const relay of availableRelays) {
|
|
63
|
-
if (userRelays.includes(relay))
|
|
64
|
-
continue;
|
|
65
|
-
if (selectedRelays.size >= maxConnections)
|
|
66
|
-
break;
|
|
67
|
-
userRelays.push(relay);
|
|
68
|
-
selectedRelays.add(relay);
|
|
69
|
-
relayUserCounts.set(relay, (relayUserCounts.get(relay) || 0) + 1);
|
|
70
|
-
if (userRelays.length >= minRelaysPerUser)
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
if (selectedRelays.size >= maxConnections)
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// Add user with selected relays (maintaining original relay order)
|
|
78
|
-
const finalRelays = availableRelays.filter((relay) => userRelays.includes(relay));
|
|
79
|
-
result.push({ ...user, relays: finalRelays });
|
|
9
|
+
for (const relay of user.relays)
|
|
10
|
+
popular.set(relay, (popular.get(relay) || 0) + 1);
|
|
80
11
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
/** Sorts each ProfilePointer's relays by popularity */
|
|
86
|
-
export function sortRelaysByPopularity(users) {
|
|
87
|
-
const relayUsageCount = new Map();
|
|
88
|
-
// Count the times the relays are used
|
|
89
|
-
for (const user of users) {
|
|
12
|
+
// sort users relays by popularity
|
|
13
|
+
for (const user of usersWithRelays) {
|
|
90
14
|
if (!user.relays)
|
|
91
15
|
continue;
|
|
92
|
-
|
|
93
|
-
|
|
16
|
+
user.relays = Array.from(user.relays).sort((a, b) => popular.get(b) - popular.get(a));
|
|
17
|
+
}
|
|
18
|
+
// Create a pool of users to calculate relay coverage from
|
|
19
|
+
let selectionPool = Array.from(usersWithRelays);
|
|
20
|
+
// Create map of times a users relay has been selected
|
|
21
|
+
const selectionCount = new Map();
|
|
22
|
+
let selection = new Set();
|
|
23
|
+
while (selectionPool.length > 0 && selection.size < maxConnections) {
|
|
24
|
+
// Create map of number of pool users per relay
|
|
25
|
+
const relayUserCount = new Map();
|
|
26
|
+
for (const user of selectionPool) {
|
|
27
|
+
if (!user.relays)
|
|
28
|
+
continue;
|
|
29
|
+
for (const relay of user.relays) {
|
|
30
|
+
// Skip relays that are already selected
|
|
31
|
+
if (selection.has(relay))
|
|
32
|
+
continue;
|
|
33
|
+
// Increment relay user count
|
|
34
|
+
relayUserCount.set(relay, (relayUserCount.get(relay) || 0) + 1);
|
|
35
|
+
}
|
|
94
36
|
}
|
|
37
|
+
// Sort relays by coverage
|
|
38
|
+
const byCoverage = Array.from(relayUserCount.entries()).sort((a, b) => b[1] - a[1]);
|
|
39
|
+
// No more relays to select, exit loop
|
|
40
|
+
if (byCoverage.length === 0)
|
|
41
|
+
break;
|
|
42
|
+
// Pick the most popular relay
|
|
43
|
+
const relay = byCoverage[0][0];
|
|
44
|
+
// Add relay to selection
|
|
45
|
+
selection.add(relay);
|
|
46
|
+
// Increment user relay count and remove users over the limit
|
|
47
|
+
selectionPool = selectionPool.filter((user) => {
|
|
48
|
+
// Ignore users that don't have the relay
|
|
49
|
+
if (!user.relays || !user.relays.includes(relay))
|
|
50
|
+
return true;
|
|
51
|
+
// Increment user relay count
|
|
52
|
+
let count = selectionCount.get(relay) || 0;
|
|
53
|
+
selectionCount.set(relay, count++);
|
|
54
|
+
// Remove user if they their relay has been selected more than minRelaysPerUser times
|
|
55
|
+
if (count >= 1)
|
|
56
|
+
return false;
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
95
59
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}),
|
|
107
|
-
};
|
|
108
|
-
});
|
|
60
|
+
// Take the original users and only include relays that where selected
|
|
61
|
+
return users.map((user) => ({
|
|
62
|
+
...user,
|
|
63
|
+
relays: maxRelaysPerUser
|
|
64
|
+
? user.relays
|
|
65
|
+
?.filter((relay) => selection.has(relay))
|
|
66
|
+
.sort((a, b) => (popular.get(a) ?? 0) - (popular.get(b) ?? 0))
|
|
67
|
+
.slice(0, maxRelaysPerUser)
|
|
68
|
+
: user.relays?.filter((relay) => selection.has(relay)),
|
|
69
|
+
}));
|
|
109
70
|
}
|
|
110
71
|
/** RxJS operator that aggregates contacts with outboxes into a relay -> pubkeys map */
|
|
111
72
|
export function groupPubkeysByRelay(pointers) {
|
|
@@ -116,9 +77,7 @@ export function groupPubkeysByRelay(pointers) {
|
|
|
116
77
|
for (const relay of pointer.relays) {
|
|
117
78
|
if (!outbox[relay])
|
|
118
79
|
outbox[relay] = [];
|
|
119
|
-
|
|
120
|
-
outbox[relay].push(pointer.pubkey);
|
|
121
|
-
}
|
|
80
|
+
outbox[relay].push(pointer);
|
|
122
81
|
}
|
|
123
82
|
}
|
|
124
83
|
return outbox;
|
package/dist/helpers/url.js
CHANGED
|
@@ -78,13 +78,13 @@ export function ensureHttpURL(url) {
|
|
|
78
78
|
*/
|
|
79
79
|
export function normalizeURL(url) {
|
|
80
80
|
let p = new URL(url);
|
|
81
|
-
//
|
|
81
|
+
// Remove any double slashes
|
|
82
82
|
p.pathname = p.pathname.replace(/\/+/g, "/");
|
|
83
|
-
//
|
|
83
|
+
// Remove the port if its not needed
|
|
84
84
|
if ((p.port === "80" && (p.protocol === "ws:" || p.protocol === "http:")) ||
|
|
85
85
|
(p.port === "443" && (p.protocol === "wss:" || p.protocol === "https:")))
|
|
86
86
|
p.port = "";
|
|
87
|
-
//
|
|
87
|
+
// Return a string if a string was passed in
|
|
88
88
|
// @ts-expect-error
|
|
89
89
|
return typeof url === "string" ? p.toString() : p;
|
|
90
90
|
}
|
package/dist/models/outbox.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ProfilePointer } from "nostr-tools/nip19";
|
|
2
2
|
import { Model } from "../event-store/interface.js";
|
|
3
|
-
import { ignoreBlacklistedRelays } from "../observable/relay-selection.js";
|
|
4
3
|
import { SelectOptimalRelaysOptions } from "../helpers/relay-selection.js";
|
|
4
|
+
import { ignoreBlacklistedRelays } from "../observable/relay-selection.js";
|
|
5
5
|
export type OutboxModelOptions = SelectOptimalRelaysOptions & {
|
|
6
6
|
type?: "inbox" | "outbox";
|
|
7
7
|
blacklist?: Parameters<typeof ignoreBlacklistedRelays>[0];
|
package/dist/models/outbox.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ignoreBlacklistedRelays, includeLegacyAppRelays, includeMailboxes } from "../observable/relay-selection.js";
|
|
2
|
-
import { selectOptimalRelays, sortRelaysByPopularity } from "../helpers/relay-selection.js";
|
|
3
|
-
import { identity, map } from "rxjs";
|
|
4
1
|
import hash_sum from "hash-sum";
|
|
2
|
+
import { identity, map } from "rxjs";
|
|
3
|
+
import { selectOptimalRelays } from "../helpers/relay-selection.js";
|
|
4
|
+
import { ignoreBlacklistedRelays, includeMailboxes } from "../observable/relay-selection.js";
|
|
5
5
|
/** A model that returns the users contacts with the relays to connect to */
|
|
6
6
|
export function OutboxModel(user, opts) {
|
|
7
7
|
return (store) => store.contacts(user).pipe(
|
|
@@ -9,21 +9,10 @@ export function OutboxModel(user, opts) {
|
|
|
9
9
|
opts?.blacklist ? ignoreBlacklistedRelays(opts.blacklist) : identity,
|
|
10
10
|
/** Include mailboxes */
|
|
11
11
|
includeMailboxes(store, opts.type),
|
|
12
|
-
/** Include legacy app relays */
|
|
13
|
-
includeLegacyAppRelays(store, opts.type),
|
|
14
|
-
/** Sort the relays by popularity */
|
|
15
|
-
map(sortRelaysByPopularity),
|
|
16
12
|
/** Select the optimal relays */
|
|
17
13
|
map((users) => selectOptimalRelays(users, opts)));
|
|
18
14
|
}
|
|
19
15
|
OutboxModel.getKey = (user, opts) => {
|
|
20
16
|
const p = typeof user === "string" ? user : user.pubkey;
|
|
21
|
-
return hash_sum([
|
|
22
|
-
p,
|
|
23
|
-
opts.type,
|
|
24
|
-
opts.maxConnections,
|
|
25
|
-
opts.maxRelayCoverage,
|
|
26
|
-
opts.maxRelaysPerUser,
|
|
27
|
-
opts.minRelaysPerUser,
|
|
28
|
-
]);
|
|
17
|
+
return hash_sum([p, opts.type, opts.maxConnections, opts.maxRelaysPerUser]);
|
|
29
18
|
};
|
|
@@ -3,7 +3,5 @@ import { type MonoTypeOperatorFunction, type Observable, type OperatorFunction }
|
|
|
3
3
|
import { IEventSubscriptions } from "../event-store/interface.js";
|
|
4
4
|
/** RxJS operator that fetches outboxes for profile pointers from the event store */
|
|
5
5
|
export declare function includeMailboxes(store: IEventSubscriptions, type?: "inbox" | "outbox"): OperatorFunction<ProfilePointer[], ProfilePointer[]>;
|
|
6
|
-
/** An operator that reads and adds the legacy relays from the kind 3 event */
|
|
7
|
-
export declare function includeLegacyAppRelays(store: IEventSubscriptions, type?: "inbox" | "outbox"): OperatorFunction<ProfilePointer[], ProfilePointer[]>;
|
|
8
6
|
/** Removes blacklisted relays from the user's relays */
|
|
9
7
|
export declare function ignoreBlacklistedRelays(blacklist: string[] | Observable<string[]>): MonoTypeOperatorFunction<ProfilePointer[]>;
|
|
@@ -1,76 +1,34 @@
|
|
|
1
|
-
import { combineLatest, combineLatestWith,
|
|
2
|
-
import { getRelaysFromContactsEvent } from "../helpers/contacts.js";
|
|
1
|
+
import { combineLatest, combineLatestWith, isObservable, map, of, pipe, switchMap, } from "rxjs";
|
|
3
2
|
import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
|
|
4
3
|
import { addRelayHintsToPointer } from "../helpers/pointers.js";
|
|
5
|
-
import { defined } from "./defined.js";
|
|
6
|
-
import { logger } from "../logger.js";
|
|
7
|
-
const log = logger.extend("relay-selection");
|
|
8
4
|
/** RxJS operator that fetches outboxes for profile pointers from the event store */
|
|
9
5
|
export function includeMailboxes(store, type = "outbox") {
|
|
10
6
|
// Get the outboxes for all contacts
|
|
11
|
-
return switchMap((contacts) => combineLatest(contacts.map((
|
|
12
|
-
//
|
|
7
|
+
return switchMap((contacts) => combineLatest(contacts.map((user) =>
|
|
8
|
+
// Subscribe to the outboxes for the user
|
|
13
9
|
store
|
|
14
10
|
.replaceable({
|
|
15
11
|
kind: 10002,
|
|
16
|
-
pubkey:
|
|
17
|
-
relays: contact.relays,
|
|
12
|
+
pubkey: user.pubkey,
|
|
18
13
|
})
|
|
19
14
|
.pipe(
|
|
20
|
-
//
|
|
21
|
-
defined(),
|
|
22
|
-
// Merge the outboxes into the pointer
|
|
15
|
+
// Add the relays to the user
|
|
23
16
|
map((event) => {
|
|
17
|
+
if (!event)
|
|
18
|
+
return user;
|
|
19
|
+
// Get the relays from the event
|
|
24
20
|
const relays = type === "outbox" ? getOutboxes(event) : getInboxes(event);
|
|
25
21
|
if (!relays)
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
timeout({ first: 5_000, with: () => EMPTY }),
|
|
31
|
-
// If no event is found, return the contact
|
|
32
|
-
defaultIfEmpty(contact)))));
|
|
33
|
-
}
|
|
34
|
-
/** An operator that reads and adds the legacy relays from the kind 3 event */
|
|
35
|
-
export function includeLegacyAppRelays(store, type = "outbox") {
|
|
36
|
-
return switchMap((users) => {
|
|
37
|
-
// Get the relays for all contacts
|
|
38
|
-
return combineLatest(users.map((contact) => {
|
|
39
|
-
// If the contact already has relays don't add any
|
|
40
|
-
if (contact.relays && contact.relays.length > 0)
|
|
41
|
-
return of(contact);
|
|
42
|
-
// Get the relays for the contact
|
|
43
|
-
return store
|
|
44
|
-
.replaceable({
|
|
45
|
-
kind: 1003,
|
|
46
|
-
pubkey: contact.pubkey,
|
|
47
|
-
relays: contact.relays,
|
|
48
|
-
})
|
|
49
|
-
.pipe(defined(),
|
|
50
|
-
// Merge the relays into the pointer
|
|
51
|
-
map((event) => {
|
|
52
|
-
let relays = getRelaysFromContactsEvent(event);
|
|
53
|
-
if (!relays)
|
|
54
|
-
return contact;
|
|
55
|
-
// Get the write relays
|
|
56
|
-
const urls = Array.from(relays.entries())
|
|
57
|
-
.filter(([_, t]) => t === type || t === "all")
|
|
58
|
-
.map(([relay]) => relay);
|
|
59
|
-
log(`Found ${urls.length} legacy ${type} relays for ${contact.pubkey}`);
|
|
60
|
-
return addRelayHintsToPointer(contact, urls);
|
|
61
|
-
}),
|
|
62
|
-
// Timeout the request if it takes too long
|
|
63
|
-
timeout({ first: 5_000, with: () => EMPTY }),
|
|
64
|
-
// If no event is found, return the contact
|
|
65
|
-
defaultIfEmpty(contact));
|
|
66
|
-
}));
|
|
67
|
-
});
|
|
22
|
+
return user;
|
|
23
|
+
// Add the relays to the user
|
|
24
|
+
return addRelayHintsToPointer(user, relays);
|
|
25
|
+
})))));
|
|
68
26
|
}
|
|
69
27
|
/** Removes blacklisted relays from the user's relays */
|
|
70
28
|
export function ignoreBlacklistedRelays(blacklist) {
|
|
71
29
|
return pipe(
|
|
72
30
|
// Combine with the observable so it re-emits when the blacklist changes
|
|
73
|
-
combineLatestWith(
|
|
31
|
+
combineLatestWith(isObservable(blacklist) ? blacklist : of(blacklist)),
|
|
74
32
|
// Filter the relays for the user
|
|
75
33
|
map(([users, blacklist]) => users.map((user) => {
|
|
76
34
|
if (!user.relays)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-core",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-20250919114711",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
72
72
|
"@types/debug": "^4.1.12",
|
|
73
73
|
"@types/hash-sum": "^1.0.2",
|
|
74
|
+
"rimraf": "^6.0.1",
|
|
74
75
|
"typescript": "^5.8.3",
|
|
75
76
|
"vitest": "^3.2.4"
|
|
76
77
|
},
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"url": "lightning:nostrudel@geyser.fund"
|
|
80
81
|
},
|
|
81
82
|
"scripts": {
|
|
83
|
+
"prebuild": "rimraf dist",
|
|
82
84
|
"build": "tsc",
|
|
83
85
|
"watch:build": "tsc --watch > /dev/null",
|
|
84
86
|
"test": "vitest run --passWithNoTests",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import * as exports from "../index.js";
|
|
3
|
-
describe("exports", () => {
|
|
4
|
-
it("should export the expected functions", () => {
|
|
5
|
-
expect(Object.keys(exports).sort()).toMatchInlineSnapshot(`
|
|
6
|
-
[
|
|
7
|
-
"EventSet",
|
|
8
|
-
"EventStore",
|
|
9
|
-
"EventStoreSymbol",
|
|
10
|
-
"Helpers",
|
|
11
|
-
"Models",
|
|
12
|
-
"TimeoutError",
|
|
13
|
-
"defined",
|
|
14
|
-
"firstValueFrom",
|
|
15
|
-
"getObservableValue",
|
|
16
|
-
"lastValueFrom",
|
|
17
|
-
"logger",
|
|
18
|
-
"mapEventsToStore",
|
|
19
|
-
"mapEventsToTimeline",
|
|
20
|
-
"simpleTimeout",
|
|
21
|
-
"watchEventUpdates",
|
|
22
|
-
"watchEventsUpdates",
|
|
23
|
-
"withImmediateValueOrDefault",
|
|
24
|
-
]
|
|
25
|
-
`);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { NostrEvent } from "nostr-tools";
|
|
2
|
-
import { EncryptedContentSigner } from "../helpers/encrypted-content.js";
|
|
3
|
-
export declare class FakeUser implements EncryptedContentSigner {
|
|
4
|
-
key: Uint8Array<ArrayBufferLike>;
|
|
5
|
-
pubkey: string;
|
|
6
|
-
nip04: {
|
|
7
|
-
encrypt: (pubkey: string, plaintext: string) => string;
|
|
8
|
-
decrypt: (pubkey: string, ciphertext: string) => string;
|
|
9
|
-
};
|
|
10
|
-
nip44: {
|
|
11
|
-
encrypt: (pubkey: string, plaintext: string) => string;
|
|
12
|
-
decrypt: (pubkey: string, ciphertext: string) => string;
|
|
13
|
-
};
|
|
14
|
-
event(data?: Partial<NostrEvent>): NostrEvent;
|
|
15
|
-
note(content?: string, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
|
|
16
|
-
profile(profile: any, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
|
|
17
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04, nip44 } from "nostr-tools";
|
|
2
|
-
import { unixNow } from "../helpers/time.js";
|
|
3
|
-
export class FakeUser {
|
|
4
|
-
key = generateSecretKey();
|
|
5
|
-
pubkey = getPublicKey(this.key);
|
|
6
|
-
nip04 = {
|
|
7
|
-
encrypt: (pubkey, plaintext) => nip04.encrypt(this.key, pubkey, plaintext),
|
|
8
|
-
decrypt: (pubkey, ciphertext) => nip04.decrypt(this.key, pubkey, ciphertext),
|
|
9
|
-
};
|
|
10
|
-
nip44 = {
|
|
11
|
-
encrypt: (pubkey, plaintext) => nip44.encrypt(plaintext, nip44.getConversationKey(this.key, pubkey)),
|
|
12
|
-
decrypt: (pubkey, ciphertext) => nip44.decrypt(ciphertext, nip44.getConversationKey(this.key, pubkey)),
|
|
13
|
-
};
|
|
14
|
-
event(data) {
|
|
15
|
-
return finalizeEvent({
|
|
16
|
-
kind: data?.kind ?? kinds.ShortTextNote,
|
|
17
|
-
content: data?.content || "",
|
|
18
|
-
created_at: data?.created_at ?? unixNow(),
|
|
19
|
-
tags: data?.tags || [],
|
|
20
|
-
}, this.key);
|
|
21
|
-
}
|
|
22
|
-
note(content = "Hello World", extra) {
|
|
23
|
-
return this.event({ kind: kinds.ShortTextNote, content, ...extra });
|
|
24
|
-
}
|
|
25
|
-
profile(profile, extra) {
|
|
26
|
-
return this.event({ kind: kinds.Metadata, content: JSON.stringify({ ...profile }), ...extra });
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|