@orangecheck/chat-core 0.2.0 → 0.2.1
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/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/directory.test.ts +59 -0
- package/src/directory.ts +15 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecheck/chat-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Verify OC Chat's public artifacts: channel descriptors and posts, directory listings and did:oc session devices. See https://github.com/orangecheck/oc-chat-protocol.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
package/src/directory.test.ts
CHANGED
|
@@ -283,6 +283,65 @@ describe('resolveDirectoryHandle', () => {
|
|
|
283
283
|
});
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
+
it('a full page of junk the reader drops is still a full page: the read goes on past it', async () => {
|
|
287
|
+
const { w, deps, anchorAt } = world();
|
|
288
|
+
const a = claim('alice', ALICE, TIP - 100);
|
|
289
|
+
anchorAt(a, ALICE);
|
|
290
|
+
w.bound.set(ALICE, [pk(1)]);
|
|
291
|
+
const newer = listing(2, 'alice', BOB, claim('alice', BOB, TIP - 50, 'bob'), {}, 1_790_000_900);
|
|
292
|
+
w.relays.set('wss://relay.ochk.io', [newer, listing(1, 'alice', ALICE, a, {}, 1_790_000_000)]);
|
|
293
|
+
const real = deps.page;
|
|
294
|
+
// The relay sends a full page: one listing that verifies, and 199 the reader
|
|
295
|
+
// drops for their signatures. Only the next page reaches alice's claim.
|
|
296
|
+
deps.page = async (relay, filter) => {
|
|
297
|
+
const got = await real(relay, filter);
|
|
298
|
+
if (relay !== 'wss://relay.ochk.io' || filter.until !== undefined) return got;
|
|
299
|
+
return { events: got.events.slice(0, 1), complete: true, sent: filter.limit };
|
|
300
|
+
};
|
|
301
|
+
expect(await resolveDirectoryHandle('alice', 'people', deps)).toMatchObject({
|
|
302
|
+
result: 'holds',
|
|
303
|
+
holder: ALICE,
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('a full page that is all junk has not been read completely, never "not listed"', async () => {
|
|
308
|
+
const { w, deps } = world();
|
|
309
|
+
w.relays.set('wss://relay.ochk.io', []);
|
|
310
|
+
const real = deps.page;
|
|
311
|
+
deps.page = async (relay, filter) =>
|
|
312
|
+
relay === 'wss://relay.ochk.io'
|
|
313
|
+
? { events: [], complete: true, sent: filter.limit }
|
|
314
|
+
: real(relay, filter);
|
|
315
|
+
expect(await resolveDirectoryHandle('alice', 'people', deps)).toEqual({
|
|
316
|
+
result: 'E_DIR_UNRESOLVED',
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('an audit relay that pads a page with junk is read past it, so the earlier claim it holds counts', async () => {
|
|
321
|
+
const { w, deps, anchorAt, publish } = world();
|
|
322
|
+
const a = claim('alice', ALICE, TIP - 100);
|
|
323
|
+
const b = claim('alice', BOB, TIP - 50, 'bob');
|
|
324
|
+
anchorAt(a, ALICE);
|
|
325
|
+
anchorAt(b, BOB);
|
|
326
|
+
w.bound.set(ALICE, [pk(1)]);
|
|
327
|
+
w.bound.set(BOB, [pk(2)]);
|
|
328
|
+
publish(listing(2, 'alice', BOB, b));
|
|
329
|
+
w.relays.set('wss://nos.lol', [
|
|
330
|
+
listing(2, 'alice', BOB, b, {}, 1_790_000_900),
|
|
331
|
+
listing(1, 'alice', ALICE, a, {}, 1_790_000_000),
|
|
332
|
+
]);
|
|
333
|
+
const real = deps.page;
|
|
334
|
+
deps.page = async (relay, filter) => {
|
|
335
|
+
const got = await real(relay, filter);
|
|
336
|
+
if (relay !== 'wss://nos.lol' || filter.until !== undefined) return got;
|
|
337
|
+
return { events: got.events.slice(0, 1), complete: true, sent: filter.limit };
|
|
338
|
+
};
|
|
339
|
+
expect(await resolveDirectoryHandle('alice', 'people', deps)).toMatchObject({
|
|
340
|
+
result: 'holds',
|
|
341
|
+
holder: ALICE,
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
286
345
|
it('a directory relay that stalls on a full page has not been read completely', async () => {
|
|
287
346
|
const { w, deps } = world();
|
|
288
347
|
const same = Array.from({ length: 200 }, (_, i) =>
|
package/src/directory.ts
CHANGED
|
@@ -49,11 +49,21 @@ const AUDIT_PAGES = 2;
|
|
|
49
49
|
/** Distinct header reads one resolution may make; past this a header is unread. */
|
|
50
50
|
const HEADER_BUDGET = 128;
|
|
51
51
|
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* One page from one relay: its events, whether the relay finished answering
|
|
54
|
+
* (EOSE), and `sent`, how many events the relay sent, the ones the reader dropped
|
|
55
|
+
* (a bad signature) included. A page is full by what the relay sent, not by what
|
|
56
|
+
* was kept: a relay that pads a page with junk has more behind it. Without
|
|
57
|
+
* `sent`, the events kept are the count.
|
|
58
|
+
*/
|
|
53
59
|
export type PageRead = (
|
|
54
60
|
relay: string,
|
|
55
61
|
filter: { kinds: number[]; '#d': string[]; limit: number; until?: number }
|
|
56
|
-
) => Promise<{ events: NostrEvent[]; complete: boolean }>;
|
|
62
|
+
) => Promise<{ events: NostrEvent[]; complete: boolean; sent?: number }>;
|
|
63
|
+
|
|
64
|
+
/** How many events a page's relay sent: `sent`, or the events kept when it is not given. */
|
|
65
|
+
const sentOn = (got: { events: NostrEvent[]; sent?: number }) =>
|
|
66
|
+
Math.max(got.sent ?? 0, got.events.length);
|
|
57
67
|
|
|
58
68
|
export interface WalkDeps {
|
|
59
69
|
page: PageRead;
|
|
@@ -148,7 +158,7 @@ async function readAll(
|
|
|
148
158
|
until = until === undefined ? ev.created_at : Math.min(until, ev.created_at);
|
|
149
159
|
}
|
|
150
160
|
// A short page is the end; a full page that adds nothing is a stalled read.
|
|
151
|
-
if (got
|
|
161
|
+
if (sentOn(got) < PAGE) return [...seen.values()];
|
|
152
162
|
if (added === 0) return null;
|
|
153
163
|
}
|
|
154
164
|
return null;
|
|
@@ -164,7 +174,7 @@ async function readSome(
|
|
|
164
174
|
const seen = new Map<string, NostrEvent>();
|
|
165
175
|
let until: number | undefined;
|
|
166
176
|
for (let i = 0; i < pages; i++) {
|
|
167
|
-
let got:
|
|
177
|
+
let got: Awaited<ReturnType<PageRead>>;
|
|
168
178
|
try {
|
|
169
179
|
got = await page(relay, {
|
|
170
180
|
kinds: [30114],
|
|
@@ -179,7 +189,7 @@ async function readSome(
|
|
|
179
189
|
seen.set(ev.id, ev);
|
|
180
190
|
until = until === undefined ? ev.created_at : Math.min(until, ev.created_at);
|
|
181
191
|
}
|
|
182
|
-
if (!got.complete || got
|
|
192
|
+
if (!got.complete || sentOn(got) < PAGE) break;
|
|
183
193
|
}
|
|
184
194
|
return [...seen.values()];
|
|
185
195
|
}
|